Compare commits

...

1 Commits

Author SHA1 Message Date
chen08209
9271d59f72 Fix windows tun issues
Fix windows backup recovery issues

Optimize overwrite handle

Optimize access control page

Optimize some details
2025-11-28 17:35:27 +08:00
114 changed files with 7349 additions and 3518 deletions

View File

@@ -64,22 +64,25 @@ jobs:
cache-dependency-path: |
core/go.sum
- name: Setup Flutter Master
if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')
uses: subosito/flutter-action@v2
with:
channel: 'master'
cache: true
- name: Setup Flutter
if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }}
uses: subosito/flutter-action@v2
with:
channel: 'stable'
channel: stable
flutter-version: 3.35.7
cache: true
- name: Setup Flutter With Other
if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')
uses: subosito/flutter-action@v2
with:
channel: master
flutter-version: 3.35.7
cache: true
# flutter-version: 3.29.3
- name: Get Flutter Dependency
run: flutter pub get
run: |
flutter --version
flutter pub get
- name: Setup
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }}
@@ -104,34 +107,26 @@ jobs:
- name: Generate
if: ${{ env.IS_STABLE == 'true' }}
run: |
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
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
echo "## $currentTag" >> NEW_CHANGELOG.md
echo "" >> NEW_CHANGELOG.md
if [ -n "$tag" ]; then
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
else
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
fi
echo "" >> NEW_CHANGELOG.md
fi
currentTag=$tag
last_ver=$(grep -m1 '^## ' CHANGELOG.md 2>/dev/null | sed 's/^## //')
tags=($(git tag --merged HEAD --sort=-creatordate))
temp="NEW_CHANGELOG.md" > "$temp"
for i in "${!tags[@]}"; do
curr="${tags[i]}"
[[ "$curr" == "$last_ver" ]] && break
prev="${tags[i+1]}"
range="${prev:+$prev..}$curr"
echo -e "## $curr\n" >> "$temp"
git log --no-merges --pretty=format:"%B" "$range" | \
awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$temp"
done
cat CHANGELOG.md >> NEW_CHANGELOG.md
cat NEW_CHANGELOG.md > CHANGELOG.md
[ -f CHANGELOG.md ] && cat CHANGELOG.md >> "$temp"
mv "$temp" CHANGELOG.md
- name: Commit
if: ${{ env.IS_STABLE == 'true' }}
@@ -181,31 +176,22 @@ jobs:
- name: Generate release.md
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
tags=($(git tag --merged HEAD --sort=-creatordate))
preTag=$(curl -s "https://api.github.com/repos/chen08209/FlClash/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' || echo "")
out="release.md" > "$out"
for i in "${!tags[@]}"; do
curr="${tags[i]}"
[[ "$curr" == "$preTag" ]] && break
prev="${tags[i+1]}"
range="${prev:+$prev..}$curr"
echo -e "## $curr\n" >> "$out"
git log --no-merges --pretty=format:"%B" "$range" | \
awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$out"
done
- name: Push to telegram
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}

View File

@@ -1,944 +0,0 @@
## v0.8.89
- Fix some issues
- Optimize Windows service mode
- Update core
- Update changelog
## v0.8.88
- Add android separates the core process
- Support core status check and force restart
- Optimize proxies page and access page
- Update flutter and pub dependencies
- Update go version
- Optimize more details
- Update changelog
## v0.8.87
- Optimize desktop view
- Optimize logs, requests, connection pages
- Optimize windows tray auto hide
- Optimize some details
- Update core
- Update changelog
## v0.8.86
- Fix windows tun issues
- Optimize android get system dns
- Optimize more details
- Update changelog
## v0.8.85
- Support override script
- Support proxies search
- Support svg display
- Optimize config persistence
- Add some scenes auto close connections
- Update core
- Optimize more details
## v0.8.84
- Fix windows service verify issues
- Update changelog
## v0.8.83
- Add windows server mode start process verify
- Add linux deb dependencies
- Add backup recovery strategy select
- Support custom text scaling
- Optimize the display of different text scale
- Optimize windows setup experience
- Optimize startTun performance
- Optimize android tv experience
- Optimize default option
- Optimize computed text size
- Optimize hyperOS freeform window
- Add developer mode
- Update core
- Optimize more details
- Add issues template
- Update changelog
## v0.8.82
- Optimize android vpn performance
- Add custom primary color and color scheme
- Add linux nad windows arm release
- Optimize requests and logs page
- Fix map input page delete issues
- Update changelog
## v0.8.81
- Add rule override
- Update core
- Optimize more details
- Update changelog
## v0.8.80
- Optimize dashboard performance
- Fix some issues
- Fix unselected proxy group delay issues
- Fix asn url issues
- Update changelog
## v0.8.79
- Fix tab delay view issues
- Fix tray action issues
- Fix get profile redirect client ua issues
- Fix proxy card delay view issues
- Add Russian, Japanese adaptation
- Fix some issues
- Update changelog
## v0.8.78
- Fix list form input view issues
- Fix traffic view issues
- Update changelog
## v0.8.77
- Optimize performance
- Update core
- Optimize core stability
- Fix linux tun authority check error
- Fix some issues
- Fix scroll physics error
- Update changelog
## v0.8.75
- Add windows storage corruption detection
- Fix core crash caused by windows resource manager restart
- Optimize logs, requests, access to pages
- Fix macos bypass domain issues
- Update changelog
## v0.8.74
- Fix some issues
- Update changelog
## v0.8.73
- Update popup menu
- Add file editor
- Fix android service issues
- Optimize desktop background performance
- Optimize android main process performance
- Optimize delay test
- Optimize vpn protect
- Update changelog
## v0.8.72
- Update core
- Fix some issues
- Update changelog
## v0.8.71
- Remake dashboard
- Optimize theme
- Optimize more details
- Update flutter version
- Update changelog
## v0.8.70
- Support better window position memory
- Add windows arm64 and linux arm64 build script
- Optimize some details
## v0.8.69
- Remake desktop
- Optimize change proxy
- Optimize network check
- Fix fallback issues
- Optimize lots of details
- Update change.yaml
- Fix android tile issues
- Fix windows tray issues
- Support setting bypassDomain
- Update flutter version
- Fix android service issues
- Fix macos dock exit button issues
- Add route address setting
- Optimize provider view
- Update changelog
- Update CHANGELOG.md
## v0.8.67
- Add android shortcuts
- Fix init params issues
- Fix dynamic color issues
- Optimize navigator animate
- Optimize window init
- Optimize fab
- Optimize save
## v0.8.66
- Fix the collapse issues
- Add fontFamily options
## v0.8.65
- Update core version
- Update flutter version
- Optimize ip check
- Optimize url-test
## v0.8.64
- Update release message
- Init auto gen changelog
- Fix windows tray issues
- Fix urltest issues
- Add auto changelog
- Fix windows admin auto launch issues
- Add android vpn options
- Support proxies icon configuration
- Optimize android immersion display
- Fix some issues
- 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
## 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

@@ -92,8 +92,9 @@ object State {
}
fun handleStartService() {
val appPlugin = flutterEngine?.plugin<AppPlugin>()
if (appPlugin != null) {
appPlugin?.requestNotificationsPermission {
appPlugin.requestNotificationsPermission {
startService()
}
return

View File

@@ -340,6 +340,8 @@
"none": "none",
"basicConfig": "Basic configuration",
"basicConfigDesc": "Modify the basic configuration globally",
"advancedConfig": "Advanced configuration",
"advancedConfigDesc": "Provide diverse configuration options",
"selectedCountTitle": "{count} items have been selected",
"addRule": "Add rule",
"ruleName": "Rule name",
@@ -390,7 +392,7 @@
"existsTip": "Current {label} already exists",
"deleteTip": "Are you sure you want to delete the current {label}?",
"deleteMultipTip": "Are you sure you want to delete the selected {label}?",
"nullTip": "No {label} at the moment",
"nullTip": "No {label} yet",
"script": "Script",
"color": "Color",
"rename": "Rename",
@@ -434,5 +436,32 @@
"crashlytics": "Crash Analysis",
"crashlyticsTip": "When enabled, automatically uploads crash logs without sensitive information when the app crashes",
"appendSystemDns": "Append System DNS",
"appendSystemDnsTip": "Forcefully append system DNS to the configuration"
"appendSystemDnsTip": "Forcefully append system DNS to the configuration",
"editRule": "Edit rule",
"overrideMode": "Override mode",
"standardModeDesc": "Standard mode, override basic configuration, provide simple rule addition capability",
"scriptModeDesc": "Script mode, use external extension scripts, provide one-click override configuration capability",
"addedRules": "Added rules",
"controlGlobalAddedRules": "Control global added rules",
"overrideScript": "Override script",
"goToConfigureScript": "Go to configure script",
"editGlobalRules": "Edit global rules",
"externalFetch": "External fetch",
"confirmForceCrashCore": "Are you sure you want to force crash the core?",
"confirmClearAllData": "Are you sure you want to clear all data?",
"loadTest": "Load test",
"yearsAgo": "{count, plural, =1{1 year ago} other{{count} years ago}}",
"monthsAgo": "{count, plural, =1{1 month ago} other{{count} months ago}}",
"daysAgo": "{count, plural, =1{1 day ago} other{{count} days ago}}",
"hoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}",
"minutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}",
"justNow": "Just now",
"noLongerRemind": "Don't remind again",
"accessControlSettings": "Access Control Settings",
"turnOn": "Turn On",
"turnOff": "Turn Off",
"coreConfigChangeDetected": "Core configuration change detected",
"reload": "Reload",
"vpnConfigChangeDetected": "VPN configuration change detected",
"restart": "Restart"
}

View File

@@ -340,6 +340,8 @@
"none": "なし",
"basicConfig": "基本設定",
"basicConfigDesc": "基本設定をグローバルに変更",
"advancedConfig": "高度な設定",
"advancedConfigDesc": "多様な設定を提供",
"selectedCountTitle": "{count} 項目が選択されています",
"addRule": "ルールを追加",
"ruleName": "ルール名",
@@ -391,7 +393,7 @@
"existsTip": "現在の{label}は既に存在しています",
"deleteTip": "現在の{label}を削除してもよろしいですか?",
"deleteMultipTip": "選択された{label}を削除してもよろしいですか?",
"nullTip": "現在{label}はありません",
"nullTip": "まだ{label}はありません",
"script": "スクリプト",
"color": "カラー",
"rename": "リネーム",
@@ -435,5 +437,32 @@
"crashlytics": "クラッシュ分析",
"crashlyticsTip": "有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします",
"appendSystemDns": "システムDNSを追加",
"appendSystemDnsTip": "設定にシステムDNSを強制的に追加します"
"appendSystemDnsTip": "設定にシステムDNSを強制的に追加します",
"editRule": "ルールを編集",
"overrideMode": "上書きモード",
"standardModeDesc": "標準モード、基本設定を上書きし、シンプルなルール追加機能を提供",
"scriptModeDesc": "スクリプトモード、外部拡張スクリプトを使用し、ワンクリックで設定を上書きする機能を提供",
"addedRules": "追加ルール",
"controlGlobalAddedRules": "グローバル追加ルールを制御",
"overrideScript": "上書きスクリプト",
"goToConfigureScript": "スクリプト設定に移動",
"editGlobalRules": "グローバルルールを編集",
"externalFetch": "外部取得",
"confirmForceCrashCore": "コアを強制的にクラッシュさせてもよろしいですか?",
"confirmClearAllData": "すべてのデータをクリアしてもよろしいですか?",
"loadTest": "読み込みテスト",
"yearsAgo": "{count}年前",
"monthsAgo": "{count}ヶ月前",
"daysAgo": "{count}日前",
"hoursAgo": "{count}時間前",
"minutesAgo": "{count}分前",
"justNow": "たった今",
"noLongerRemind": "今後表示しない",
"accessControlSettings": "アクセス制御設定",
"turnOn": "オン",
"turnOff": "オフ",
"coreConfigChangeDetected": "コア設定の変更が検出されました",
"reload": "リロード",
"vpnConfigChangeDetected": "VPN設定の変更が検出されました",
"restart": "再起動"
}

View File

@@ -340,6 +340,8 @@
"none": "Нет",
"basicConfig": "Базовая конфигурация",
"basicConfigDesc": "Глобальное изменение базовых настроек",
"advancedConfig": "Расширенная конфигурация",
"advancedConfigDesc": "Предоставляет разнообразные варианты конфигурации",
"selectedCountTitle": "Выбрано {count} элементов",
"addRule": "Добавить правило",
"ruleName": "Название правила",
@@ -391,7 +393,7 @@
"existsTip": "Текущий {label} уже существует",
"deleteTip": "Вы уверены, что хотите удалить текущий {label}?",
"deleteMultipTip": "Вы уверены, что хотите удалить выбранные {label}?",
"nullTip": "Сейчас {label} нет",
"nullTip": "{label} пока отсутствуют",
"script": "Скрипт",
"color": "Цвет",
"rename": "Переименовать",
@@ -435,5 +437,32 @@
"crashlytics": "Анализ сбоев",
"crashlyticsTip": "При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя",
"appendSystemDns": "Добавить системный DNS",
"appendSystemDnsTip": "Принудительно добавить системный DNS к конфигурации"
"appendSystemDnsTip": "Принудительно добавить системный DNS к конфигурации",
"editRule": "Редактировать правило",
"overrideMode": "Режим переопределения",
"standardModeDesc": "Стандартный режим, переопределение базовой конфигурации, предоставление возможности простого добавления правил",
"scriptModeDesc": "Режим скрипта, использование внешних расширяющих скриптов, предоставление возможности переопределения конфигурации одним кликом",
"addedRules": "Добавленные правила",
"controlGlobalAddedRules": "Управление глобальными добавленными правилами",
"overrideScript": "Скрипт переопределения",
"goToConfigureScript": "Перейти к настройке скрипта",
"editGlobalRules": "Редактировать глобальные правила",
"externalFetch": "Внешнее получение",
"confirmForceCrashCore": "Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
"confirmClearAllData": "Вы уверены, что хотите очистить все данные?",
"loadTest": "Тест загрузки",
"yearsAgo": "{count, plural, one{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} года назад}}",
"monthsAgo": "{count, plural, one{{count} месяц назад} few{{count} месяца назад} many{{count} месяцев назад} other{{count} месяца назад}}",
"daysAgo": "{count, plural, one{{count} день назад} few{{count} дня назад} many{{count} дней назад} other{{count} дня назад}}",
"hoursAgo": "{count, plural, one{{count} час назад} few{{count} часа назад} many{{count} часов назад} other{{count} часа назад}}",
"minutesAgo": "{count, plural, one{{count} минута назад} few{{count} минуты назад} many{{count} минут назад} other{{count} минуты назад}}",
"justNow": "Только что",
"noLongerRemind": "Больше не напоминать",
"accessControlSettings": "Настройки контроля доступа",
"turnOn": "Включить",
"turnOff": "Выключить",
"coreConfigChangeDetected": "Обнаружено изменение конфигурации ядра",
"reload": "Перезагрузить",
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
"restart": "Перезапустить"
}

View File

@@ -340,6 +340,8 @@
"none": "无",
"basicConfig": "基本配置",
"basicConfigDesc": "全局修改基本配置",
"advancedConfig": "进阶配置",
"advancedConfigDesc": "提供多样化配置",
"selectedCountTitle": "已选择 {count} 项",
"addRule": "添加规则",
"ruleName": "规则名称",
@@ -435,5 +437,32 @@
"crashlytics": "崩溃分析",
"crashlyticsTip": "开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志",
"appendSystemDns": "追加系统DNS",
"appendSystemDnsTip": "强制为配置附加系统DNS"
"appendSystemDnsTip": "强制为配置附加系统DNS",
"editRule": "编辑规则",
"overrideMode": "覆写模式",
"standardModeDesc": "标准模式,覆写基本配置,提供简单追加规则能力",
"scriptModeDesc": "脚本模式,使用外部扩展脚本,提供一键覆写配置的能力",
"addedRules": "附加规则",
"controlGlobalAddedRules": "控制全局附加规则",
"overrideScript": "覆写脚本",
"goToConfigureScript": "前往配置脚本",
"editGlobalRules": "编辑全局规则",
"externalFetch": "外部获取",
"confirmForceCrashCore": "确定要强制崩溃核心?",
"confirmClearAllData": "确定要清除所有数据?",
"loadTest": "加载测试",
"yearsAgo": "{count} 年前",
"monthsAgo": "{count} 个月前",
"daysAgo": "{count} 天前",
"hoursAgo": "{count} 小时前",
"minutesAgo": "{count} 分钟前",
"justNow": "刚刚",
"noLongerRemind": "不再提示",
"accessControlSettings": "访问控制设置",
"turnOn": "开启",
"turnOff": "关闭",
"coreConfigChangeDetected": "检测到核心配置更改",
"reload": "重载",
"vpnConfigChangeDetected": "检测到VPN相关配置改动",
"restart": "重启"
}

View File

@@ -0,0 +1,23 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="95" y="90" width="36" height="12" rx="4" fill="#E8DEF8"/>
<rect x="140" y="90" width="65" height="12" rx="6" fill="#E8DEF8"/>
<path d="M95 118H205" stroke="#E8DEF8" stroke-width="2" stroke-dasharray="4 4"/>
<rect x="95" y="138" width="40" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
<rect x="145" y="138" width="50" height="12" rx="6" fill="#E8DEF8" opacity="0.5"/>
<rect x="95" y="162" width="55" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
<rect x="160" y="162" width="30" height="12" rx="6" fill="#E8DEF8" opacity="0.5"/>
<g transform="translate(210, 210)">
<circle cx="0" cy="0" r="38" fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
<path d="M-10 16V-16M-10 -16L-18 -8M-10 -16L-2 -8" stroke="#FDF7FF" stroke-width="5" stroke-linecap="round"
stroke-linejoin="round"/>
<path d="M10 -16V16M10 16L2 8M10 16L18 8" stroke="#FDF7FF" stroke-width="5" stroke-linecap="round"
stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,9 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="100" y="90" width="100" height="12" rx="6" fill="#E8DEF8"/>
<rect x="100" y="115" width="70" height="12" rx="6" fill="#E8DEF8"/>
<rect x="100" y="140" width="80" height="12" rx="6" fill="#E8DEF8"/>
<rect x="155" y="170" width="80" height="60" rx="12" fill="#6750A4"/>
<rect x="150" y="165" width="90" height="18" rx="6" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
<rect x="185" y="200" width="20" height="6" rx="3" fill="#FDF7FF" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 700 B

View File

@@ -0,0 +1,25 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90 200C90 180 100 165 150 165C200 165 210 180 210 200V220C210 231.046 201.046 240 190 240H110C98.9543 240 90 231.046 90 220V200Z"
fill="#E8DEF8"/>
<rect x="75" y="85" width="150" height="100" rx="30" fill="#6750A4"/>
<rect x="85" y="95" width="130" height="80" rx="22" fill="#6750A4" stroke="#7D66B5" stroke-width="2"/>
<path d="M110 135 C110 142 118 148 128 148 C138 148 146 142 146 135" stroke="#FDF7FF" stroke-width="4"
stroke-linecap="round"/>
<path d="M154 135 C154 142 162 148 172 148 C182 148 190 142 190 135" stroke="#FDF7FF" stroke-width="4"
stroke-linecap="round"/>
<circle cx="150" cy="160" r="4" fill="#E8DEF8" opacity="0.5"/>
<path d="M150 85 V 65" stroke="#6750A4" stroke-width="4" stroke-linecap="round"/>
<circle cx="150" cy="60" r="8" fill="#6750A4"/>
<circle cx="150" cy="60" r="3" fill="#FDF7FF"/>
<path d="M220 70 L235 70 L220 85 H235" stroke="#6750A4" stroke-width="3" stroke-linecap="round"
stroke-linejoin="round"/>
<path d="M245 40 L255 40 L245 50 H255" stroke="#E8DEF8" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"/>
<path d="M90 185 H210" stroke="#000" stroke-width="4" stroke-opacity="0.1" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,37 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60 94V84C60 72.9543 68.9543 64 80 64H130C141.046 64 150 72.9543 150 84V94H220C231.046 94 240 102.954 240 114V210C240 221.046 231.046 230 220 230H80C68.9543 230 60 221.046 60 210V94Z"
fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="90" y="124" width="60" height="12" rx="6" fill="#E8DEF8"/>
<rect x="90" y="154" width="50" height="12" rx="6" fill="#E8DEF8"/>
<rect x="90" y="184" width="40" height="12" rx="6" fill="#E8DEF8"/>
<rect x="180" y="184" width="30" height="12" rx="6" fill="#E8DEF8" opacity="0.6"/>
<circle cx="186" cy="190" r="6" fill="#E8DEF8"/>
<g transform="translate(210, 210)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M0 -32 C-17.67 -32 -32 -17.67 -32 0 C-32 17.67 -17.67 32 0 32 C17.67 32 32 17.67 32 0 C32 -17.67 17.67 -32 0 -32ZM0 -8 C-4.42 -8 -8 -4.42 -8 0 C-8 4.42 -4.42 8 0 8 C4.42 8 8 4.42 8 0 C8 -4.42 4.42 -8 0 -8Z"
fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
<rect x="-5" y="-38" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
<rect x="-5" y="26" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
<rect x="-38" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
<rect x="26" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
<rect x="-5" y="-38" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
transform="rotate(45)"/>
<rect x="-5" y="26" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
transform="rotate(45)"/>
<rect x="-38" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
transform="rotate(45)"/>
<rect x="26" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
transform="rotate(45)"/>
<circle cx="0" cy="0" r="22" fill="#6750A4"/>
<circle cx="0" cy="0" r="8" fill="#FDF7FF" opacity="0.8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,18 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="95" y="90" width="110" height="16" rx="4" fill="#E8DEF8"/>
<circle cx="105" cy="98" r="3" fill="#FDF7FF"/>
<circle cx="115" cy="98" r="3" fill="#FDF7FF"/>
<rect x="95" y="120" width="110" height="16" rx="4" fill="#E8DEF8"/>
<circle cx="105" cy="128" r="3" fill="#FDF7FF"/>
<circle cx="115" cy="128" r="3" fill="#FDF7FF"/>
<rect x="95" y="150" width="80" height="16" rx="4" fill="#E8DEF8"/>
<circle cx="105" cy="158" r="3" fill="#FDF7FF"/>
<circle cx="115" cy="158" r="3" fill="#FDF7FF"/>
<circle cx="195" cy="195" r="30" fill="#6750A4"/>
<rect x="180" y="193" width="30" height="4" rx="2" fill="#FDF7FF" transform="rotate(45 195 195)"/>
<circle cx="183" cy="183" r="4" fill="#FDF7FF"/>
<circle cx="207" cy="207" r="4" fill="#FDF7FF"/>
<path d="M175 158 H190 V165" stroke="#E8DEF8" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,31 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="95" y="90" width="80" height="12" rx="6" fill="#E8DEF8"/>
<rect x="185" y="90" width="25" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
<rect x="95" y="125" width="60" height="12" rx="6" fill="#E8DEF8"/>
<rect x="165" y="125" width="45" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
<rect x="95" y="160" width="70" height="12" rx="6" fill="#E8DEF8"/>
<rect x="175" y="160" width="35" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
<g transform="translate(210, 210)">
<circle cx="0" cy="0" r="36" fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
<circle cx="0" cy="0" r="24" stroke="#FDF7FF" stroke-width="3"/>
<path d="M-24 0C-24 0 -12 8 0 8C12 8 24 0 24 0" stroke="#FDF7FF" stroke-width="3" stroke-linecap="round"
stroke-linejoin="round"/>
<ellipse cx="0" cy="0" rx="10" ry="24" stroke="#FDF7FF" stroke-width="3"/>
<circle cx="14" cy="-12" r="3" fill="#FDF7FF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,19 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
<rect x="100" y="90" width="30" height="12" rx="6" fill="#E8DEF8"/>
<rect x="136" y="90" width="50" height="12" rx="6" fill="#E8DEF8"/>
<rect x="120" y="120" width="80" height="12" rx="6" fill="#E8DEF8"/>
<rect x="120" y="150" width="50" height="12" rx="6" fill="#E8DEF8"/>
<rect x="100" y="180" width="20" height="12" rx="6" fill="#E8DEF8"/>
<g transform="translate(165, 160)">
<rect x="0" y="0" width="80" height="80" rx="20" fill="#6750A4" stroke="#FDF7FF" stroke-width="4"/>
<path d="M28 30L18 40L28 50" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M52 30L62 40L52 50" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M46 26L34 54" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" opacity="0.8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -17,6 +17,7 @@ import (
"github.com/metacubex/mihomo/constant/features"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log"
@@ -263,7 +264,7 @@ func setupConfig(params *SetupParams) error {
defer runLock.Unlock()
var err error
constant.DefaultTestURL = params.TestURL
currentConfig, err = parseWithPath(filepath.Join(constant.Path.HomeDir(), "config.json"))
currentConfig, err = executor.ParseWithPath(filepath.Join(constant.Path.HomeDir(), "config.yaml"))
if err != nil {
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
}

View File

@@ -18,8 +18,8 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/enfein/mieru/v3 v3.20.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/enfein/mieru/v3 v3.22.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
@@ -33,7 +33,7 @@ require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/gofrs/uuid/v5 v5.4.0 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
@@ -46,32 +46,32 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d // indirect
github.com/metacubex/ascon v0.1.0 // indirect
github.com/metacubex/bart v0.24.0 // indirect
github.com/metacubex/bart v0.26.0 // indirect
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
github.com/metacubex/blake3 v0.1.0 // indirect
github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b // indirect
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // indirect
github.com/metacubex/sing-mux v0.3.4 // indirect
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 // indirect
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.8 // indirect
github.com/metacubex/sing-tun v0.4.9 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect
github.com/metacubex/utls v1.8.1 // indirect
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect
github.com/metacubex/utls v1.8.3 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect
@@ -84,7 +84,7 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
@@ -96,7 +96,6 @@ require (
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
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.33.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect

View File

@@ -22,10 +22,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.20.0 h1:1ob7pCIVSH5FYFAfYvim8isLW1vBOS4cFOUF9exJS38=
github.com/enfein/mieru/v3 v3.20.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -54,8 +54,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
@@ -86,12 +86,12 @@ 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-20250902133113-a7f637c14281 h1:09EM0sOLb2kfL0KETGhHujsBLB5iy5U/2yHRHsxf/pI=
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c=
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
github.com/metacubex/bart v0.24.0 h1:EyNiPeVOlg0joSHTzi5oentI0j5M89utUq/5dd76pWM=
github.com/metacubex/bart v0.24.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bart v0.26.0 h1:d/bBTvVatfVWGfQbiDpYKI1bXUJgjaabB2KpK1Tnk6w=
github.com/metacubex/bart v0.26.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
@@ -104,12 +104,12 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b h1:z7JLKjugnQ1qvDOAD8yMA5C8AlJY3bG+VrrgRntRlUY=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
@@ -119,26 +119,26 @@ github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 h1:dGvo7UahC/gYBQNBoictr14baJzBjAKUAorP63QFFtg=
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.8 h1:3PyiUKWXYi37yHptXskzL1723O3OUdyt0Aej4XHVikM=
github.com/metacubex/sing-tun v0.4.8/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1 h1:RW8GeCGWAegjV0HW5nw9DoqNoeGAXXeYUP6AysmRvx4=
github.com/metacubex/utls v1.8.1/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -170,8 +170,8 @@ 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/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
@@ -211,7 +211,6 @@ gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+r
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
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=

View File

@@ -24,15 +24,14 @@ class Application extends ConsumerStatefulWidget {
}
class ApplicationState extends ConsumerState<Application> {
Timer? _autoUpdateGroupTaskTimer;
Timer? _autoUpdateProfilesTaskTimer;
final _pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: CommonPageTransitionsBuilder(),
TargetPlatform.windows: CommonPageTransitionsBuilder(),
TargetPlatform.linux: CommonPageTransitionsBuilder(),
TargetPlatform.macOS: CommonPageTransitionsBuilder(),
TargetPlatform.android: commonSharedXPageTransitions,
TargetPlatform.windows: commonSharedXPageTransitions,
TargetPlatform.linux: commonSharedXPageTransitions,
TargetPlatform.macOS: commonSharedXPageTransitions,
},
);
@@ -102,7 +101,7 @@ class ApplicationState extends ConsumerState<Application> {
}
Widget _buildApp({required Widget child}) {
return MessageManager(child: ThemeManager(child: child));
return StatusManager(child: ThemeManager(child: child));
}
@override
@@ -162,7 +161,6 @@ class ApplicationState extends ConsumerState<Application> {
@override
Future<void> dispose() async {
linkManager.destroy();
_autoUpdateGroupTaskTimer?.cancel();
_autoUpdateProfilesTaskTimer?.cancel();
await coreController.destroy();
await globalState.appController.savePreferences();

View File

@@ -1,3 +1,3 @@
import 'package:fl_clash/l10n/l10n.dart';
final appLocalizations = AppLocalizations.current;
final appLocalizations = AppLocalizations.current;

View File

@@ -7,6 +7,9 @@ import 'package:path/path.dart';
extension ArchiveExt on Archive {
void addDirectoryToArchive(String dirPath, String parentPath) {
final dir = Directory(dirPath);
if (!dir.existsSync()) {
return;
}
final entities = dir.listSync(recursive: false);
for (final entity in entities) {
final relativePath = relative(entity.path, from: parentPath);

View File

@@ -38,3 +38,4 @@ export 'text.dart';
export 'tray.dart';
export 'utils.dart';
export 'window.dart';
export 'yaml.dart';

View File

@@ -23,6 +23,12 @@ final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
);
final listHeaderPadding = EdgeInsets.only(
left: 16.ap,
right: 8.ap,
top: 24.ap,
bottom: 8.ap,
);
final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
@@ -63,6 +69,7 @@ const stringListEquality = ListEquality<String>();
const intListEquality = ListEquality<int>();
const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>();
const ruleEquality = ListEquality<Rule>();
const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>();

View File

@@ -1,4 +1,6 @@
import 'package:fl_clash/manager/message_manager.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/models/widget.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
@@ -7,8 +9,11 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<CommonScaffoldState>();
}
void showNotifier(String text) {
return findAncestorStateOfType<MessageManagerState>()?.message(text);
void showNotifier(String text, {MessageActionState? actionState}) {
return findAncestorStateOfType<StatusManagerState>()?.message(
text,
actionState: actionState,
);
}
void showSnackBar(String message, {SnackBarAction? action}) {
@@ -42,6 +47,8 @@ extension BuildContextExtension on BuildContext {
TextTheme get textTheme => Theme.of(this).textTheme;
AppLocalizations get appLocalizations => AppLocalizations.of(this);
T? findLastStateOfType<T extends State>() {
T? state;

View File

@@ -17,23 +17,25 @@ extension DateTimeExtension on DateTime {
final difference = currentDateTime.difference(this);
final days = difference.inDays;
if (days >= 365) {
return '${(days / 365).floor()} ${appLocalizations.years}${appLocalizations.ago}';
final years = (days / 365).floor();
return appLocalizations.yearsAgo(years);
}
if (days >= 30) {
return '${(days / 30).floor()} ${appLocalizations.months}${appLocalizations.ago}';
final months = (days / 30).floor();
return appLocalizations.monthsAgo(months);
}
if (days >= 1) {
return '$days ${appLocalizations.days}${appLocalizations.ago}';
return appLocalizations.daysAgo(days);
}
final hours = difference.inHours;
if (hours >= 1) {
return '$hours ${appLocalizations.hours}${appLocalizations.ago}';
return appLocalizations.hoursAgo(hours);
}
final minutes = difference.inMinutes;
if (minutes >= 1) {
return '$minutes ${appLocalizations.minutes}${appLocalizations.ago}';
return appLocalizations.minutesAgo(minutes);
}
return appLocalizations.just;
return appLocalizations.justNow;
}
String get show {

View File

@@ -10,14 +10,14 @@ extension FutureExt<T> on Future<T> {
VoidCallback? onLast,
FutureOr<T> Function()? onTimeout,
}) {
final realTimout = timeout ?? const Duration(minutes: 3);
Timer(realTimout + commonDuration, () {
final realTimeout = timeout ?? const Duration(minutes: 3);
Timer(realTimeout + commonDuration, () {
if (onLast != null) {
onLast();
}
});
return this.timeout(
realTimout,
realTimeout,
onTimeout: () async {
if (onTimeout != null) {
return onTimeout();

View File

@@ -23,10 +23,7 @@ extension IterableExt<T> on Iterable<T> {
}
}
Iterable<T> fill(
int length, {
required T Function(int count) filler,
}) sync* {
Iterable<T> fill(int length, {required T Function(int count) filler}) sync* {
int count = 0;
for (var item in this) {
yield item;
@@ -85,6 +82,24 @@ extension ListExt<T> on List<T> {
if (length > index) return this[index];
return last;
}
void addOrRemove(T value) {
if (contains(value)) {
remove(value);
} else {
add(value);
}
}
}
extension SetExt<T> on Set<T> {
void addOrRemove(T value) {
if (contains(value)) {
remove(value);
} else {
add(value);
}
}
}
extension DoubleListExt on List<double> {

View File

@@ -8,26 +8,16 @@ class Measure {
final Map<String, dynamic> _measureMap;
Measure.of(this.context, double textScaleFactor)
: _measureMap = {},
_textScaler = TextScaler.linear(
textScaleFactor,
);
: _measureMap = {},
_textScaler = TextScaler.linear(textScaleFactor);
Size computeTextSize(
Text text, {
double maxWidth = double.infinity,
}) {
Size computeTextSize(Text text, {double maxWidth = double.infinity}) {
final textPainter = TextPainter(
text: TextSpan(
text: text.data,
style: text.style,
),
text: TextSpan(text: text.data, style: text.style),
maxLines: text.maxLines,
textScaler: _textScaler,
textDirection: text.textDirection ?? TextDirection.ltr,
)..layout(
maxWidth: maxWidth,
);
)..layout(maxWidth: maxWidth);
return textPainter.size;
}
@@ -35,10 +25,7 @@ class Measure {
return _measureMap.updateCacheValue(
'bodyMediumHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.bodyMedium,
),
Text('X', style: context.textTheme.bodyMedium),
).height,
);
}
@@ -46,24 +33,16 @@ class Measure {
double get bodyLargeHeight {
return _measureMap.updateCacheValue(
'bodyLargeHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.bodyLarge,
),
).height,
() =>
computeTextSize(Text('X', style: context.textTheme.bodyLarge)).height,
);
}
double get bodySmallHeight {
return _measureMap.updateCacheValue(
'bodySmallHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.bodySmall,
),
).height,
() =>
computeTextSize(Text('X', style: context.textTheme.bodySmall)).height,
);
}
@@ -71,10 +50,16 @@ class Measure {
return _measureMap.updateCacheValue(
'labelSmallHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.labelSmall,
),
Text('X', style: context.textTheme.labelSmall),
).height,
);
}
double get titleSmallHeight {
return _measureMap.updateCacheValue(
'titleSmallHeight',
() => computeTextSize(
Text('X', style: context.textTheme.titleSmall),
).height,
);
}
@@ -83,10 +68,7 @@ class Measure {
return _measureMap.updateCacheValue(
'labelMediumHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.labelMedium,
),
Text('X', style: context.textTheme.labelMedium),
).height,
);
}
@@ -95,10 +77,7 @@ class Measure {
return _measureMap.updateCacheValue(
'titleLargeHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.titleLarge,
),
Text('X', style: context.textTheme.titleLarge),
).height,
);
}
@@ -107,10 +86,7 @@ class Measure {
return _measureMap.updateCacheValue(
'titleMediumHeight',
() => computeTextSize(
Text(
'X',
style: context.textTheme.titleMedium,
),
Text('X', style: context.textTheme.titleMedium),
).height,
);
}

View File

@@ -20,6 +20,11 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
}
void onUpdate(T value) {}
void update(T Function(T) builder) {
final value = builder(state);
this.value = value;
}
}
mixin AnyNotifierMixin<T> on AnyNotifier<T, T> {

View File

@@ -1,3 +1,4 @@
import 'package:animations/animations.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -32,6 +33,11 @@ class BaseNavigator {
// }
}
const commonSharedXPageTransitions = SharedAxisPageTransitionsBuilder(
transitionType: SharedAxisTransitionType.horizontal,
fillColor: Colors.transparent,
);
class CommonDesktopRoute<T> extends PageRoute<T> {
final Widget Function(BuildContext context) builder;
@@ -67,14 +73,45 @@ class CommonDesktopRoute<T> extends PageRoute<T> {
Duration get reverseTransitionDuration => Duration(milliseconds: 200);
}
class CommonRoute<T> extends MaterialPageRoute<T> {
CommonRoute({required super.builder});
class CommonRoute<T> extends PageRoute<T> {
final Widget Function(BuildContext context) builder;
CommonRoute({required this.builder});
@override
Duration get transitionDuration => const Duration(milliseconds: 500);
Color? get barrierColor => null;
@override
Duration get reverseTransitionDuration => const Duration(milliseconds: 500);
String? get barrierLabel => null;
@override
bool get maintainState => true;
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final Widget result = builder(context);
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: SharedAxisTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
fillColor: context.colorScheme.surface,
child: result,
),
);
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
@override
Duration get reverseTransitionDuration => Duration(milliseconds: 300);
}
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
@@ -228,7 +265,7 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
DecorationTween(
begin: const _CommonEdgeShadowDecoration(),
end: _CommonEdgeShadowDecoration(<Color>[
widget.context.colorScheme.inverseSurface.withValues(alpha: 0.02),
Color(0x04000000),
Colors.transparent,
]),
),
@@ -279,7 +316,7 @@ class _CommonEdgeShadowPainter extends BoxPainter {
return;
}
final double shadowWidth = 1 * configuration.size!.width;
final double shadowWidth = 0.05 * configuration.size!.width;
final double shadowHeight = configuration.size!.height;
final double bandWidth = shadowWidth / (colors.length - 1);

View File

@@ -68,7 +68,7 @@ class AppPath {
Future<String> get configFilePath async {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'config.json');
return join(homeDirPath, 'config.yaml');
}
Future<String> get validateFilePath async {

View File

@@ -30,16 +30,15 @@ class Request {
);
}
Future<Response> getFileResponseForUrl(String url) async {
final response = await _clashDio.get(
Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
return await _clashDio.get<Uint8List>(
url,
options: Options(responseType: ResponseType.bytes),
);
return response;
}
Future<Response> getTextResponseForUrl(String url) async {
final response = await _clashDio.get(
Future<Response<String>> getTextResponseForUrl(String url) async {
final response = await _clashDio.get<String>(
url,
options: Options(responseType: ResponseType.plain),
);

View File

@@ -260,7 +260,7 @@ class Windows {
await Future.delayed(Duration(milliseconds: 300));
final retryStatus = await retry(
task: checkService,
retryIf: (status) => status == WindowsHelperServiceStatus.running,
retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: commonDuration,
);
return res && retryStatus == WindowsHelperServiceStatus.running;

18
lib/common/yaml.dart Normal file
View File

@@ -0,0 +1,18 @@
import 'package:yaml_writer/yaml_writer.dart';
class Yaml {
static Yaml? _instance;
Yaml._internal();
factory Yaml() {
_instance ??= Yaml._internal();
return _instance!;
}
String encode(Object? value) {
return YamlWriter().convert(value);
}
}
final yaml = Yaml();

View File

@@ -22,8 +22,6 @@ import 'common/common.dart';
import 'models/models.dart';
class AppController {
int? lastProfileModified;
final BuildContext context;
final WidgetRef _ref;
@@ -99,14 +97,9 @@ class AppController {
if (isStart) {
await globalState.appController.tryStartCore();
await globalState.handleStart([updateRunTime, updateTraffic]);
final currentLastModified = await _ref
.read(currentProfileProvider)
?.profileLastModified;
if (currentLastModified == null || lastProfileModified == null) {
addCheckIpNumDebounce();
return;
}
if (currentLastModified <= (lastProfileModified ?? 0)) {
final profileId = _ref.read(currentProfileIdProvider);
final setupState = globalState.getSetupState(profileId);
if (!setupState.needSetup(globalState.lastSetupState)) {
addCheckIpNumDebounce();
return;
}
@@ -313,9 +306,15 @@ class AppController {
}
final realTunEnable = _ref.read(realTunEnableProvider);
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
final message = await coreController.setupConfig(realPatchConfig);
lastProfileModified = await _ref.read(
currentProfileProvider.select((state) => state?.profileLastModified),
final currentProfile = _ref.read(currentProfileProvider);
final setupState = _ref.read(setupStateProvider(currentProfile?.id ?? ''));
globalState.lastSetupState = setupState;
if (system.isAndroid) {
globalState.lastVpnState = _ref.read(vpnStateProvider);
}
final message = await globalState.setupConfig(
setupState: setupState,
patchConfig: realPatchConfig,
);
if (message.isNotEmpty) {
throw message;
@@ -449,15 +448,15 @@ class AppController {
}
Future<void> handleExit() async {
Future.delayed(commonDuration, () {
Future.delayed(Duration(seconds: 3), () {
system.exit();
});
try {
await savePreferences();
await macOS?.updateDns(true);
await proxy?.stopProxy();
await coreController.shutdown();
await macOS?.updateDns(true);
await coreController.destroy();
commonPrint.log('exit');
} finally {
system.exit();
}
@@ -477,7 +476,7 @@ class AppController {
Future<void> checkUpdateResultHandle({
Map<String, dynamic>? data,
bool handleError = false,
bool isUser = false,
}) async {
if (data != null) {
final tagName = data['tag_name'];
@@ -496,12 +495,16 @@ class AppController {
],
),
confirmText: appLocalizations.goDownload,
cancelText: isUser ? null : appLocalizations.noLongerRemind,
);
if (res != true) {
return;
if (res == true) {
launchUrl(Uri.parse('https://github.com/$repository/releases/latest'));
} else if (!isUser && res == false) {
_ref
.read(appSettingProvider.notifier)
.update((state) => state.copyWith(autoCheckUpdate: false));
}
launchUrl(Uri.parse('https://github.com/$repository/releases/latest'));
} else if (handleError) {
} else if (isUser) {
globalState.showMessage(
title: appLocalizations.checkUpdate,
message: TextSpan(text: appLocalizations.checkUpdateError),
@@ -902,7 +905,7 @@ class AppController {
json.decode(utf8.decode(configFile.content)),
);
for (final profile in profiles) {
final filePath = join(homeDirPath, profile.name);
final filePath = join(homeDirPath, posix.normalize(profile.name));
final file = File(filePath);
await file.create(recursive: true);
await file.writeAsBytes(profile.content);
@@ -949,14 +952,27 @@ class AppController {
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
_ref.read(scriptStateProvider.notifier).value = config.scriptProps;
_ref.read(scriptsProvider.notifier).value = config.scripts;
_ref.read(rulesProvider.notifier).value = config.rules;
}
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {
if (currentProfile == null && profiles.isNotEmpty) {
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
}
}
void checkNeedSetup() {
if (!globalState.isStart) {
return;
}
final profileId = _ref.read(currentProfileIdProvider);
final setupState = globalState.getSetupState(profileId);
if (!setupState.needSetup(globalState.lastSetupState)) {
return;
}
setupClashConfigDebounce();
}
Future<T?> safeRun<T>(
FutureOr<T> Function() futureFunction, {
String? title,

View File

@@ -93,12 +93,11 @@ class CoreController {
return await _interface.updateConfig(updateParams);
}
Future<String> setupConfig(
ClashConfig clashConfig, {
Future<String> setupConfig({
required SetupParams params,
required SetupState setupState,
VoidCallback? preloadInvoke,
}) async {
await globalState.genConfigFile(clashConfig);
final params = await globalState.getSetupParams();
final res = _interface.setupConfig(params);
if (preloadInvoke != null) {
preloadInvoke();

View File

@@ -86,7 +86,15 @@ abstract class CoreHandlerInterface with CoreInterface {
dynamic data,
Duration? timeout,
}) async {
await completer.future;
try {
await completer.future.timeout(const Duration(seconds: 10));
} catch (e) {
commonPrint.log(
'Invoke pre ${method.name} timeout $e',
logLevel: LogLevel.error,
);
return null;
}
if (kDebugMode) {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
}

View File

@@ -112,6 +112,7 @@ class CoreService extends CoreHandlerInterface {
@override
destroy() async {
final server = await _serverCompleter.future;
await shutdown();
await server.close();
await _deleteSocketFile();
return true;

View File

@@ -368,6 +368,18 @@ enum RuleAction {
final String value;
const RuleAction(this.value);
static List<RuleAction> get addedRuleActions {
return RuleAction.values
.where(
(item) => ![
RuleAction.MATCH,
RuleAction.RULE_SET,
RuleAction.SUB_RULE,
].contains(item),
)
.toList();
}
}
extension RuleActionExt on RuleAction {
@@ -384,13 +396,20 @@ extension RuleActionExt on RuleAction {
enum OverrideRuleType { override, added }
enum RuleTarget { DIRECT, REJECT }
enum OverwriteType {
// none,
standard,
script,
// custom,
}
enum RuleTarget { DIRECT, REJECT, MATCH }
enum RecoveryStrategy { compatible, override }
enum CacheTag { logs, rules, requests, proxiesList }
enum Language { yaml, javaScript }
enum Language { yaml, javaScript, json }
enum ImportOption { file, url }

View File

@@ -0,0 +1 @@
export 'overwrite/overwrite.dart';

View File

@@ -0,0 +1 @@
export 'rule.dart';

View File

@@ -0,0 +1,328 @@
library;
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:flutter/material.dart';
class RuleItem extends StatelessWidget {
final bool isSelected;
final bool isEditing;
final Rule rule;
final void Function(String id) onSelected;
final void Function(Rule rule) onEdit;
const RuleItem({
super.key,
required this.isSelected,
required this.rule,
required this.onSelected,
required this.onEdit,
this.isEditing = false,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
type: CommonCardType.filled,
isSelected: isSelected,
onPressed: () {
if (isEditing) {
onSelected(rule.id);
return;
}
onEdit(rule);
},
onLongPress: () {
onSelected(rule.id);
},
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
trailing: SizedBox(
width: 24,
height: 24,
child: CommonCheckBox(
value: isSelected,
isCircle: true,
onChanged: (_) {
onSelected(rule.id);
},
),
),
title: Text(rule.value),
),
),
),
);
}
}
class RuleStatusItem extends StatelessWidget {
final bool status;
final Rule rule;
final void Function(bool) onChange;
const RuleStatusItem({
super.key,
required this.status,
required this.rule,
required this.onChange,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
type: CommonCardType.filled,
onPressed: () {
onChange(!status);
},
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
trailing: Switch(value: status, onChanged: onChange),
title: Text(rule.value),
),
),
),
);
}
}
class AddOrEditRuleDialog extends StatefulWidget {
final Rule? rule;
const AddOrEditRuleDialog({super.key, this.rule});
@override
State<AddOrEditRuleDialog> createState() => _AddOrEditRuleDialogState();
}
class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
late RuleAction _ruleAction;
final _ruleTargetController = TextEditingController();
final _contentController = TextEditingController();
bool _noResolve = false;
bool _src = false;
List<DropdownMenuEntry> _targetItems = [];
final _formKey = GlobalKey<FormState>();
@override
void initState() {
_initState();
super.initState();
}
void _initState() {
_targetItems = [
...RuleTarget.values.map(
(item) => DropdownMenuEntry(value: item.name, label: item.name),
),
];
if (widget.rule != null) {
final parsedRule = ParsedRule.parseString(widget.rule!.value);
_ruleAction = parsedRule.ruleAction;
_contentController.text = parsedRule.content ?? '';
_ruleTargetController.text = parsedRule.ruleTarget ?? '';
_noResolve = parsedRule.noResolve;
_src = parsedRule.src;
return;
}
_ruleAction = RuleAction.addedRuleActions.first;
if (_targetItems.isNotEmpty) {
_ruleTargetController.text = _targetItems.first.value;
}
}
@override
void didUpdateWidget(AddOrEditRuleDialog oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.rule != widget.rule) {
_initState();
}
}
void _handleSubmit() {
final res = _formKey.currentState?.validate();
if (res == false) {
return;
}
final parsedRule = ParsedRule(
ruleAction: _ruleAction,
content: _contentController.text,
ruleTarget: _ruleTargetController.text,
noResolve: _noResolve,
src: _src,
);
final rule = widget.rule != null
? widget.rule!.copyWith(value: parsedRule.value)
: Rule.value(parsedRule.value);
Navigator.of(context).pop(rule);
}
@override
Widget build(BuildContext context) {
return CommonDialog(
title: widget.rule != null
? appLocalizations.editRule
: appLocalizations.addRule,
actions: [
TextButton(
onPressed: _handleSubmit,
child: Text(appLocalizations.confirm),
),
],
child: DropdownMenuTheme(
data: DropdownMenuThemeData(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(),
labelStyle: context.textTheme.bodyLarge?.copyWith(
overflow: TextOverflow.ellipsis,
),
),
),
child: Form(
key: _formKey,
child: LayoutBuilder(
builder: (_, constraints) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilledButton.tonal(
onPressed: () async {
_ruleAction =
await globalState.showCommonDialog<RuleAction>(
filter: false,
child: OptionsDialog<RuleAction>(
title: appLocalizations.ruleName,
options: RuleAction.addedRuleActions,
textBuilder: (item) => item.value,
value: _ruleAction,
),
) ??
_ruleAction;
setState(() {});
},
child: Text(_ruleAction.value),
),
SizedBox(height: 24),
TextFormField(
controller: _contentController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.content,
),
validator: (_) {
if (_contentController.text.isEmpty) {
return appLocalizations.emptyTip(
appLocalizations.content,
);
}
return null;
},
),
SizedBox(height: 24),
FormField<String>(
validator: (_) {
if (_ruleTargetController.text.isEmpty) {
return appLocalizations.emptyTip(
appLocalizations.ruleTarget,
);
}
return null;
},
builder: (filed) {
return DropdownMenu(
controller: _ruleTargetController,
label: Text(appLocalizations.ruleTarget),
width: 200,
menuHeight: 250,
enableFilter: false,
enableSearch: false,
dropdownMenuEntries: _targetItems,
errorText: filed.errorText,
);
},
),
if (_ruleAction.hasParams) ...[
SizedBox(height: 20),
Wrap(
spacing: 8,
children: [
CommonCard(
radius: 8,
isSelected: _src,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Text(
appLocalizations.sourceIp,
style: context.textTheme.bodyMedium,
),
),
onPressed: () {
setState(() {
_src = !_src;
});
},
),
CommonCard(
radius: 8,
isSelected: _noResolve,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Text(
appLocalizations.noResolve,
style: context.textTheme.bodyMedium,
),
),
onPressed: () {
setState(() {
_noResolve = !_noResolve;
});
},
),
],
),
],
SizedBox(height: 20),
],
);
},
),
),
),
);
}
}

View File

@@ -20,27 +20,42 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'en';
static String m0(label) =>
"Are you sure you want to delete the selected ${label}?";
static String m0(count) =>
"${Intl.plural(count, one: '1 day ago', other: '${count} days ago')}";
static String m1(label) =>
"Are you sure you want to delete the selected ${label}?";
static String m2(label) =>
"Are you sure you want to delete the current ${label}?";
static String m2(label) => "${label} details";
static String m3(label) => "${label} details";
static String m3(label) => "${label} cannot be empty";
static String m4(label) => "${label} cannot be empty";
static String m4(label) => "Current ${label} already exists";
static String m5(label) => "Current ${label} already exists";
static String m5(label) => "No ${label} at the moment";
static String m6(count) =>
"${Intl.plural(count, one: '1 hour ago', other: '${count} hours ago')}";
static String m6(label) => "${label} must be a number";
static String m7(count) =>
"${Intl.plural(count, one: '1 minute ago', other: '${count} minutes ago')}";
static String m7(label) => "${label} must be between 1024 and 49151";
static String m8(count) =>
"${Intl.plural(count, one: '1 month ago', other: '${count} months ago')}";
static String m8(count) => "${count} items have been selected";
static String m9(label) => "No ${label} yet";
static String m9(label) => "${label} must be a url";
static String m10(label) => "${label} must be a number";
static String m11(label) => "${label} must be between 1024 and 49151";
static String m12(count) => "${count} items have been selected";
static String m13(label) => "${label} must be a url";
static String m14(count) =>
"${Intl.plural(count, one: '1 year ago', other: '${count} years ago')}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -55,6 +70,9 @@ class MessageLookup extends MessageLookupByLibrary {
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
"The selected application will be excluded from VPN",
),
"accessControlSettings": MessageLookupByLibrary.simpleMessage(
"Access Control Settings",
),
"account": MessageLookupByLibrary.simpleMessage("Account"),
"action": MessageLookupByLibrary.simpleMessage("Action"),
"action_mode": MessageLookupByLibrary.simpleMessage("Switch mode"),
@@ -67,6 +85,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Attach on the original rules",
),
"addedRules": MessageLookupByLibrary.simpleMessage("Added rules"),
"address": MessageLookupByLibrary.simpleMessage("Address"),
"addressHelp": MessageLookupByLibrary.simpleMessage(
"WebDAV server address",
@@ -80,6 +99,12 @@ class MessageLookup extends MessageLookupByLibrary {
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage(
"Boot up by using admin mode",
),
"advancedConfig": MessageLookupByLibrary.simpleMessage(
"Advanced configuration",
),
"advancedConfigDesc": MessageLookupByLibrary.simpleMessage(
"Provide diverse configuration options",
),
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
"agree": MessageLookupByLibrary.simpleMessage("Agree"),
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
@@ -183,6 +208,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Opening it will lose part of its application ability and gain the support of full amount of Clash.",
),
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to clear all data?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to force crash the core?",
),
"connected": MessageLookupByLibrary.simpleMessage("Connected"),
"connecting": MessageLookupByLibrary.simpleMessage("Connecting..."),
"connection": MessageLookupByLibrary.simpleMessage("Connection"),
@@ -194,6 +225,9 @@ class MessageLookup extends MessageLookupByLibrary {
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
"content": MessageLookupByLibrary.simpleMessage("Content"),
"contentScheme": MessageLookupByLibrary.simpleMessage("Content"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"Control global added rules",
),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
"Copying environment variables",
@@ -201,6 +235,9 @@ class MessageLookup extends MessageLookupByLibrary {
"copyLink": MessageLookupByLibrary.simpleMessage("Copy link"),
"copySuccess": MessageLookupByLibrary.simpleMessage("Copy success"),
"core": MessageLookupByLibrary.simpleMessage("Core"),
"coreConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"Core configuration change detected",
),
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
"coreStatus": MessageLookupByLibrary.simpleMessage("Core status"),
"country": MessageLookupByLibrary.simpleMessage("Country"),
@@ -221,6 +258,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Data Collection Notice",
),
"days": MessageLookupByLibrary.simpleMessage("Days"),
"daysAgo": m0,
"defaultNameserver": MessageLookupByLibrary.simpleMessage(
"Default nameserver",
),
@@ -232,8 +270,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteMultipTip": m0,
"deleteTip": m1,
"deleteMultipTip": m1,
"deleteTip": m2,
"desc": MessageLookupByLibrary.simpleMessage(
"A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
),
@@ -244,7 +282,7 @@ class MessageLookup extends MessageLookupByLibrary {
"destinationIPASN": MessageLookupByLibrary.simpleMessage(
"Destination IPASN",
),
"details": m2,
"details": m3,
"detectionTip": MessageLookupByLibrary.simpleMessage(
"Relying on third-party api is for reference only",
),
@@ -275,7 +313,11 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("Domain"),
"download": MessageLookupByLibrary.simpleMessage("Download"),
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
"emptyTip": m3,
"editGlobalRules": MessageLookupByLibrary.simpleMessage(
"Edit global rules",
),
"editRule": MessageLookupByLibrary.simpleMessage("Edit rule"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("English"),
"enableOverride": MessageLookupByLibrary.simpleMessage("Enable override"),
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
@@ -283,7 +325,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"When the app is in the background, the app is hidden from the recent task",
),
"existsTip": m4,
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Expiration time"),
@@ -297,6 +339,7 @@ class MessageLookup extends MessageLookupByLibrary {
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"Once enabled, the Clash kernel can be controlled on port 9090",
),
"externalFetch": MessageLookupByLibrary.simpleMessage("External fetch"),
"externalLink": MessageLookupByLibrary.simpleMessage("External link"),
"externalResources": MessageLookupByLibrary.simpleMessage(
"External resources",
@@ -345,6 +388,9 @@ class MessageLookup extends MessageLookupByLibrary {
"global": MessageLookupByLibrary.simpleMessage("Global"),
"go": MessageLookupByLibrary.simpleMessage("Go"),
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage(
"Go to configure script",
),
"hasCacheChange": MessageLookupByLibrary.simpleMessage(
"Do you want to cache the changes?",
),
@@ -358,6 +404,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Use keyboard to control applications",
),
"hours": MessageLookupByLibrary.simpleMessage("Hours"),
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("Icon"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage(
"Icon configuration",
@@ -387,6 +434,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"ja": MessageLookupByLibrary.simpleMessage("Japanese"),
"just": MessageLookupByLibrary.simpleMessage("Just"),
"justNow": MessageLookupByLibrary.simpleMessage("Just now"),
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
"Tcp keep alive interval",
),
@@ -396,6 +444,7 @@ class MessageLookup extends MessageLookupByLibrary {
"light": MessageLookupByLibrary.simpleMessage("Light"),
"list": MessageLookupByLibrary.simpleMessage("List"),
"listen": MessageLookupByLibrary.simpleMessage("Listen"),
"loadTest": MessageLookupByLibrary.simpleMessage("Load test"),
"local": MessageLookupByLibrary.simpleMessage("Local"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local",
@@ -428,10 +477,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify the default system exit event",
),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"minutesAgo": m7,
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
"months": MessageLookupByLibrary.simpleMessage("Months"),
"monthsAgo": m8,
"more": MessageLookupByLibrary.simpleMessage("More"),
"name": MessageLookupByLibrary.simpleMessage("Name"),
"nameSort": MessageLookupByLibrary.simpleMessage("Sort by name"),
@@ -459,6 +510,9 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("No HotKey"),
"noIcon": MessageLookupByLibrary.simpleMessage("None"),
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noLongerRemind": MessageLookupByLibrary.simpleMessage(
"Don\'t remind again",
),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network APP"),
@@ -474,8 +528,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile",
),
"nullTip": m5,
"numberTip": m6,
"nullTip": m9,
"numberTip": m10,
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -504,9 +558,11 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"Does not take effect in script mode",
),
"overrideMode": MessageLookupByLibrary.simpleMessage("Override mode"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Override the original rule",
),
"overrideScript": MessageLookupByLibrary.simpleMessage("Override script"),
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"),
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
@@ -529,7 +585,7 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Please enter a different port",
),
"portTip": m7,
"portTip": m11,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Prioritize the use of DOH\'s http/3",
),
@@ -603,6 +659,7 @@ class MessageLookup extends MessageLookupByLibrary {
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
"reload": MessageLookupByLibrary.simpleMessage("Reload"),
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to WebDAV",
@@ -630,6 +687,7 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS connection following rules, need to configure proxy-server-nameserver",
),
"restart": MessageLookupByLibrary.simpleMessage("Restart"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to restart the core?",
),
@@ -655,11 +713,14 @@ class MessageLookup extends MessageLookupByLibrary {
"Are you sure you want to save?",
),
"script": MessageLookupByLibrary.simpleMessage("Script"),
"scriptModeDesc": MessageLookupByLibrary.simpleMessage(
"Script mode, use external extension scripts, provide one-click override configuration capability",
),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
"selectedCountTitle": m8,
"selectedCountTitle": m12,
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
@@ -676,6 +737,9 @@ class MessageLookup extends MessageLookupByLibrary {
"specialRules": MessageLookupByLibrary.simpleMessage("special rules"),
"stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
"Standard mode, override basic configuration, provide simple rule addition capability",
),
"start": MessageLookupByLibrary.simpleMessage("Start"),
"startVpn": MessageLookupByLibrary.simpleMessage("Starting VPN..."),
"status": MessageLookupByLibrary.simpleMessage("Status"),
@@ -725,6 +789,8 @@ class MessageLookup extends MessageLookupByLibrary {
"tunDesc": MessageLookupByLibrary.simpleMessage(
"only effective in administrator mode",
),
"turnOff": MessageLookupByLibrary.simpleMessage("Turn Off"),
"turnOn": MessageLookupByLibrary.simpleMessage("Turn On"),
"twoColumns": MessageLookupByLibrary.simpleMessage("Two columns"),
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
"unable to update current profile",
@@ -742,12 +808,15 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Obtain profile through URL",
),
"urlTip": m9,
"urlTip": m13,
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
"value": MessageLookupByLibrary.simpleMessage("Value"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Vibrant"),
"view": MessageLookupByLibrary.simpleMessage("View"),
"vpnConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"VPN configuration change detected",
),
"vpnDesc": MessageLookupByLibrary.simpleMessage(
"Modify VPN related settings",
),
@@ -765,6 +834,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"whitelistMode": MessageLookupByLibrary.simpleMessage("Whitelist mode"),
"years": MessageLookupByLibrary.simpleMessage("Years"),
"yearsAgo": m14,
"zh_CN": MessageLookupByLibrary.simpleMessage("Simplified Chinese"),
};
}

View File

@@ -20,25 +20,35 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'ja';
static String m0(label) => "選択された${label}を削除してもよろしいですか?";
static String m0(count) => "${count}日前";
static String m1(label) => "現在の${label}を削除してもよろしいですか?";
static String m1(label) => "選択された${label}を削除してもよろしいですか?";
static String m2(label) => "${label}詳細";
static String m2(label) => "現在の${label}を削除してもよろしいですか?";
static String m3(label) => "${label}は空欄にできません";
static String m3(label) => "${label}詳細";
static String m4(label) => "現在の${label}既に存在しています";
static String m4(label) => "${label}空欄にできません";
static String m5(label) => "現在${label}ありません";
static String m5(label) => "現在${label}既に存在しています";
static String m6(label) => "${label}は数字でなければなりません";
static String m6(count) => "${count}時間前";
static String m7(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m7(count) => "${count}分前";
static String m8(count) => "${count} 項目が選択されています";
static String m8(count) => "${count}ヶ月前";
static String m9(label) => "${label}URLである必要がありま";
static String m9(label) => "まだ${label}はありません";
static String m10(label) => "${label}は数字でなければなりません";
static String m11(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m12(count) => "${count} 項目が選択されています";
static String m13(label) => "${label}はURLである必要があります";
static String m14(count) => "${count}年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -53,6 +63,7 @@ class MessageLookup extends MessageLookupByLibrary {
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
"選択したアプリをVPNから除外",
),
"accessControlSettings": MessageLookupByLibrary.simpleMessage("アクセス制御設定"),
"account": MessageLookupByLibrary.simpleMessage("アカウント"),
"action": MessageLookupByLibrary.simpleMessage("アクション"),
"action_mode": MessageLookupByLibrary.simpleMessage("モード切替"),
@@ -63,11 +74,14 @@ class MessageLookup extends MessageLookupByLibrary {
"add": MessageLookupByLibrary.simpleMessage("追加"),
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
"addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"),
"address": MessageLookupByLibrary.simpleMessage("アドレス"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAVサーバーアドレス"),
"addressTip": MessageLookupByLibrary.simpleMessage("有効なWebDAVアドレスを入力"),
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理者自動起動"),
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage("管理者モードで起動"),
"advancedConfig": MessageLookupByLibrary.simpleMessage("高度な設定"),
"advancedConfigDesc": MessageLookupByLibrary.simpleMessage("多様な設定を提供"),
"ago": MessageLookupByLibrary.simpleMessage(""),
"agree": MessageLookupByLibrary.simpleMessage("同意"),
"allApps": MessageLookupByLibrary.simpleMessage("全アプリ"),
@@ -137,6 +151,12 @@ class MessageLookup extends MessageLookupByLibrary {
"有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
),
"confirm": MessageLookupByLibrary.simpleMessage("確認"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"すべてのデータをクリアしてもよろしいですか?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"コアを強制的にクラッシュさせてもよろしいですか?",
),
"connected": MessageLookupByLibrary.simpleMessage("接続済み"),
"connecting": MessageLookupByLibrary.simpleMessage("接続中..."),
"connection": MessageLookupByLibrary.simpleMessage("接続"),
@@ -146,11 +166,17 @@ class MessageLookup extends MessageLookupByLibrary {
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"グローバル追加ルールを制御",
),
"copy": MessageLookupByLibrary.simpleMessage("コピー"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"),
"copyLink": MessageLookupByLibrary.simpleMessage("リンクをコピー"),
"copySuccess": MessageLookupByLibrary.simpleMessage("コピー成功"),
"core": MessageLookupByLibrary.simpleMessage("コア"),
"coreConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"コア設定の変更が検出されました",
),
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
"coreStatus": MessageLookupByLibrary.simpleMessage("コアステータス"),
"country": MessageLookupByLibrary.simpleMessage(""),
@@ -169,6 +195,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"dataCollectionTip": MessageLookupByLibrary.simpleMessage("データ収集説明"),
"days": MessageLookupByLibrary.simpleMessage(""),
"daysAgo": m0,
"defaultNameserver": MessageLookupByLibrary.simpleMessage("デフォルトネームサーバー"),
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage(
"DNSサーバーの解決用",
@@ -178,15 +205,15 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteMultipTip": m0,
"deleteTip": m1,
"deleteMultipTip": m1,
"deleteTip": m2,
"desc": MessageLookupByLibrary.simpleMessage(
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
),
"destination": MessageLookupByLibrary.simpleMessage("宛先"),
"destinationGeoIP": MessageLookupByLibrary.simpleMessage("宛先地理情報"),
"destinationIPASN": MessageLookupByLibrary.simpleMessage("宛先IP ASN"),
"details": m2,
"details": m3,
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存参考値"),
"developerMode": MessageLookupByLibrary.simpleMessage("デベロッパーモード"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
@@ -207,7 +234,9 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("ドメイン"),
"download": MessageLookupByLibrary.simpleMessage("ダウンロード"),
"edit": MessageLookupByLibrary.simpleMessage("編集"),
"emptyTip": m3,
"editGlobalRules": MessageLookupByLibrary.simpleMessage("グローバルルールを編集"),
"editRule": MessageLookupByLibrary.simpleMessage("ルールを編集"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英語"),
"enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"),
"entries": MessageLookupByLibrary.simpleMessage(" エントリ"),
@@ -215,7 +244,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"アプリがバックグラウンド時に最近のタスクから非表示",
),
"existsTip": m4,
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("終了"),
"expand": MessageLookupByLibrary.simpleMessage("標準"),
"expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"),
@@ -227,6 +256,7 @@ class MessageLookup extends MessageLookupByLibrary {
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"有効化するとClashコアをポート9090で制御可能",
),
"externalFetch": MessageLookupByLibrary.simpleMessage("外部取得"),
"externalLink": MessageLookupByLibrary.simpleMessage("外部リンク"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部リソース"),
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeipフィルター"),
@@ -263,6 +293,7 @@ class MessageLookup extends MessageLookupByLibrary {
"global": MessageLookupByLibrary.simpleMessage("グローバル"),
"go": MessageLookupByLibrary.simpleMessage("移動"),
"goDownload": MessageLookupByLibrary.simpleMessage("ダウンロードへ"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage("スクリプト設定に移動"),
"hasCacheChange": MessageLookupByLibrary.simpleMessage("変更をキャッシュしますか?"),
"host": MessageLookupByLibrary.simpleMessage("ホスト"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("ホストを追加"),
@@ -272,6 +303,7 @@ class MessageLookup extends MessageLookupByLibrary {
"キーボードでアプリを制御",
),
"hours": MessageLookupByLibrary.simpleMessage("時間"),
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
@@ -291,6 +323,7 @@ class MessageLookup extends MessageLookupByLibrary {
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
"ja": MessageLookupByLibrary.simpleMessage("日本語"),
"just": MessageLookupByLibrary.simpleMessage("たった今"),
"justNow": MessageLookupByLibrary.simpleMessage("たった今"),
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
"TCPキープアライブ間隔",
),
@@ -300,6 +333,7 @@ class MessageLookup extends MessageLookupByLibrary {
"light": MessageLookupByLibrary.simpleMessage("ライト"),
"list": MessageLookupByLibrary.simpleMessage("リスト"),
"listen": MessageLookupByLibrary.simpleMessage("リスン"),
"loadTest": MessageLookupByLibrary.simpleMessage("読み込みテスト"),
"local": MessageLookupByLibrary.simpleMessage("ローカル"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),
@@ -322,10 +356,12 @@ class MessageLookup extends MessageLookupByLibrary {
"システムの終了イベントを変更",
),
"minutes": MessageLookupByLibrary.simpleMessage(""),
"minutesAgo": m7,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
"mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
"months": MessageLookupByLibrary.simpleMessage(""),
"monthsAgo": m8,
"more": MessageLookupByLibrary.simpleMessage("詳細"),
"name": MessageLookupByLibrary.simpleMessage("名前"),
"nameSort": MessageLookupByLibrary.simpleMessage("名前順"),
@@ -345,6 +381,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("ホットキーなし"),
"noIcon": MessageLookupByLibrary.simpleMessage("なし"),
"noInfo": MessageLookupByLibrary.simpleMessage("情報なし"),
"noLongerRemind": MessageLookupByLibrary.simpleMessage("今後表示しない"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("追加情報なし"),
"noNetwork": MessageLookupByLibrary.simpleMessage("ネットワークなし"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("ネットワークなしアプリ"),
@@ -360,8 +397,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルがありません。追加してください",
),
"nullTip": m5,
"numberTip": m6,
"nullTip": m9,
"numberTip": m10,
"oneColumn": MessageLookupByLibrary.simpleMessage("1列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"),
@@ -382,7 +419,9 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"スクリプトモードでは有効になりません",
),
"overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
"overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"),
"palette": MessageLookupByLibrary.simpleMessage("パレット"),
"password": MessageLookupByLibrary.simpleMessage("パスワード"),
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
@@ -403,7 +442,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("ポート"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
"portTip": m7,
"portTip": m11,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
@@ -459,6 +498,7 @@ class MessageLookup extends MessageLookupByLibrary {
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
"reload": MessageLookupByLibrary.simpleMessage("リロード"),
"remote": MessageLookupByLibrary.simpleMessage("リモート"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVにデータをバックアップ",
@@ -480,6 +520,7 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS接続がルールに従うproxy-server-nameserverの設定が必要",
),
"restart": MessageLookupByLibrary.simpleMessage("再起動"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"),
"routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"),
@@ -497,11 +538,14 @@ class MessageLookup extends MessageLookupByLibrary {
"saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"),
"saveTip": MessageLookupByLibrary.simpleMessage("保存してもよろしいですか?"),
"script": MessageLookupByLibrary.simpleMessage("スクリプト"),
"scriptModeDesc": MessageLookupByLibrary.simpleMessage(
"スクリプトモード、外部拡張スクリプトを使用し、ワンクリックで設定を上書きする機能を提供",
),
"search": MessageLookupByLibrary.simpleMessage("検索"),
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
"selectedCountTitle": m8,
"selectedCountTitle": m12,
"settings": MessageLookupByLibrary.simpleMessage("設定"),
"show": MessageLookupByLibrary.simpleMessage("表示"),
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
@@ -516,6 +560,9 @@ class MessageLookup extends MessageLookupByLibrary {
"specialRules": MessageLookupByLibrary.simpleMessage("特殊ルール"),
"stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"),
"standard": MessageLookupByLibrary.simpleMessage("標準"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
"標準モード、基本設定を上書きし、シンプルなルール追加機能を提供",
),
"start": MessageLookupByLibrary.simpleMessage("開始"),
"startVpn": MessageLookupByLibrary.simpleMessage("VPNを開始中..."),
"status": MessageLookupByLibrary.simpleMessage("ステータス"),
@@ -555,6 +602,8 @@ class MessageLookup extends MessageLookupByLibrary {
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage("管理者モードでのみ有効"),
"turnOff": MessageLookupByLibrary.simpleMessage("オフ"),
"turnOn": MessageLookupByLibrary.simpleMessage("オン"),
"twoColumns": MessageLookupByLibrary.simpleMessage("2列"),
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
"現在のプロファイルを更新できません",
@@ -570,12 +619,15 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
"urlTip": m9,
"urlTip": m13,
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
"value": MessageLookupByLibrary.simpleMessage(""),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("ビブラント"),
"view": MessageLookupByLibrary.simpleMessage("表示"),
"vpnConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"VPN設定の変更が検出されました",
),
"vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"),
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
"VpnService経由で全システムトラフィックをルーティング",
@@ -587,6 +639,7 @@ class MessageLookup extends MessageLookupByLibrary {
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV設定"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("ホワイトリストモード"),
"years": MessageLookupByLibrary.simpleMessage(""),
"yearsAgo": m14,
"zh_CN": MessageLookupByLibrary.simpleMessage("簡体字中国語"),
};
}

View File

@@ -20,26 +20,41 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'ru';
static String m0(label) =>
static String m0(count) =>
"${Intl.plural(count, one: '${count} день назад', few: '${count} дня назад', many: '${count} дней назад', other: '${count} дня назад')}";
static String m1(label) =>
"Вы уверены, что хотите удалить выбранные ${label}?";
static String m1(label) => "Вы уверены, что хотите удалить текущий ${label}?";
static String m2(label) => "Вы уверены, что хотите удалить текущий ${label}?";
static String m2(label) => "Детали {}";
static String m3(label) => "Детали {}";
static String m3(label) => "${label} не может быть пустым";
static String m4(label) => "${label} не может быть пустым";
static String m4(label) => "Текущий ${label} уже существует";
static String m5(label) => "Текущий ${label} уже существует";
static String m5(label) => "Сейчас ${label} нет";
static String m6(count) =>
"${Intl.plural(count, one: '${count} час назад', few: '${count} часа назад', many: '${count} часов назад', other: '${count} часа назад')}";
static String m6(label) => "${label} должно быть числом";
static String m7(count) =>
"${Intl.plural(count, one: '${count} минута назад', few: '${count} минуты назад', many: '${count} минут назад', other: '${count} минуты назад')}";
static String m7(label) => "${label} должен быть числом от 1024 до 49151";
static String m8(count) =>
"${Intl.plural(count, one: '${count} месяц назад', few: '${count} месяца назад', many: '${count} месяцев назад', other: '${count} месяца назад')}";
static String m8(count) => "Выбрано ${count} элементов";
static String m9(label) => "${label} пока отсутствуют";
static String m9(label) => "${label} должен быть URL";
static String m10(label) => "${label} должно быть числом";
static String m11(label) => "${label} должен быть числом от 1024 до 49151";
static String m12(count) => "Выбрано ${count} элементов";
static String m13(label) => "${label} должен быть URL";
static String m14(count) =>
"${Intl.plural(count, one: '${count} год назад', few: '${count} года назад', many: '${count} лет назад', other: '${count} года назад')}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -54,6 +69,9 @@ class MessageLookup extends MessageLookupByLibrary {
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
"Выбранные приложения будут исключены из VPN",
),
"accessControlSettings": MessageLookupByLibrary.simpleMessage(
"Настройки контроля доступа",
),
"account": MessageLookupByLibrary.simpleMessage("Аккаунт"),
"action": MessageLookupByLibrary.simpleMessage("Действие"),
"action_mode": MessageLookupByLibrary.simpleMessage("Переключить режим"),
@@ -66,6 +84,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Добавить к оригинальным правилам",
),
"addedRules": MessageLookupByLibrary.simpleMessage("Добавленные правила"),
"address": MessageLookupByLibrary.simpleMessage("Адрес"),
"addressHelp": MessageLookupByLibrary.simpleMessage("Адрес сервера WebDAV"),
"addressTip": MessageLookupByLibrary.simpleMessage(
@@ -77,6 +96,12 @@ class MessageLookup extends MessageLookupByLibrary {
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage(
"Запуск с правами администратора при загрузке системы",
),
"advancedConfig": MessageLookupByLibrary.simpleMessage(
"Расширенная конфигурация",
),
"advancedConfigDesc": MessageLookupByLibrary.simpleMessage(
"Предоставляет разнообразные варианты конфигурации",
),
"ago": MessageLookupByLibrary.simpleMessage(" назад"),
"agree": MessageLookupByLibrary.simpleMessage("Согласен"),
"allApps": MessageLookupByLibrary.simpleMessage("Все приложения"),
@@ -188,6 +213,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Включение приведет к потере части функциональности приложения, но обеспечит полную поддержку Clash.",
),
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите очистить все данные?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
),
"connected": MessageLookupByLibrary.simpleMessage("Подключено"),
"connecting": MessageLookupByLibrary.simpleMessage("Подключение..."),
"connection": MessageLookupByLibrary.simpleMessage("Соединение"),
@@ -199,6 +230,9 @@ class MessageLookup extends MessageLookupByLibrary {
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
"contentScheme": MessageLookupByLibrary.simpleMessage("Контентная тема"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"Управление глобальными добавленными правилами",
),
"copy": MessageLookupByLibrary.simpleMessage("Копировать"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
"Копирование переменных окружения",
@@ -206,6 +240,9 @@ class MessageLookup extends MessageLookupByLibrary {
"copyLink": MessageLookupByLibrary.simpleMessage("Копировать ссылку"),
"copySuccess": MessageLookupByLibrary.simpleMessage("Копирование успешно"),
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
"coreConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"Обнаружено изменение конфигурации ядра",
),
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
"coreStatus": MessageLookupByLibrary.simpleMessage("Основной статус"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
@@ -226,6 +263,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Уведомление о сборе данных",
),
"days": MessageLookupByLibrary.simpleMessage("Дней"),
"daysAgo": m0,
"defaultNameserver": MessageLookupByLibrary.simpleMessage(
"Сервер имен по умолчанию",
),
@@ -239,8 +277,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteMultipTip": m0,
"deleteTip": m1,
"deleteMultipTip": m1,
"deleteTip": m2,
"desc": MessageLookupByLibrary.simpleMessage(
"Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.",
),
@@ -249,7 +287,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Геолокация назначения",
),
"destinationIPASN": MessageLookupByLibrary.simpleMessage("ASN назначения"),
"details": m2,
"details": m3,
"detectionTip": MessageLookupByLibrary.simpleMessage(
"Опирается на сторонний API, только для справки",
),
@@ -282,7 +320,11 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("Домен"),
"download": MessageLookupByLibrary.simpleMessage("Скачивание"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"emptyTip": m3,
"editGlobalRules": MessageLookupByLibrary.simpleMessage(
"Редактировать глобальные правила",
),
"editRule": MessageLookupByLibrary.simpleMessage("Редактировать правило"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("Английский"),
"enableOverride": MessageLookupByLibrary.simpleMessage(
"Включить переопределение",
@@ -294,7 +336,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
),
"existsTip": m4,
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"),
@@ -308,6 +350,7 @@ class MessageLookup extends MessageLookupByLibrary {
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"При включении ядро Clash можно контролировать на порту 9090",
),
"externalFetch": MessageLookupByLibrary.simpleMessage("Внешнее получение"),
"externalLink": MessageLookupByLibrary.simpleMessage("Внешняя ссылка"),
"externalResources": MessageLookupByLibrary.simpleMessage(
"Внешние ресурсы",
@@ -360,6 +403,9 @@ class MessageLookup extends MessageLookupByLibrary {
"global": MessageLookupByLibrary.simpleMessage("Глобальный"),
"go": MessageLookupByLibrary.simpleMessage("Перейти"),
"goDownload": MessageLookupByLibrary.simpleMessage("Перейти к загрузке"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage(
"Перейти к настройке скрипта",
),
"hasCacheChange": MessageLookupByLibrary.simpleMessage(
"Хотите сохранить изменения в кэше?",
),
@@ -375,6 +421,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Использование клавиатуры для управления приложением",
),
"hours": MessageLookupByLibrary.simpleMessage("Часов"),
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("Иконка"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage(
"Конфигурация иконки",
@@ -406,6 +453,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"ja": MessageLookupByLibrary.simpleMessage("Японский"),
"just": MessageLookupByLibrary.simpleMessage("Только что"),
"justNow": MessageLookupByLibrary.simpleMessage("Только что"),
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
"Интервал поддержания TCP-соединения",
),
@@ -415,6 +463,7 @@ class MessageLookup extends MessageLookupByLibrary {
"light": MessageLookupByLibrary.simpleMessage("Светлый"),
"list": MessageLookupByLibrary.simpleMessage("Список"),
"listen": MessageLookupByLibrary.simpleMessage("Слушать"),
"loadTest": MessageLookupByLibrary.simpleMessage("Тест загрузки"),
"local": MessageLookupByLibrary.simpleMessage("Локальный"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на локальный диск",
@@ -451,10 +500,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Изменить стандартное событие выхода из системы",
),
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
"minutesAgo": m7,
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
"monthsAgo": m8,
"more": MessageLookupByLibrary.simpleMessage("Еще"),
"name": MessageLookupByLibrary.simpleMessage("Имя"),
"nameSort": MessageLookupByLibrary.simpleMessage("Сортировка по имени"),
@@ -482,6 +533,9 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("Нет горячей клавиши"),
"noIcon": MessageLookupByLibrary.simpleMessage("Нет иконки"),
"noInfo": MessageLookupByLibrary.simpleMessage("Нет информации"),
"noLongerRemind": MessageLookupByLibrary.simpleMessage(
"Больше не напоминать",
),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage(
"Нет дополнительной информации",
),
@@ -499,8 +553,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"Нет профиля, пожалуйста, добавьте профиль",
),
"nullTip": m5,
"numberTip": m6,
"nullTip": m9,
"numberTip": m10,
"oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -531,9 +585,15 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"В скриптовом режиме не действует",
),
"overrideMode": MessageLookupByLibrary.simpleMessage(
"Режим переопределения",
),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Переопределить оригинальное правило",
),
"overrideScript": MessageLookupByLibrary.simpleMessage(
"Скрипт переопределения",
),
"palette": MessageLookupByLibrary.simpleMessage("Палитра"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"paste": MessageLookupByLibrary.simpleMessage("Вставить"),
@@ -556,7 +616,7 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Введите другой порт",
),
"portTip": m7,
"portTip": m11,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Приоритетное использование HTTP/3 для DOH",
),
@@ -636,6 +696,7 @@ class MessageLookup extends MessageLookupByLibrary {
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
"reload": MessageLookupByLibrary.simpleMessage("Перезагрузить"),
"remote": MessageLookupByLibrary.simpleMessage("Удаленный"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на WebDAV",
@@ -665,6 +726,7 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS-соединение следует правилам, необходимо настроить proxy-server-nameserver",
),
"restart": MessageLookupByLibrary.simpleMessage("Перезапустить"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите перезапустить ядро?",
),
@@ -690,11 +752,14 @@ class MessageLookup extends MessageLookupByLibrary {
"Вы уверены, что хотите сохранить?",
),
"script": MessageLookupByLibrary.simpleMessage("Скрипт"),
"scriptModeDesc": MessageLookupByLibrary.simpleMessage(
"Режим скрипта, использование внешних расширяющих скриптов, предоставление возможности переопределения конфигурации одним кликом",
),
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
"selectedCountTitle": m8,
"selectedCountTitle": m12,
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"show": MessageLookupByLibrary.simpleMessage("Показать"),
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
@@ -711,6 +776,9 @@ class MessageLookup extends MessageLookupByLibrary {
"specialRules": MessageLookupByLibrary.simpleMessage("Специальные правила"),
"stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"),
"standard": MessageLookupByLibrary.simpleMessage("Стандартный"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
"Стандартный режим, переопределение базовой конфигурации, предоставление возможности простого добавления правил",
),
"start": MessageLookupByLibrary.simpleMessage("Старт"),
"startVpn": MessageLookupByLibrary.simpleMessage("Запуск VPN..."),
"status": MessageLookupByLibrary.simpleMessage("Статус"),
@@ -762,6 +830,8 @@ class MessageLookup extends MessageLookupByLibrary {
"tunDesc": MessageLookupByLibrary.simpleMessage(
"действительно только в режиме администратора",
),
"turnOff": MessageLookupByLibrary.simpleMessage("Выключить"),
"turnOn": MessageLookupByLibrary.simpleMessage("Включить"),
"twoColumns": MessageLookupByLibrary.simpleMessage("Два столбца"),
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
"невозможно обновить текущий профиль",
@@ -781,7 +851,7 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Получить профиль через URL",
),
"urlTip": m9,
"urlTip": m13,
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
"Использовать системные hosts",
@@ -789,6 +859,9 @@ class MessageLookup extends MessageLookupByLibrary {
"value": MessageLookupByLibrary.simpleMessage("Значение"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Яркие"),
"view": MessageLookupByLibrary.simpleMessage("Просмотр"),
"vpnConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"Обнаружено изменение конфигурации VPN",
),
"vpnDesc": MessageLookupByLibrary.simpleMessage(
"Изменение настроек, связанных с VPN",
),
@@ -808,6 +881,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Режим белого списка",
),
"years": MessageLookupByLibrary.simpleMessage("Лет"),
"yearsAgo": m14,
"zh_CN": MessageLookupByLibrary.simpleMessage("Упрощенный китайский"),
};
}

View File

@@ -20,25 +20,35 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'zh_CN';
static String m0(label) => "确定删除选中的${label}吗?";
static String m0(count) => "${count} 天前";
static String m1(label) => "确定删除当前${label}吗?";
static String m1(label) => "确定删除选中的${label}吗?";
static String m2(label) => "${label}详情";
static String m2(label) => "确定删除当前${label}吗?";
static String m3(label) => "${label}不能为空";
static String m3(label) => "${label}详情";
static String m4(label) => "${label}当前已存在";
static String m4(label) => "${label}不能为空";
static String m5(label) => "暂无${label}";
static String m5(label) => "${label}当前已存在";
static String m6(label) => "${label}必须为数字";
static String m6(count) => "${count} 小时前";
static String m7(label) => "${label} 必须在 1024 到 49151 之间";
static String m7(count) => "${count} 分钟前";
static String m8(count) => "已选择 ${count} ";
static String m8(count) => "${count} 个月前";
static String m9(label) => "${label}必须为URL";
static String m9(label) => "暂无${label}";
static String m10(label) => "${label}必须为数字";
static String m11(label) => "${label} 必须在 1024 到 49151 之间";
static String m12(count) => "已选择 ${count}";
static String m13(label) => "${label}必须为URL";
static String m14(count) => "${count} 年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -51,6 +61,7 @@ class MessageLookup extends MessageLookupByLibrary {
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
"选中应用将会被排除在VPN之外",
),
"accessControlSettings": MessageLookupByLibrary.simpleMessage("访问控制设置"),
"account": MessageLookupByLibrary.simpleMessage("账号"),
"action": MessageLookupByLibrary.simpleMessage("操作"),
"action_mode": MessageLookupByLibrary.simpleMessage("切换模式"),
@@ -61,11 +72,14 @@ class MessageLookup extends MessageLookupByLibrary {
"add": MessageLookupByLibrary.simpleMessage("添加"),
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
"addedRules": MessageLookupByLibrary.simpleMessage("附加规则"),
"address": MessageLookupByLibrary.simpleMessage("地址"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理员自启动"),
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage("使用管理员模式开机自启动"),
"advancedConfig": MessageLookupByLibrary.simpleMessage("进阶配置"),
"advancedConfigDesc": MessageLookupByLibrary.simpleMessage("提供多样化配置"),
"ago": MessageLookupByLibrary.simpleMessage(""),
"agree": MessageLookupByLibrary.simpleMessage("同意"),
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
@@ -125,6 +139,8 @@ class MessageLookup extends MessageLookupByLibrary {
"开启将失去部分应用能力获得全量的Clash的支持",
),
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage("确定要清除所有数据?"),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage("确定要强制崩溃核心?"),
"connected": MessageLookupByLibrary.simpleMessage("已连接"),
"connecting": MessageLookupByLibrary.simpleMessage("连接中..."),
"connection": MessageLookupByLibrary.simpleMessage("连接"),
@@ -134,11 +150,15 @@ class MessageLookup extends MessageLookupByLibrary {
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage("控制全局附加规则"),
"copy": MessageLookupByLibrary.simpleMessage("复制"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
"copyLink": MessageLookupByLibrary.simpleMessage("复制链接"),
"copySuccess": MessageLookupByLibrary.simpleMessage("复制成功"),
"core": MessageLookupByLibrary.simpleMessage("内核"),
"coreConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"检测到核心配置更改",
),
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
"coreStatus": MessageLookupByLibrary.simpleMessage("核心状态"),
"country": MessageLookupByLibrary.simpleMessage("区域"),
@@ -157,6 +177,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"dataCollectionTip": MessageLookupByLibrary.simpleMessage("数据收集说明"),
"days": MessageLookupByLibrary.simpleMessage(""),
"daysAgo": m0,
"defaultNameserver": MessageLookupByLibrary.simpleMessage("默认域名服务器"),
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析DNS服务器"),
"defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"),
@@ -164,15 +185,15 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteMultipTip": m0,
"deleteTip": m1,
"deleteMultipTip": m1,
"deleteTip": m2,
"desc": MessageLookupByLibrary.simpleMessage(
"基于ClashMeta的多平台代理客户端简单易用开源无广告。",
),
"destination": MessageLookupByLibrary.simpleMessage("目标地址"),
"destinationGeoIP": MessageLookupByLibrary.simpleMessage("目标地理定位"),
"destinationIPASN": MessageLookupByLibrary.simpleMessage("目标IP ASN"),
"details": m2,
"details": m3,
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api仅供参考"),
"developerMode": MessageLookupByLibrary.simpleMessage("开发者模式"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage("开发者模式已启用。"),
@@ -191,13 +212,15 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("域名"),
"download": MessageLookupByLibrary.simpleMessage("下载"),
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
"emptyTip": m3,
"editGlobalRules": MessageLookupByLibrary.simpleMessage("编辑全局规则"),
"editRule": MessageLookupByLibrary.simpleMessage("编辑规则"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英语"),
"enableOverride": MessageLookupByLibrary.simpleMessage("启用覆写"),
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
"existsTip": m4,
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
@@ -209,6 +232,7 @@ class MessageLookup extends MessageLookupByLibrary {
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"开启后将可以通过9090端口控制Clash内核",
),
"externalFetch": MessageLookupByLibrary.simpleMessage("外部获取"),
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeip过滤"),
@@ -237,6 +261,7 @@ class MessageLookup extends MessageLookupByLibrary {
"global": MessageLookupByLibrary.simpleMessage("全局"),
"go": MessageLookupByLibrary.simpleMessage("前往"),
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage("前往配置脚本"),
"hasCacheChange": MessageLookupByLibrary.simpleMessage("是否缓存修改"),
"host": MessageLookupByLibrary.simpleMessage("主机"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("追加Hosts"),
@@ -244,6 +269,7 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagement": MessageLookupByLibrary.simpleMessage("快捷键管理"),
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage("使用键盘控制应用程序"),
"hours": MessageLookupByLibrary.simpleMessage("小时"),
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
@@ -263,6 +289,7 @@ class MessageLookup extends MessageLookupByLibrary {
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
"ja": MessageLookupByLibrary.simpleMessage("日语"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
"justNow": MessageLookupByLibrary.simpleMessage("刚刚"),
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
"key": MessageLookupByLibrary.simpleMessage(""),
"language": MessageLookupByLibrary.simpleMessage("语言"),
@@ -270,6 +297,7 @@ class MessageLookup extends MessageLookupByLibrary {
"light": MessageLookupByLibrary.simpleMessage("浅色"),
"list": MessageLookupByLibrary.simpleMessage("列表"),
"listen": MessageLookupByLibrary.simpleMessage("监听"),
"loadTest": MessageLookupByLibrary.simpleMessage("加载测试"),
"local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
@@ -290,10 +318,12 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"minutesAgo": m7,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
"months": MessageLookupByLibrary.simpleMessage(""),
"monthsAgo": m8,
"more": MessageLookupByLibrary.simpleMessage("更多"),
"name": MessageLookupByLibrary.simpleMessage("名称"),
"nameSort": MessageLookupByLibrary.simpleMessage("按名称排序"),
@@ -311,6 +341,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
"noIcon": MessageLookupByLibrary.simpleMessage("无图标"),
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
"noLongerRemind": MessageLookupByLibrary.simpleMessage("不再提示"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("无网络应用"),
@@ -320,8 +351,8 @@ class MessageLookup extends MessageLookupByLibrary {
"none": MessageLookupByLibrary.simpleMessage(""),
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullTip": m5,
"numberTip": m6,
"nullTip": m9,
"numberTip": m10,
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
@@ -338,7 +369,9 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage("在脚本模式下不生效"),
"overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
"overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"),
"palette": MessageLookupByLibrary.simpleMessage("调色板"),
"password": MessageLookupByLibrary.simpleMessage("密码"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
@@ -353,7 +386,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("端口"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
"portTip": m7,
"portTip": m11,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
"preview": MessageLookupByLibrary.simpleMessage("预览"),
@@ -403,6 +436,7 @@ class MessageLookup extends MessageLookupByLibrary {
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
"reload": MessageLookupByLibrary.simpleMessage("重载"),
"remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"),
@@ -420,6 +454,7 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS连接跟随rules,需配置proxy-server-nameserver",
),
"restart": MessageLookupByLibrary.simpleMessage("重启"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"),
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
@@ -435,11 +470,14 @@ class MessageLookup extends MessageLookupByLibrary {
"saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"),
"saveTip": MessageLookupByLibrary.simpleMessage("确定要保存吗?"),
"script": MessageLookupByLibrary.simpleMessage("脚本"),
"scriptModeDesc": MessageLookupByLibrary.simpleMessage(
"脚本模式,使用外部扩展脚本,提供一键覆写配置的能力",
),
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
"selectedCountTitle": m8,
"selectedCountTitle": m12,
"settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
@@ -454,6 +492,9 @@ class MessageLookup extends MessageLookupByLibrary {
"specialRules": MessageLookupByLibrary.simpleMessage("特殊规则"),
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
"standard": MessageLookupByLibrary.simpleMessage("标准"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
"标准模式,覆写基本配置,提供简单追加规则能力",
),
"start": MessageLookupByLibrary.simpleMessage("启动"),
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
"status": MessageLookupByLibrary.simpleMessage("状态"),
@@ -491,6 +532,8 @@ class MessageLookup extends MessageLookupByLibrary {
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
"turnOff": MessageLookupByLibrary.simpleMessage("关闭"),
"turnOn": MessageLookupByLibrary.simpleMessage("开启"),
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
"无法更新当前配置文件",
@@ -504,12 +547,15 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("上传"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
"urlTip": m9,
"urlTip": m13,
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage(""),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("活力"),
"view": MessageLookupByLibrary.simpleMessage("查看"),
"vpnConfigChangeDetected": MessageLookupByLibrary.simpleMessage(
"检测到VPN相关配置改动",
),
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
"通过VpnService自动路由系统所有流量",
@@ -521,6 +567,7 @@ class MessageLookup extends MessageLookupByLibrary {
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
"years": MessageLookupByLibrary.simpleMessage(""),
"yearsAgo": m14,
"zh_CN": MessageLookupByLibrary.simpleMessage("中文简体"),
};
}

View File

@@ -2619,6 +2619,26 @@ class AppLocalizations {
);
}
/// `Advanced configuration`
String get advancedConfig {
return Intl.message(
'Advanced configuration',
name: 'advancedConfig',
desc: '',
args: [],
);
}
/// `Provide diverse configuration options`
String get advancedConfigDesc {
return Intl.message(
'Provide diverse configuration options',
name: 'advancedConfigDesc',
desc: '',
args: [],
);
}
/// `{count} items have been selected`
String selectedCountTitle(Object count) {
return Intl.message(
@@ -3014,10 +3034,10 @@ class AppLocalizations {
);
}
/// `No {label} at the moment`
/// `No {label} yet`
String nullTip(Object label) {
return Intl.message(
'No $label at the moment',
'No $label yet',
name: 'nullTip',
desc: '',
args: [label],
@@ -3378,6 +3398,246 @@ class AppLocalizations {
args: [],
);
}
/// `Edit rule`
String get editRule {
return Intl.message('Edit rule', name: 'editRule', desc: '', args: []);
}
/// `Override mode`
String get overrideMode {
return Intl.message(
'Override mode',
name: 'overrideMode',
desc: '',
args: [],
);
}
/// `Standard mode, override basic configuration, provide simple rule addition capability`
String get standardModeDesc {
return Intl.message(
'Standard mode, override basic configuration, provide simple rule addition capability',
name: 'standardModeDesc',
desc: '',
args: [],
);
}
/// `Script mode, use external extension scripts, provide one-click override configuration capability`
String get scriptModeDesc {
return Intl.message(
'Script mode, use external extension scripts, provide one-click override configuration capability',
name: 'scriptModeDesc',
desc: '',
args: [],
);
}
/// `Added rules`
String get addedRules {
return Intl.message('Added rules', name: 'addedRules', desc: '', args: []);
}
/// `Control global added rules`
String get controlGlobalAddedRules {
return Intl.message(
'Control global added rules',
name: 'controlGlobalAddedRules',
desc: '',
args: [],
);
}
/// `Override script`
String get overrideScript {
return Intl.message(
'Override script',
name: 'overrideScript',
desc: '',
args: [],
);
}
/// `Go to configure script`
String get goToConfigureScript {
return Intl.message(
'Go to configure script',
name: 'goToConfigureScript',
desc: '',
args: [],
);
}
/// `Edit global rules`
String get editGlobalRules {
return Intl.message(
'Edit global rules',
name: 'editGlobalRules',
desc: '',
args: [],
);
}
/// `External fetch`
String get externalFetch {
return Intl.message(
'External fetch',
name: 'externalFetch',
desc: '',
args: [],
);
}
/// `Are you sure you want to force crash the core?`
String get confirmForceCrashCore {
return Intl.message(
'Are you sure you want to force crash the core?',
name: 'confirmForceCrashCore',
desc: '',
args: [],
);
}
/// `Are you sure you want to clear all data?`
String get confirmClearAllData {
return Intl.message(
'Are you sure you want to clear all data?',
name: 'confirmClearAllData',
desc: '',
args: [],
);
}
/// `Load test`
String get loadTest {
return Intl.message('Load test', name: 'loadTest', desc: '', args: []);
}
/// `{count, plural, =1{1 year ago} other{{count} years ago}}`
String yearsAgo(num count) {
return Intl.plural(
count,
one: '1 year ago',
other: '$count years ago',
name: 'yearsAgo',
desc: '',
args: [count],
);
}
/// `{count, plural, =1{1 month ago} other{{count} months ago}}`
String monthsAgo(num count) {
return Intl.plural(
count,
one: '1 month ago',
other: '$count months ago',
name: 'monthsAgo',
desc: '',
args: [count],
);
}
/// `{count, plural, =1{1 day ago} other{{count} days ago}}`
String daysAgo(num count) {
return Intl.plural(
count,
one: '1 day ago',
other: '$count days ago',
name: 'daysAgo',
desc: '',
args: [count],
);
}
/// `{count, plural, =1{1 hour ago} other{{count} hours ago}}`
String hoursAgo(num count) {
return Intl.plural(
count,
one: '1 hour ago',
other: '$count hours ago',
name: 'hoursAgo',
desc: '',
args: [count],
);
}
/// `{count, plural, =1{1 minute ago} other{{count} minutes ago}}`
String minutesAgo(num count) {
return Intl.plural(
count,
one: '1 minute ago',
other: '$count minutes ago',
name: 'minutesAgo',
desc: '',
args: [count],
);
}
/// `Just now`
String get justNow {
return Intl.message('Just now', name: 'justNow', desc: '', args: []);
}
/// `Don't remind again`
String get noLongerRemind {
return Intl.message(
'Don\'t remind again',
name: 'noLongerRemind',
desc: '',
args: [],
);
}
/// `Access Control Settings`
String get accessControlSettings {
return Intl.message(
'Access Control Settings',
name: 'accessControlSettings',
desc: '',
args: [],
);
}
/// `Turn On`
String get turnOn {
return Intl.message('Turn On', name: 'turnOn', desc: '', args: []);
}
/// `Turn Off`
String get turnOff {
return Intl.message('Turn Off', name: 'turnOff', desc: '', args: []);
}
/// `Core configuration change detected`
String get coreConfigChangeDetected {
return Intl.message(
'Core configuration change detected',
name: 'coreConfigChangeDetected',
desc: '',
args: [],
);
}
/// `Reload`
String get reload {
return Intl.message('Reload', name: 'reload', desc: '', args: []);
}
/// `VPN configuration change detected`
String get vpnConfigChangeDetected {
return Intl.message(
'VPN configuration change detected',
name: 'vpnConfigChangeDetected',
desc: '',
args: [],
);
}
/// `Restart`
String get restart {
return Intl.message('Restart', name: 'restart', desc: '', args: []);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -39,8 +39,12 @@ Future<void> _service(List<String> flags) async {
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
coreController.setupConfig(
clashConfig,
final setupState = globalState.getSetupState(
globalState.config.currentProfileId,
);
globalState.setupConfig(
setupState: setupState,
patchConfig: clashConfig,
preloadInvoke: () {
globalState.handleStart();
},

View File

@@ -2,8 +2,8 @@ export 'android_manager.dart';
export 'app_manager.dart';
export 'connectivity_manager.dart';
export 'core_manager.dart';
export 'message_manager.dart';
export 'proxy_manager.dart';
export 'status_manager.dart';
export 'theme_manager.dart';
export 'tile_manager.dart';
export 'tray_manager.dart';

View File

@@ -1,151 +0,0 @@
import 'dart:async';
import 'dart:collection';
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:flutter/material.dart';
class MessageManager extends StatefulWidget {
final Widget child;
const MessageManager({super.key, required this.child});
@override
State<MessageManager> createState() => MessageManagerState();
}
class MessageManagerState extends State<MessageManager> {
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
final _bufferMessages = Queue<CommonMessage>();
final _activeTimers = <String, Timer>{};
bool _isDisplayingMessage = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_messagesNotifier.dispose();
for (final timer in _activeTimers.values) {
timer.cancel();
}
_activeTimers.clear();
_bufferMessages.clear();
super.dispose();
}
void message(String text) {
final commonMessage = CommonMessage(id: utils.uuidV4, text: text);
_bufferMessages.add(commonMessage);
commonPrint.log('message: $text');
_processQueue();
}
void _cancelMessage(String id) {
_bufferMessages.removeWhere((msg) => msg.id == id);
if (_activeTimers.containsKey(id)) {
_removeMessage(id);
}
}
void _processQueue() {
if (_isDisplayingMessage || _bufferMessages.isEmpty) {
return;
}
_isDisplayingMessage = true;
final message = _bufferMessages.removeFirst();
_messagesNotifier.value = List.from(_messagesNotifier.value)..add(message);
final timer = Timer(message.duration, () {
_removeMessage(message.id);
});
_activeTimers[message.id] = timer;
}
void _removeMessage(String id) {
_activeTimers.remove(id)?.cancel();
final currentMessages = List<CommonMessage>.from(_messagesNotifier.value);
currentMessages.removeWhere((msg) => msg.id == id);
_messagesNotifier.value = currentMessages;
_isDisplayingMessage = false;
_processQueue();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
ValueListenableBuilder(
valueListenable: _messagesNotifier,
builder: (_, messages, _) {
return Container(
margin: EdgeInsets.only(
top: kToolbarHeight + 12,
left: 12,
right: 12,
),
child: FadeThroughBox(
alignment: Alignment.topRight,
child: messages.isEmpty
? SizedBox()
: LayoutBuilder(
key: Key(messages.last.id),
builder: (_, constraints) {
return Dismissible(
key: ValueKey(messages.last.id),
onDismissed: (_) {
_cancelMessage(messages.last.id);
},
child: Card(
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(
Radius.circular(14),
),
),
elevation: 10,
color: context.colorScheme.surfaceContainerHigh,
child: Container(
width: min(constraints.maxWidth, 500),
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
messages.last.text,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: 16),
IconButton(
padding: EdgeInsets.all(2),
visualDensity: VisualDensity.compact,
onPressed: () {
_cancelMessage(messages.last.id);
},
icon: Icon(Icons.close),
),
],
),
),
),
);
},
),
),
);
},
),
],
);
}
}

View File

@@ -0,0 +1,239 @@
import 'dart:async';
import 'dart:collection';
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class StatusManager extends StatefulWidget {
final Widget child;
const StatusManager({super.key, required this.child});
@override
State<StatusManager> createState() => StatusManagerState();
}
class StatusManagerState extends State<StatusManager> {
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
final _bufferMessages = Queue<CommonMessage>();
final _activeTimers = <String, Timer>{};
bool _isDisplayingMessage = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_messagesNotifier.dispose();
for (final timer in _activeTimers.values) {
timer.cancel();
}
_activeTimers.clear();
_bufferMessages.clear();
super.dispose();
}
void message(String text, {MessageActionState? actionState}) {
final commonMessage = CommonMessage(
id: utils.uuidV4,
text: text,
actionState: actionState,
);
_bufferMessages.add(commonMessage);
commonPrint.log('message: $text');
_processQueue();
}
void _cancelMessage(String id) {
_bufferMessages.removeWhere((msg) => msg.id == id);
if (_activeTimers.containsKey(id)) {
_removeMessage(id);
}
}
void _processQueue() {
if (_isDisplayingMessage || _bufferMessages.isEmpty) {
return;
}
_isDisplayingMessage = true;
final message = _bufferMessages.removeFirst();
_messagesNotifier.value = List.from(_messagesNotifier.value)..add(message);
final timer = Timer(message.duration, () {
_removeMessage(message.id);
});
_activeTimers[message.id] = timer;
}
void _removeMessage(String id) {
_activeTimers.remove(id)?.cancel();
final currentMessages = List<CommonMessage>.from(_messagesNotifier.value);
currentMessages.removeWhere((msg) => msg.id == id);
_messagesNotifier.value = currentMessages;
_isDisplayingMessage = false;
_processQueue();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
Consumer(
builder: (_, ref, child) {
final top = ref.watch(overlayTopOffsetProvider);
return Container(
margin: EdgeInsets.only(
top: top + MediaQuery.of(context).viewPadding.top,
),
child: child,
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LoadingIndicator(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: ValueListenableBuilder(
valueListenable: _messagesNotifier,
builder: (_, messages, _) {
return FadeThroughBox(
alignment: Alignment.centerRight,
child: messages.isEmpty
? SizedBox()
: LayoutBuilder(
key: Key(messages.last.id),
builder: (_, constraints) {
return Dismissible(
key: ValueKey(messages.last.id),
onDismissed: (_) {
_cancelMessage(messages.last.id);
},
child: Card(
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(
Radius.circular(14),
),
),
elevation: 10,
color: context
.colorScheme
.surfaceContainerHigh,
child: Container(
width: min(constraints.maxWidth, 500),
constraints: BoxConstraints(
minHeight: 50,
),
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
messages.last.text,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: 16),
if (messages.last.actionState != null)
CommonMinFilledButtonTheme(
child: FilledButton.tonal(
onPressed: () async {
_cancelMessage(
messages.last.id,
);
messages.last.actionState!
.action();
},
child: Text(
messages
.last
.actionState!
.actionText,
),
),
),
],
),
),
),
);
},
),
);
},
),
),
],
),
),
],
);
}
}
class LoadingIndicator extends ConsumerWidget {
const LoadingIndicator({super.key});
@override
Widget build(BuildContext context, ref) {
final loading = ref.watch(loadingProvider);
final isMobileView = ref.watch(isMobileViewProvider);
return AnimatedSwitcher(
duration: midDuration,
transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
child: loading && isMobileView
? Container(
height: 36,
margin: EdgeInsets.only(top: 8),
child: Material(
elevation: 3,
color: context.colorScheme.surfaceContainer,
surfaceTintColor: context.colorScheme.surfaceTint,
borderRadius: BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 10, height: 36),
SizedBox(
width: 36,
height: 36,
child: Padding(
padding: EdgeInsets.all(6),
child: CircularProgressIndicator(),
),
),
],
),
),
)
: SizedBox(),
);
}
}

View File

@@ -1,5 +1,6 @@
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/providers/state.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -19,17 +20,29 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
void initState() {
super.initState();
ref.listenManual(vpnStateProvider, (prev, next) {
showTip();
if (prev != next) {
showTip(next);
}
});
}
void showTip() {
void showTip(VpnState state) {
throttler.call(
FunctionTag.vpnTip,
() {
if (ref.read(isStartProvider)) {
globalState.showNotifier(appLocalizations.vpnTip);
if (!ref.read(isStartProvider) || state == globalState.lastVpnState) {
return;
}
globalState.showNotifier(
appLocalizations.vpnConfigChangeDetected,
actionState: MessageActionState(
actionText: appLocalizations.restart,
action: () async {
await globalState.handleStop();
await globalState.appController.updateStatus(true);
},
),
);
},
duration: const Duration(seconds: 6),
fire: true,

View File

@@ -386,6 +386,19 @@ abstract class Rule with _$Rule {
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
}
extension RulesExt on List<Rule> {
List<Rule> updateWith(Rule rule) {
var newList = List<Rule>.from(this);
final index = newList.indexWhere((item) => item.id == rule.id);
if (index != -1) {
newList[index] = rule;
} else {
newList.insert(0, rule);
}
return newList;
}
}
@freezed
abstract class SubRule with _$SubRule {
const factory SubRule({required String name}) = _SubRule;

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -34,6 +35,27 @@ abstract class Package with _$Package {
_$PackageFromJson(json);
}
extension PackagesExt on List<Package> {
List<Package> getSortList({
required List<String> pinedList,
required AccessSortType sortType,
}) {
return sorted((a, b) {
final isSelectA = pinedList.contains(a.packageName);
final isSelectB = pinedList.contains(b.packageName);
if (isSelectA != isSelectB) {
return isSelectA ? -1 : 1;
}
return switch (sortType) {
AccessSortType.none => 0,
AccessSortType.name => a.label.compareTo(b.label),
AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime),
};
});
}
}
@freezed
abstract class Metadata with _$Metadata {
const factory Metadata({
@@ -423,20 +445,20 @@ abstract class Field with _$Field {
}) = _Field;
}
enum PopupMenuItemType { primary, danger }
class PopupMenuItemData {
const PopupMenuItemData({
this.icon,
required this.label,
required this.onPressed,
this.onPressed,
this.danger = false,
this.subItems = const [],
});
final String label;
final VoidCallback? onPressed;
final IconData? icon;
final bool danger;
final List<PopupMenuItemData> subItems;
}
@freezed
@@ -492,6 +514,19 @@ abstract class Script with _$Script {
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
}
extension ScriptsExt on List<Script> {
Script? get(String? id) {
if (id == null) {
return null;
}
final index = indexWhere((script) => script.id == id);
if (index != -1) {
return this[index];
}
return null;
}
}
@freezed
abstract class DelayState with _$DelayState {
const factory DelayState({required int delay, required bool group}) =

View File

@@ -116,6 +116,11 @@ extension AccessControlExt on AccessControl {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
AccessControl copyWithNewList(List<String> value) => switch (mode) {
AccessControlMode.acceptSelected => copyWith(acceptList: value),
AccessControlMode.rejectSelected => copyWith(rejectList: value),
};
}
@freezed
@@ -223,24 +228,6 @@ abstract class ScriptProps with _$ScriptProps {
_$ScriptPropsFromJson(json);
}
extension ScriptPropsExt on ScriptProps {
String? get realId {
final index = scripts.indexWhere((script) => script.id == currentId);
if (index != -1) {
return currentId;
}
return null;
}
Script? get currentScript {
final index = scripts.indexWhere((script) => script.id == currentId);
if (index != -1) {
return scripts[index];
}
return null;
}
}
@freezed
abstract class Config with _$Config {
const factory Config({
@@ -258,7 +245,8 @@ abstract class Config with _$Config {
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
@Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default(ScriptProps()) ScriptProps scriptProps,
@Default([]) List<Script> scripts,
@Default([]) List<Rule> rules,
}) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);
@@ -273,6 +261,12 @@ abstract class Config with _$Config {
(json['vpnProps'] as Map)['accessControl'] = accessControlMap;
}
}
if (json['scripts'] == null) {
final scriptPropsJson = json['scriptProps'] as Map<String, Object?>?;
if (scriptPropsJson != null) {
json['scripts'] = scriptPropsJson['scripts'];
}
}
} catch (_) {}
return Config.fromJson(json);
}

View File

@@ -2604,7 +2604,7 @@ as List<Script>,
/// @nodoc
mixin _$Config {
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps get appSetting; List<Profile> get profiles; List<HotKeyAction> get hotKeyActions; String? get currentProfileId; bool get overrideDns; DAV? get dav; NetworkProps get networkProps; VpnProps get vpnProps;@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps get themeProps; ProxiesStyle get proxiesStyle; WindowProps get windowProps; ClashConfig get patchClashConfig; ScriptProps get scriptProps;
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps get appSetting; List<Profile> get profiles; List<HotKeyAction> get hotKeyActions; String? get currentProfileId; bool get overrideDns; DAV? get dav; NetworkProps get networkProps; VpnProps get vpnProps;@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps get themeProps; ProxiesStyle get proxiesStyle; WindowProps get windowProps; ClashConfig get patchClashConfig; List<Script> get scripts; List<Rule> get rules;
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -2617,16 +2617,16 @@ $ConfigCopyWith<Config> get copyWith => _$ConfigCopyWithImpl<Config>(this as Con
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Config&&(identical(other.appSetting, appSetting) || other.appSetting == appSetting)&&const DeepCollectionEquality().equals(other.profiles, profiles)&&const DeepCollectionEquality().equals(other.hotKeyActions, hotKeyActions)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dav, dav) || other.dav == dav)&&(identical(other.networkProps, networkProps) || other.networkProps == networkProps)&&(identical(other.vpnProps, vpnProps) || other.vpnProps == vpnProps)&&(identical(other.themeProps, themeProps) || other.themeProps == themeProps)&&(identical(other.proxiesStyle, proxiesStyle) || other.proxiesStyle == proxiesStyle)&&(identical(other.windowProps, windowProps) || other.windowProps == windowProps)&&(identical(other.patchClashConfig, patchClashConfig) || other.patchClashConfig == patchClashConfig)&&(identical(other.scriptProps, scriptProps) || other.scriptProps == scriptProps));
return identical(this, other) || (other.runtimeType == runtimeType&&other is Config&&(identical(other.appSetting, appSetting) || other.appSetting == appSetting)&&const DeepCollectionEquality().equals(other.profiles, profiles)&&const DeepCollectionEquality().equals(other.hotKeyActions, hotKeyActions)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dav, dav) || other.dav == dav)&&(identical(other.networkProps, networkProps) || other.networkProps == networkProps)&&(identical(other.vpnProps, vpnProps) || other.vpnProps == vpnProps)&&(identical(other.themeProps, themeProps) || other.themeProps == themeProps)&&(identical(other.proxiesStyle, proxiesStyle) || other.proxiesStyle == proxiesStyle)&&(identical(other.windowProps, windowProps) || other.windowProps == windowProps)&&(identical(other.patchClashConfig, patchClashConfig) || other.patchClashConfig == patchClashConfig)&&const DeepCollectionEquality().equals(other.scripts, scripts)&&const DeepCollectionEquality().equals(other.rules, rules));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,appSetting,const DeepCollectionEquality().hash(profiles),const DeepCollectionEquality().hash(hotKeyActions),currentProfileId,overrideDns,dav,networkProps,vpnProps,themeProps,proxiesStyle,windowProps,patchClashConfig,scriptProps);
int get hashCode => Object.hash(runtimeType,appSetting,const DeepCollectionEquality().hash(profiles),const DeepCollectionEquality().hash(hotKeyActions),currentProfileId,overrideDns,dav,networkProps,vpnProps,themeProps,proxiesStyle,windowProps,patchClashConfig,const DeepCollectionEquality().hash(scripts),const DeepCollectionEquality().hash(rules));
@override
String toString() {
return 'Config(appSetting: $appSetting, profiles: $profiles, hotKeyActions: $hotKeyActions, currentProfileId: $currentProfileId, overrideDns: $overrideDns, dav: $dav, networkProps: $networkProps, vpnProps: $vpnProps, themeProps: $themeProps, proxiesStyle: $proxiesStyle, windowProps: $windowProps, patchClashConfig: $patchClashConfig, scriptProps: $scriptProps)';
return 'Config(appSetting: $appSetting, profiles: $profiles, hotKeyActions: $hotKeyActions, currentProfileId: $currentProfileId, overrideDns: $overrideDns, dav: $dav, networkProps: $networkProps, vpnProps: $vpnProps, themeProps: $themeProps, proxiesStyle: $proxiesStyle, windowProps: $windowProps, patchClashConfig: $patchClashConfig, scripts: $scripts, rules: $rules)';
}
@@ -2637,11 +2637,11 @@ abstract mixin class $ConfigCopyWith<$Res> {
factory $ConfigCopyWith(Config value, $Res Function(Config) _then) = _$ConfigCopyWithImpl;
@useResult
$Res call({
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, ScriptProps scriptProps
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, List<Script> scripts, List<Rule> rules
});
$AppSettingPropsCopyWith<$Res> get appSetting;$DAVCopyWith<$Res>? get dav;$NetworkPropsCopyWith<$Res> get networkProps;$VpnPropsCopyWith<$Res> get vpnProps;$ThemePropsCopyWith<$Res> get themeProps;$ProxiesStyleCopyWith<$Res> get proxiesStyle;$WindowPropsCopyWith<$Res> get windowProps;$ClashConfigCopyWith<$Res> get patchClashConfig;$ScriptPropsCopyWith<$Res> get scriptProps;
$AppSettingPropsCopyWith<$Res> get appSetting;$DAVCopyWith<$Res>? get dav;$NetworkPropsCopyWith<$Res> get networkProps;$VpnPropsCopyWith<$Res> get vpnProps;$ThemePropsCopyWith<$Res> get themeProps;$ProxiesStyleCopyWith<$Res> get proxiesStyle;$WindowPropsCopyWith<$Res> get windowProps;$ClashConfigCopyWith<$Res> get patchClashConfig;
}
/// @nodoc
@@ -2654,7 +2654,7 @@ class _$ConfigCopyWithImpl<$Res>
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? appSetting = null,Object? profiles = null,Object? hotKeyActions = null,Object? currentProfileId = freezed,Object? overrideDns = null,Object? dav = freezed,Object? networkProps = null,Object? vpnProps = null,Object? themeProps = null,Object? proxiesStyle = null,Object? windowProps = null,Object? patchClashConfig = null,Object? scriptProps = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? appSetting = null,Object? profiles = null,Object? hotKeyActions = null,Object? currentProfileId = freezed,Object? overrideDns = null,Object? dav = freezed,Object? networkProps = null,Object? vpnProps = null,Object? themeProps = null,Object? proxiesStyle = null,Object? windowProps = null,Object? patchClashConfig = null,Object? scripts = null,Object? rules = null,}) {
return _then(_self.copyWith(
appSetting: null == appSetting ? _self.appSetting : appSetting // ignore: cast_nullable_to_non_nullable
as AppSettingProps,profiles: null == profiles ? _self.profiles : profiles // ignore: cast_nullable_to_non_nullable
@@ -2668,8 +2668,9 @@ as VpnProps,themeProps: null == themeProps ? _self.themeProps : themeProps // ig
as ThemeProps,proxiesStyle: null == proxiesStyle ? _self.proxiesStyle : proxiesStyle // ignore: cast_nullable_to_non_nullable
as ProxiesStyle,windowProps: null == windowProps ? _self.windowProps : windowProps // ignore: cast_nullable_to_non_nullable
as WindowProps,patchClashConfig: null == patchClashConfig ? _self.patchClashConfig : patchClashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,scriptProps: null == scriptProps ? _self.scriptProps : scriptProps // ignore: cast_nullable_to_non_nullable
as ScriptProps,
as ClashConfig,scripts: null == scripts ? _self.scripts : scripts // ignore: cast_nullable_to_non_nullable
as List<Script>,rules: null == rules ? _self.rules : rules // ignore: cast_nullable_to_non_nullable
as List<Rule>,
));
}
/// Create a copy of Config
@@ -2747,15 +2748,6 @@ $ClashConfigCopyWith<$Res> get patchClashConfig {
return $ClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return _then(_self.copyWith(patchClashConfig: value));
});
}/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ScriptPropsCopyWith<$Res> get scriptProps {
return $ScriptPropsCopyWith<$Res>(_self.scriptProps, (value) {
return _then(_self.copyWith(scriptProps: value));
});
}
}
@@ -2838,10 +2830,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, ScriptProps scriptProps)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, List<Script> scripts, List<Rule> rules)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Config() when $default != null:
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scriptProps);case _:
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scripts,_that.rules);case _:
return orElse();
}
@@ -2859,10 +2851,10 @@ return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.curren
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, ScriptProps scriptProps) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, List<Script> scripts, List<Rule> rules) $default,) {final _that = this;
switch (_that) {
case _Config():
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scriptProps);case _:
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scripts,_that.rules);case _:
throw StateError('Unexpected subclass');
}
@@ -2879,10 +2871,10 @@ return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.curren
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, ScriptProps scriptProps)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, List<Script> scripts, List<Rule> rules)? $default,) {final _that = this;
switch (_that) {
case _Config() when $default != null:
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scriptProps);case _:
return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.currentProfileId,_that.overrideDns,_that.dav,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyle,_that.windowProps,_that.patchClashConfig,_that.scripts,_that.rules);case _:
return null;
}
@@ -2894,7 +2886,7 @@ return $default(_that.appSetting,_that.profiles,_that.hotKeyActions,_that.curren
@JsonSerializable()
class _Config implements Config {
const _Config({@JsonKey(fromJson: AppSettingProps.safeFromJson) this.appSetting = defaultAppSettingProps, final List<Profile> profiles = const [], final List<HotKeyAction> hotKeyActions = const [], this.currentProfileId, this.overrideDns = false, this.dav, this.networkProps = defaultNetworkProps, this.vpnProps = defaultVpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) required this.themeProps, this.proxiesStyle = defaultProxiesStyle, this.windowProps = defaultWindowProps, this.patchClashConfig = defaultClashConfig, this.scriptProps = const ScriptProps()}): _profiles = profiles,_hotKeyActions = hotKeyActions;
const _Config({@JsonKey(fromJson: AppSettingProps.safeFromJson) this.appSetting = defaultAppSettingProps, final List<Profile> profiles = const [], final List<HotKeyAction> hotKeyActions = const [], this.currentProfileId, this.overrideDns = false, this.dav, this.networkProps = defaultNetworkProps, this.vpnProps = defaultVpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) required this.themeProps, this.proxiesStyle = defaultProxiesStyle, this.windowProps = defaultWindowProps, this.patchClashConfig = defaultClashConfig, final List<Script> scripts = const [], final List<Rule> rules = const []}): _profiles = profiles,_hotKeyActions = hotKeyActions,_scripts = scripts,_rules = rules;
factory _Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
@override@JsonKey(fromJson: AppSettingProps.safeFromJson) final AppSettingProps appSetting;
@@ -2921,7 +2913,20 @@ class _Config implements Config {
@override@JsonKey() final ProxiesStyle proxiesStyle;
@override@JsonKey() final WindowProps windowProps;
@override@JsonKey() final ClashConfig patchClashConfig;
@override@JsonKey() final ScriptProps scriptProps;
final List<Script> _scripts;
@override@JsonKey() List<Script> get scripts {
if (_scripts is EqualUnmodifiableListView) return _scripts;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_scripts);
}
final List<Rule> _rules;
@override@JsonKey() List<Rule> get rules {
if (_rules is EqualUnmodifiableListView) return _rules;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_rules);
}
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@@ -2936,16 +2941,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Config&&(identical(other.appSetting, appSetting) || other.appSetting == appSetting)&&const DeepCollectionEquality().equals(other._profiles, _profiles)&&const DeepCollectionEquality().equals(other._hotKeyActions, _hotKeyActions)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dav, dav) || other.dav == dav)&&(identical(other.networkProps, networkProps) || other.networkProps == networkProps)&&(identical(other.vpnProps, vpnProps) || other.vpnProps == vpnProps)&&(identical(other.themeProps, themeProps) || other.themeProps == themeProps)&&(identical(other.proxiesStyle, proxiesStyle) || other.proxiesStyle == proxiesStyle)&&(identical(other.windowProps, windowProps) || other.windowProps == windowProps)&&(identical(other.patchClashConfig, patchClashConfig) || other.patchClashConfig == patchClashConfig)&&(identical(other.scriptProps, scriptProps) || other.scriptProps == scriptProps));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Config&&(identical(other.appSetting, appSetting) || other.appSetting == appSetting)&&const DeepCollectionEquality().equals(other._profiles, _profiles)&&const DeepCollectionEquality().equals(other._hotKeyActions, _hotKeyActions)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dav, dav) || other.dav == dav)&&(identical(other.networkProps, networkProps) || other.networkProps == networkProps)&&(identical(other.vpnProps, vpnProps) || other.vpnProps == vpnProps)&&(identical(other.themeProps, themeProps) || other.themeProps == themeProps)&&(identical(other.proxiesStyle, proxiesStyle) || other.proxiesStyle == proxiesStyle)&&(identical(other.windowProps, windowProps) || other.windowProps == windowProps)&&(identical(other.patchClashConfig, patchClashConfig) || other.patchClashConfig == patchClashConfig)&&const DeepCollectionEquality().equals(other._scripts, _scripts)&&const DeepCollectionEquality().equals(other._rules, _rules));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,appSetting,const DeepCollectionEquality().hash(_profiles),const DeepCollectionEquality().hash(_hotKeyActions),currentProfileId,overrideDns,dav,networkProps,vpnProps,themeProps,proxiesStyle,windowProps,patchClashConfig,scriptProps);
int get hashCode => Object.hash(runtimeType,appSetting,const DeepCollectionEquality().hash(_profiles),const DeepCollectionEquality().hash(_hotKeyActions),currentProfileId,overrideDns,dav,networkProps,vpnProps,themeProps,proxiesStyle,windowProps,patchClashConfig,const DeepCollectionEquality().hash(_scripts),const DeepCollectionEquality().hash(_rules));
@override
String toString() {
return 'Config(appSetting: $appSetting, profiles: $profiles, hotKeyActions: $hotKeyActions, currentProfileId: $currentProfileId, overrideDns: $overrideDns, dav: $dav, networkProps: $networkProps, vpnProps: $vpnProps, themeProps: $themeProps, proxiesStyle: $proxiesStyle, windowProps: $windowProps, patchClashConfig: $patchClashConfig, scriptProps: $scriptProps)';
return 'Config(appSetting: $appSetting, profiles: $profiles, hotKeyActions: $hotKeyActions, currentProfileId: $currentProfileId, overrideDns: $overrideDns, dav: $dav, networkProps: $networkProps, vpnProps: $vpnProps, themeProps: $themeProps, proxiesStyle: $proxiesStyle, windowProps: $windowProps, patchClashConfig: $patchClashConfig, scripts: $scripts, rules: $rules)';
}
@@ -2956,11 +2961,11 @@ abstract mixin class _$ConfigCopyWith<$Res> implements $ConfigCopyWith<$Res> {
factory _$ConfigCopyWith(_Config value, $Res Function(_Config) _then) = __$ConfigCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, ScriptProps scriptProps
@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSetting, List<Profile> profiles, List<HotKeyAction> hotKeyActions, String? currentProfileId, bool overrideDns, DAV? dav, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyle proxiesStyle, WindowProps windowProps, ClashConfig patchClashConfig, List<Script> scripts, List<Rule> rules
});
@override $AppSettingPropsCopyWith<$Res> get appSetting;@override $DAVCopyWith<$Res>? get dav;@override $NetworkPropsCopyWith<$Res> get networkProps;@override $VpnPropsCopyWith<$Res> get vpnProps;@override $ThemePropsCopyWith<$Res> get themeProps;@override $ProxiesStyleCopyWith<$Res> get proxiesStyle;@override $WindowPropsCopyWith<$Res> get windowProps;@override $ClashConfigCopyWith<$Res> get patchClashConfig;@override $ScriptPropsCopyWith<$Res> get scriptProps;
@override $AppSettingPropsCopyWith<$Res> get appSetting;@override $DAVCopyWith<$Res>? get dav;@override $NetworkPropsCopyWith<$Res> get networkProps;@override $VpnPropsCopyWith<$Res> get vpnProps;@override $ThemePropsCopyWith<$Res> get themeProps;@override $ProxiesStyleCopyWith<$Res> get proxiesStyle;@override $WindowPropsCopyWith<$Res> get windowProps;@override $ClashConfigCopyWith<$Res> get patchClashConfig;
}
/// @nodoc
@@ -2973,7 +2978,7 @@ class __$ConfigCopyWithImpl<$Res>
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? appSetting = null,Object? profiles = null,Object? hotKeyActions = null,Object? currentProfileId = freezed,Object? overrideDns = null,Object? dav = freezed,Object? networkProps = null,Object? vpnProps = null,Object? themeProps = null,Object? proxiesStyle = null,Object? windowProps = null,Object? patchClashConfig = null,Object? scriptProps = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? appSetting = null,Object? profiles = null,Object? hotKeyActions = null,Object? currentProfileId = freezed,Object? overrideDns = null,Object? dav = freezed,Object? networkProps = null,Object? vpnProps = null,Object? themeProps = null,Object? proxiesStyle = null,Object? windowProps = null,Object? patchClashConfig = null,Object? scripts = null,Object? rules = null,}) {
return _then(_Config(
appSetting: null == appSetting ? _self.appSetting : appSetting // ignore: cast_nullable_to_non_nullable
as AppSettingProps,profiles: null == profiles ? _self._profiles : profiles // ignore: cast_nullable_to_non_nullable
@@ -2987,8 +2992,9 @@ as VpnProps,themeProps: null == themeProps ? _self.themeProps : themeProps // ig
as ThemeProps,proxiesStyle: null == proxiesStyle ? _self.proxiesStyle : proxiesStyle // ignore: cast_nullable_to_non_nullable
as ProxiesStyle,windowProps: null == windowProps ? _self.windowProps : windowProps // ignore: cast_nullable_to_non_nullable
as WindowProps,patchClashConfig: null == patchClashConfig ? _self.patchClashConfig : patchClashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,scriptProps: null == scriptProps ? _self.scriptProps : scriptProps // ignore: cast_nullable_to_non_nullable
as ScriptProps,
as ClashConfig,scripts: null == scripts ? _self._scripts : scripts // ignore: cast_nullable_to_non_nullable
as List<Script>,rules: null == rules ? _self._rules : rules // ignore: cast_nullable_to_non_nullable
as List<Rule>,
));
}
@@ -3067,15 +3073,6 @@ $ClashConfigCopyWith<$Res> get patchClashConfig {
return $ClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return _then(_self.copyWith(patchClashConfig: value));
});
}/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ScriptPropsCopyWith<$Res> get scriptProps {
return $ScriptPropsCopyWith<$Res>(_self.scriptProps, (value) {
return _then(_self.copyWith(scriptProps: value));
});
}
}

View File

@@ -360,9 +360,16 @@ _Config _$ConfigFromJson(Map<String, dynamic> json) => _Config(
patchClashConfig: json['patchClashConfig'] == null
? defaultClashConfig
: ClashConfig.fromJson(json['patchClashConfig'] as Map<String, dynamic>),
scriptProps: json['scriptProps'] == null
? const ScriptProps()
: ScriptProps.fromJson(json['scriptProps'] as Map<String, dynamic>),
scripts:
(json['scripts'] as List<dynamic>?)
?.map((e) => Script.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
rules:
(json['rules'] as List<dynamic>?)
?.map((e) => Rule.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
);
Map<String, dynamic> _$ConfigToJson(_Config instance) => <String, dynamic>{
@@ -378,5 +385,6 @@ Map<String, dynamic> _$ConfigToJson(_Config instance) => <String, dynamic>{
'proxiesStyle': instance.proxiesStyle,
'windowProps': instance.windowProps,
'patchClashConfig': instance.patchClashConfig,
'scriptProps': instance.scriptProps,
'scripts': instance.scripts,
'rules': instance.rules,
};

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,9 @@ _Profile _$ProfileFromJson(Map<String, dynamic> json) => _Profile(
overrideData: json['overrideData'] == null
? const OverrideData()
: OverrideData.fromJson(json['overrideData'] as Map<String, dynamic>),
overwrite: json['overwrite'] == null
? const Overwrite()
: Overwrite.fromJson(json['overwrite'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ProfileToJson(_Profile instance) => <String, dynamic>{
@@ -64,8 +67,63 @@ Map<String, dynamic> _$ProfileToJson(_Profile instance) => <String, dynamic>{
'selectedMap': instance.selectedMap,
'unfoldSet': instance.unfoldSet.toList(),
'overrideData': instance.overrideData,
'overwrite': instance.overwrite,
};
_Overwrite _$OverwriteFromJson(Map<String, dynamic> json) => _Overwrite(
type:
$enumDecodeNullable(_$OverwriteTypeEnumMap, json['type']) ??
OverwriteType.standard,
standardOverwrite: json['standardOverwrite'] == null
? const StandardOverwrite()
: StandardOverwrite.fromJson(
json['standardOverwrite'] as Map<String, dynamic>,
),
scriptOverwrite: json['scriptOverwrite'] == null
? const ScriptOverwrite()
: ScriptOverwrite.fromJson(
json['scriptOverwrite'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$OverwriteToJson(_Overwrite instance) =>
<String, dynamic>{
'type': _$OverwriteTypeEnumMap[instance.type]!,
'standardOverwrite': instance.standardOverwrite,
'scriptOverwrite': instance.scriptOverwrite,
};
const _$OverwriteTypeEnumMap = {
OverwriteType.standard: 'standard',
OverwriteType.script: 'script',
};
_StandardOverwrite _$StandardOverwriteFromJson(Map<String, dynamic> json) =>
_StandardOverwrite(
addedRules:
(json['addedRules'] as List<dynamic>?)
?.map((e) => Rule.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
disabledRuleIds:
(json['disabledRuleIds'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
);
Map<String, dynamic> _$StandardOverwriteToJson(_StandardOverwrite instance) =>
<String, dynamic>{
'addedRules': instance.addedRules,
'disabledRuleIds': instance.disabledRuleIds,
};
_ScriptOverwrite _$ScriptOverwriteFromJson(Map<String, dynamic> json) =>
_ScriptOverwrite(scriptId: json['scriptId'] as String?);
Map<String, dynamic> _$ScriptOverwriteToJson(_ScriptOverwrite instance) =>
<String, dynamic>{'scriptId': instance.scriptId};
_OverrideData _$OverrideDataFromJson(Map<String, dynamic> json) =>
_OverrideData(
enable: json['enable'] as bool? ?? false,

View File

@@ -6572,4 +6572,303 @@ $OverrideDataCopyWith<$Res>? get overrideData {
}
}
/// @nodoc
mixin _$SetupState {
String? get profileId; int? get profileLastUpdateDate; OverwriteType get overwriteType; List<Rule> get addedRules; String? get scriptContent; bool get overrideDns; Dns get dns;
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SetupStateCopyWith<SetupState> get copyWith => _$SetupStateCopyWithImpl<SetupState>(this as SetupState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupState&&(identical(other.profileId, profileId) || other.profileId == profileId)&&(identical(other.profileLastUpdateDate, profileLastUpdateDate) || other.profileLastUpdateDate == profileLastUpdateDate)&&(identical(other.overwriteType, overwriteType) || other.overwriteType == overwriteType)&&const DeepCollectionEquality().equals(other.addedRules, addedRules)&&(identical(other.scriptContent, scriptContent) || other.scriptContent == scriptContent)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dns, dns) || other.dns == dns));
}
@override
int get hashCode => Object.hash(runtimeType,profileId,profileLastUpdateDate,overwriteType,const DeepCollectionEquality().hash(addedRules),scriptContent,overrideDns,dns);
@override
String toString() {
return 'SetupState(profileId: $profileId, profileLastUpdateDate: $profileLastUpdateDate, overwriteType: $overwriteType, addedRules: $addedRules, scriptContent: $scriptContent, overrideDns: $overrideDns, dns: $dns)';
}
}
/// @nodoc
abstract mixin class $SetupStateCopyWith<$Res> {
factory $SetupStateCopyWith(SetupState value, $Res Function(SetupState) _then) = _$SetupStateCopyWithImpl;
@useResult
$Res call({
String? profileId, int? profileLastUpdateDate, OverwriteType overwriteType, List<Rule> addedRules, String? scriptContent, bool overrideDns, Dns dns
});
$DnsCopyWith<$Res> get dns;
}
/// @nodoc
class _$SetupStateCopyWithImpl<$Res>
implements $SetupStateCopyWith<$Res> {
_$SetupStateCopyWithImpl(this._self, this._then);
final SetupState _self;
final $Res Function(SetupState) _then;
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? profileId = freezed,Object? profileLastUpdateDate = freezed,Object? overwriteType = null,Object? addedRules = null,Object? scriptContent = freezed,Object? overrideDns = null,Object? dns = null,}) {
return _then(_self.copyWith(
profileId: freezed == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as String?,profileLastUpdateDate: freezed == profileLastUpdateDate ? _self.profileLastUpdateDate : profileLastUpdateDate // ignore: cast_nullable_to_non_nullable
as int?,overwriteType: null == overwriteType ? _self.overwriteType : overwriteType // ignore: cast_nullable_to_non_nullable
as OverwriteType,addedRules: null == addedRules ? _self.addedRules : addedRules // ignore: cast_nullable_to_non_nullable
as List<Rule>,scriptContent: freezed == scriptContent ? _self.scriptContent : scriptContent // ignore: cast_nullable_to_non_nullable
as String?,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,dns: null == dns ? _self.dns : dns // ignore: cast_nullable_to_non_nullable
as Dns,
));
}
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DnsCopyWith<$Res> get dns {
return $DnsCopyWith<$Res>(_self.dns, (value) {
return _then(_self.copyWith(dns: value));
});
}
}
/// Adds pattern-matching-related methods to [SetupState].
extension SetupStatePatterns on SetupState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SetupState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SetupState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SetupState value) $default,){
final _that = this;
switch (_that) {
case _SetupState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SetupState value)? $default,){
final _that = this;
switch (_that) {
case _SetupState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? profileId, int? profileLastUpdateDate, OverwriteType overwriteType, List<Rule> addedRules, String? scriptContent, bool overrideDns, Dns dns)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SetupState() when $default != null:
return $default(_that.profileId,_that.profileLastUpdateDate,_that.overwriteType,_that.addedRules,_that.scriptContent,_that.overrideDns,_that.dns);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? profileId, int? profileLastUpdateDate, OverwriteType overwriteType, List<Rule> addedRules, String? scriptContent, bool overrideDns, Dns dns) $default,) {final _that = this;
switch (_that) {
case _SetupState():
return $default(_that.profileId,_that.profileLastUpdateDate,_that.overwriteType,_that.addedRules,_that.scriptContent,_that.overrideDns,_that.dns);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? profileId, int? profileLastUpdateDate, OverwriteType overwriteType, List<Rule> addedRules, String? scriptContent, bool overrideDns, Dns dns)? $default,) {final _that = this;
switch (_that) {
case _SetupState() when $default != null:
return $default(_that.profileId,_that.profileLastUpdateDate,_that.overwriteType,_that.addedRules,_that.scriptContent,_that.overrideDns,_that.dns);case _:
return null;
}
}
}
/// @nodoc
class _SetupState implements SetupState {
const _SetupState({required this.profileId, required this.profileLastUpdateDate, required this.overwriteType, required final List<Rule> addedRules, required this.scriptContent, required this.overrideDns, required this.dns}): _addedRules = addedRules;
@override final String? profileId;
@override final int? profileLastUpdateDate;
@override final OverwriteType overwriteType;
final List<Rule> _addedRules;
@override List<Rule> get addedRules {
if (_addedRules is EqualUnmodifiableListView) return _addedRules;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_addedRules);
}
@override final String? scriptContent;
@override final bool overrideDns;
@override final Dns dns;
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SetupStateCopyWith<_SetupState> get copyWith => __$SetupStateCopyWithImpl<_SetupState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupState&&(identical(other.profileId, profileId) || other.profileId == profileId)&&(identical(other.profileLastUpdateDate, profileLastUpdateDate) || other.profileLastUpdateDate == profileLastUpdateDate)&&(identical(other.overwriteType, overwriteType) || other.overwriteType == overwriteType)&&const DeepCollectionEquality().equals(other._addedRules, _addedRules)&&(identical(other.scriptContent, scriptContent) || other.scriptContent == scriptContent)&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.dns, dns) || other.dns == dns));
}
@override
int get hashCode => Object.hash(runtimeType,profileId,profileLastUpdateDate,overwriteType,const DeepCollectionEquality().hash(_addedRules),scriptContent,overrideDns,dns);
@override
String toString() {
return 'SetupState(profileId: $profileId, profileLastUpdateDate: $profileLastUpdateDate, overwriteType: $overwriteType, addedRules: $addedRules, scriptContent: $scriptContent, overrideDns: $overrideDns, dns: $dns)';
}
}
/// @nodoc
abstract mixin class _$SetupStateCopyWith<$Res> implements $SetupStateCopyWith<$Res> {
factory _$SetupStateCopyWith(_SetupState value, $Res Function(_SetupState) _then) = __$SetupStateCopyWithImpl;
@override @useResult
$Res call({
String? profileId, int? profileLastUpdateDate, OverwriteType overwriteType, List<Rule> addedRules, String? scriptContent, bool overrideDns, Dns dns
});
@override $DnsCopyWith<$Res> get dns;
}
/// @nodoc
class __$SetupStateCopyWithImpl<$Res>
implements _$SetupStateCopyWith<$Res> {
__$SetupStateCopyWithImpl(this._self, this._then);
final _SetupState _self;
final $Res Function(_SetupState) _then;
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? profileId = freezed,Object? profileLastUpdateDate = freezed,Object? overwriteType = null,Object? addedRules = null,Object? scriptContent = freezed,Object? overrideDns = null,Object? dns = null,}) {
return _then(_SetupState(
profileId: freezed == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as String?,profileLastUpdateDate: freezed == profileLastUpdateDate ? _self.profileLastUpdateDate : profileLastUpdateDate // ignore: cast_nullable_to_non_nullable
as int?,overwriteType: null == overwriteType ? _self.overwriteType : overwriteType // ignore: cast_nullable_to_non_nullable
as OverwriteType,addedRules: null == addedRules ? _self._addedRules : addedRules // ignore: cast_nullable_to_non_nullable
as List<Rule>,scriptContent: freezed == scriptContent ? _self.scriptContent : scriptContent // ignore: cast_nullable_to_non_nullable
as String?,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,dns: null == dns ? _self.dns : dns // ignore: cast_nullable_to_non_nullable
as Dns,
));
}
/// Create a copy of SetupState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DnsCopyWith<$Res> get dns {
return $DnsCopyWith<$Res>(_self.dns, (value) {
return _then(_self.copyWith(dns: value));
});
}
}
// dart format on

View File

@@ -271,7 +271,7 @@ as bool,
/// @nodoc
mixin _$CommonMessage {
String get id; String get text; Duration get duration;
String get id; String get text; Duration get duration; MessageActionState? get actionState;
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -282,16 +282,16 @@ $CommonMessageCopyWith<CommonMessage> get copyWith => _$CommonMessageCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CommonMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.duration, duration) || other.duration == duration));
return identical(this, other) || (other.runtimeType == runtimeType&&other is CommonMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.actionState, actionState) || other.actionState == actionState));
}
@override
int get hashCode => Object.hash(runtimeType,id,text,duration);
int get hashCode => Object.hash(runtimeType,id,text,duration,actionState);
@override
String toString() {
return 'CommonMessage(id: $id, text: $text, duration: $duration)';
return 'CommonMessage(id: $id, text: $text, duration: $duration, actionState: $actionState)';
}
@@ -302,11 +302,11 @@ abstract mixin class $CommonMessageCopyWith<$Res> {
factory $CommonMessageCopyWith(CommonMessage value, $Res Function(CommonMessage) _then) = _$CommonMessageCopyWithImpl;
@useResult
$Res call({
String id, String text, Duration duration
String id, String text, Duration duration, MessageActionState? actionState
});
$MessageActionStateCopyWith<$Res>? get actionState;
}
/// @nodoc
@@ -319,15 +319,28 @@ class _$CommonMessageCopyWithImpl<$Res>
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? text = null,Object? duration = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? text = null,Object? duration = null,Object? actionState = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
as Duration,
as Duration,actionState: freezed == actionState ? _self.actionState : actionState // ignore: cast_nullable_to_non_nullable
as MessageActionState?,
));
}
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$MessageActionStateCopyWith<$Res>? get actionState {
if (_self.actionState == null) {
return null;
}
return $MessageActionStateCopyWith<$Res>(_self.actionState!, (value) {
return _then(_self.copyWith(actionState: value));
});
}
}
@@ -409,10 +422,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String text, Duration duration)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String text, Duration duration, MessageActionState? actionState)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CommonMessage() when $default != null:
return $default(_that.id,_that.text,_that.duration);case _:
return $default(_that.id,_that.text,_that.duration,_that.actionState);case _:
return orElse();
}
@@ -430,10 +443,10 @@ return $default(_that.id,_that.text,_that.duration);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String text, Duration duration) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String text, Duration duration, MessageActionState? actionState) $default,) {final _that = this;
switch (_that) {
case _CommonMessage():
return $default(_that.id,_that.text,_that.duration);case _:
return $default(_that.id,_that.text,_that.duration,_that.actionState);case _:
throw StateError('Unexpected subclass');
}
@@ -450,10 +463,10 @@ return $default(_that.id,_that.text,_that.duration);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String text, Duration duration)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String text, Duration duration, MessageActionState? actionState)? $default,) {final _that = this;
switch (_that) {
case _CommonMessage() when $default != null:
return $default(_that.id,_that.text,_that.duration);case _:
return $default(_that.id,_that.text,_that.duration,_that.actionState);case _:
return null;
}
@@ -465,12 +478,13 @@ return $default(_that.id,_that.text,_that.duration);case _:
class _CommonMessage implements CommonMessage {
const _CommonMessage({required this.id, required this.text, this.duration = const Duration(seconds: 3)});
const _CommonMessage({required this.id, required this.text, this.duration = const Duration(seconds: 3), this.actionState});
@override final String id;
@override final String text;
@override@JsonKey() final Duration duration;
@override final MessageActionState? actionState;
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@@ -482,16 +496,16 @@ _$CommonMessageCopyWith<_CommonMessage> get copyWith => __$CommonMessageCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CommonMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.duration, duration) || other.duration == duration));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CommonMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.actionState, actionState) || other.actionState == actionState));
}
@override
int get hashCode => Object.hash(runtimeType,id,text,duration);
int get hashCode => Object.hash(runtimeType,id,text,duration,actionState);
@override
String toString() {
return 'CommonMessage(id: $id, text: $text, duration: $duration)';
return 'CommonMessage(id: $id, text: $text, duration: $duration, actionState: $actionState)';
}
@@ -502,11 +516,11 @@ abstract mixin class _$CommonMessageCopyWith<$Res> implements $CommonMessageCopy
factory _$CommonMessageCopyWith(_CommonMessage value, $Res Function(_CommonMessage) _then) = __$CommonMessageCopyWithImpl;
@override @useResult
$Res call({
String id, String text, Duration duration
String id, String text, Duration duration, MessageActionState? actionState
});
@override $MessageActionStateCopyWith<$Res>? get actionState;
}
/// @nodoc
@@ -519,12 +533,285 @@ class __$CommonMessageCopyWithImpl<$Res>
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? text = null,Object? duration = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? text = null,Object? duration = null,Object? actionState = freezed,}) {
return _then(_CommonMessage(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
as Duration,
as Duration,actionState: freezed == actionState ? _self.actionState : actionState // ignore: cast_nullable_to_non_nullable
as MessageActionState?,
));
}
/// Create a copy of CommonMessage
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$MessageActionStateCopyWith<$Res>? get actionState {
if (_self.actionState == null) {
return null;
}
return $MessageActionStateCopyWith<$Res>(_self.actionState!, (value) {
return _then(_self.copyWith(actionState: value));
});
}
}
/// @nodoc
mixin _$MessageActionState {
String get actionText; VoidCallback get action;
/// Create a copy of MessageActionState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MessageActionStateCopyWith<MessageActionState> get copyWith => _$MessageActionStateCopyWithImpl<MessageActionState>(this as MessageActionState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MessageActionState&&(identical(other.actionText, actionText) || other.actionText == actionText)&&(identical(other.action, action) || other.action == action));
}
@override
int get hashCode => Object.hash(runtimeType,actionText,action);
@override
String toString() {
return 'MessageActionState(actionText: $actionText, action: $action)';
}
}
/// @nodoc
abstract mixin class $MessageActionStateCopyWith<$Res> {
factory $MessageActionStateCopyWith(MessageActionState value, $Res Function(MessageActionState) _then) = _$MessageActionStateCopyWithImpl;
@useResult
$Res call({
String actionText, VoidCallback action
});
}
/// @nodoc
class _$MessageActionStateCopyWithImpl<$Res>
implements $MessageActionStateCopyWith<$Res> {
_$MessageActionStateCopyWithImpl(this._self, this._then);
final MessageActionState _self;
final $Res Function(MessageActionState) _then;
/// Create a copy of MessageActionState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? actionText = null,Object? action = null,}) {
return _then(_self.copyWith(
actionText: null == actionText ? _self.actionText : actionText // ignore: cast_nullable_to_non_nullable
as String,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable
as VoidCallback,
));
}
}
/// Adds pattern-matching-related methods to [MessageActionState].
extension MessageActionStatePatterns on MessageActionState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MessageActionState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MessageActionState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MessageActionState value) $default,){
final _that = this;
switch (_that) {
case _MessageActionState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MessageActionState value)? $default,){
final _that = this;
switch (_that) {
case _MessageActionState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String actionText, VoidCallback action)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MessageActionState() when $default != null:
return $default(_that.actionText,_that.action);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String actionText, VoidCallback action) $default,) {final _that = this;
switch (_that) {
case _MessageActionState():
return $default(_that.actionText,_that.action);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String actionText, VoidCallback action)? $default,) {final _that = this;
switch (_that) {
case _MessageActionState() when $default != null:
return $default(_that.actionText,_that.action);case _:
return null;
}
}
}
/// @nodoc
class _MessageActionState implements MessageActionState {
const _MessageActionState({required this.actionText, required this.action});
@override final String actionText;
@override final VoidCallback action;
/// Create a copy of MessageActionState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MessageActionStateCopyWith<_MessageActionState> get copyWith => __$MessageActionStateCopyWithImpl<_MessageActionState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MessageActionState&&(identical(other.actionText, actionText) || other.actionText == actionText)&&(identical(other.action, action) || other.action == action));
}
@override
int get hashCode => Object.hash(runtimeType,actionText,action);
@override
String toString() {
return 'MessageActionState(actionText: $actionText, action: $action)';
}
}
/// @nodoc
abstract mixin class _$MessageActionStateCopyWith<$Res> implements $MessageActionStateCopyWith<$Res> {
factory _$MessageActionStateCopyWith(_MessageActionState value, $Res Function(_MessageActionState) _then) = __$MessageActionStateCopyWithImpl;
@override @useResult
$Res call({
String actionText, VoidCallback action
});
}
/// @nodoc
class __$MessageActionStateCopyWithImpl<$Res>
implements _$MessageActionStateCopyWith<$Res> {
__$MessageActionStateCopyWithImpl(this._self, this._then);
final _MessageActionState _self;
final $Res Function(_MessageActionState) _then;
/// Create a copy of MessageActionState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? actionText = null,Object? action = null,}) {
return _then(_MessageActionState(
actionText: null == actionText ? _self.actionText : actionText // ignore: cast_nullable_to_non_nullable
as String,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable
as VoidCallback,
));
}
@@ -851,7 +1138,7 @@ $AppBarEditStateCopyWith<$Res>? get editState {
/// @nodoc
mixin _$AppBarSearchState {
Function(String) get onSearch; String? get query;
Function(String) get onSearch; bool get autoAddSearch; String? get query;
/// Create a copy of AppBarSearchState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -862,16 +1149,16 @@ $AppBarSearchStateCopyWith<AppBarSearchState> get copyWith => _$AppBarSearchStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppBarSearchState&&(identical(other.onSearch, onSearch) || other.onSearch == onSearch)&&(identical(other.query, query) || other.query == query));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppBarSearchState&&(identical(other.onSearch, onSearch) || other.onSearch == onSearch)&&(identical(other.autoAddSearch, autoAddSearch) || other.autoAddSearch == autoAddSearch)&&(identical(other.query, query) || other.query == query));
}
@override
int get hashCode => Object.hash(runtimeType,onSearch,query);
int get hashCode => Object.hash(runtimeType,onSearch,autoAddSearch,query);
@override
String toString() {
return 'AppBarSearchState(onSearch: $onSearch, query: $query)';
return 'AppBarSearchState(onSearch: $onSearch, autoAddSearch: $autoAddSearch, query: $query)';
}
@@ -882,7 +1169,7 @@ abstract mixin class $AppBarSearchStateCopyWith<$Res> {
factory $AppBarSearchStateCopyWith(AppBarSearchState value, $Res Function(AppBarSearchState) _then) = _$AppBarSearchStateCopyWithImpl;
@useResult
$Res call({
Function(String) onSearch, String? query
Function(String) onSearch, bool autoAddSearch, String? query
});
@@ -899,10 +1186,11 @@ class _$AppBarSearchStateCopyWithImpl<$Res>
/// Create a copy of AppBarSearchState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? onSearch = null,Object? query = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? onSearch = null,Object? autoAddSearch = null,Object? query = freezed,}) {
return _then(_self.copyWith(
onSearch: null == onSearch ? _self.onSearch : onSearch // ignore: cast_nullable_to_non_nullable
as Function(String),query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as Function(String),autoAddSearch: null == autoAddSearch ? _self.autoAddSearch : autoAddSearch // ignore: cast_nullable_to_non_nullable
as bool,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -988,10 +1276,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Function(String) onSearch, String? query)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Function(String) onSearch, bool autoAddSearch, String? query)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AppBarSearchState() when $default != null:
return $default(_that.onSearch,_that.query);case _:
return $default(_that.onSearch,_that.autoAddSearch,_that.query);case _:
return orElse();
}
@@ -1009,10 +1297,10 @@ return $default(_that.onSearch,_that.query);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Function(String) onSearch, String? query) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Function(String) onSearch, bool autoAddSearch, String? query) $default,) {final _that = this;
switch (_that) {
case _AppBarSearchState():
return $default(_that.onSearch,_that.query);case _:
return $default(_that.onSearch,_that.autoAddSearch,_that.query);case _:
throw StateError('Unexpected subclass');
}
@@ -1029,10 +1317,10 @@ return $default(_that.onSearch,_that.query);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Function(String) onSearch, String? query)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Function(String) onSearch, bool autoAddSearch, String? query)? $default,) {final _that = this;
switch (_that) {
case _AppBarSearchState() when $default != null:
return $default(_that.onSearch,_that.query);case _:
return $default(_that.onSearch,_that.autoAddSearch,_that.query);case _:
return null;
}
@@ -1044,10 +1332,11 @@ return $default(_that.onSearch,_that.query);case _:
class _AppBarSearchState implements AppBarSearchState {
const _AppBarSearchState({required this.onSearch, this.query = null});
const _AppBarSearchState({required this.onSearch, this.autoAddSearch = true, this.query = null});
@override final Function(String) onSearch;
@override@JsonKey() final bool autoAddSearch;
@override@JsonKey() final String? query;
/// Create a copy of AppBarSearchState
@@ -1060,16 +1349,16 @@ _$AppBarSearchStateCopyWith<_AppBarSearchState> get copyWith => __$AppBarSearchS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppBarSearchState&&(identical(other.onSearch, onSearch) || other.onSearch == onSearch)&&(identical(other.query, query) || other.query == query));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppBarSearchState&&(identical(other.onSearch, onSearch) || other.onSearch == onSearch)&&(identical(other.autoAddSearch, autoAddSearch) || other.autoAddSearch == autoAddSearch)&&(identical(other.query, query) || other.query == query));
}
@override
int get hashCode => Object.hash(runtimeType,onSearch,query);
int get hashCode => Object.hash(runtimeType,onSearch,autoAddSearch,query);
@override
String toString() {
return 'AppBarSearchState(onSearch: $onSearch, query: $query)';
return 'AppBarSearchState(onSearch: $onSearch, autoAddSearch: $autoAddSearch, query: $query)';
}
@@ -1080,7 +1369,7 @@ abstract mixin class _$AppBarSearchStateCopyWith<$Res> implements $AppBarSearchS
factory _$AppBarSearchStateCopyWith(_AppBarSearchState value, $Res Function(_AppBarSearchState) _then) = __$AppBarSearchStateCopyWithImpl;
@override @useResult
$Res call({
Function(String) onSearch, String? query
Function(String) onSearch, bool autoAddSearch, String? query
});
@@ -1097,10 +1386,11 @@ class __$AppBarSearchStateCopyWithImpl<$Res>
/// Create a copy of AppBarSearchState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? onSearch = null,Object? query = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? onSearch = null,Object? autoAddSearch = null,Object? query = freezed,}) {
return _then(_AppBarSearchState(
onSearch: null == onSearch ? _self.onSearch : onSearch // ignore: cast_nullable_to_non_nullable
as Function(String),query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as Function(String),autoAddSearch: null == autoAddSearch ? _self.autoAddSearch : autoAddSearch // ignore: cast_nullable_to_non_nullable
as bool,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as String?,
));
}

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart';
@@ -56,6 +57,7 @@ abstract class Profile with _$Profile {
@Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet,
@Default(OverrideData()) OverrideData overrideData,
@Default(Overwrite()) Overwrite overwrite,
@JsonKey(includeToJson: false, includeFromJson: false)
@Default(false)
bool isUpdating,
@@ -74,6 +76,37 @@ abstract class Profile with _$Profile {
}
}
@freezed
abstract class Overwrite with _$Overwrite {
const factory Overwrite({
@Default(OverwriteType.standard) OverwriteType type,
@Default(StandardOverwrite()) StandardOverwrite standardOverwrite,
@Default(ScriptOverwrite()) ScriptOverwrite scriptOverwrite,
}) = _Overwrite;
factory Overwrite.fromJson(Map<String, Object?> json) =>
_$OverwriteFromJson(json);
}
@freezed
abstract class StandardOverwrite with _$StandardOverwrite {
const factory StandardOverwrite({
@Default([]) List<Rule> addedRules,
@Default([]) List<String> disabledRuleIds,
}) = _StandardOverwrite;
factory StandardOverwrite.fromJson(Map<String, Object?> json) =>
_$StandardOverwriteFromJson(json);
}
@freezed
abstract class ScriptOverwrite with _$ScriptOverwrite {
const factory ScriptOverwrite({String? scriptId}) = _ScriptOverwrite;
factory ScriptOverwrite.fromJson(Map<String, Object?> json) =>
_$ScriptOverwriteFromJson(json);
}
@freezed
abstract class OverrideData with _$OverrideData {
const factory OverrideData({
@@ -85,15 +118,6 @@ abstract class OverrideData with _$OverrideData {
_$OverrideDataFromJson(json);
}
extension OverrideDataExt on OverrideData {
List<String> get runningRule {
if (!enable) {
return [];
}
return rule.rules.map((item) => item.value).toList();
}
}
@freezed
abstract class OverrideRule with _$OverrideRule {
const factory OverrideRule({
@@ -106,6 +130,15 @@ abstract class OverrideRule with _$OverrideRule {
_$OverrideRuleFromJson(json);
}
extension OverrideDataExt on OverrideData {
List<String> get runningRule {
if (!enable) {
return [];
}
return rule.rules.map((item) => item.value).toList();
}
}
extension OverrideRuleExt on OverrideRule {
List<Rule> get rules => switch (type == OverrideRuleType.override) {
true => overrideRules,
@@ -169,7 +202,7 @@ extension ProfileExtension on Profile {
return await copyWith(
label: label ?? utils.getFileNameForDisposition(disposition) ?? id,
subscriptionInfo: SubscriptionInfo.formHString(userinfo),
).saveFile(response.data);
).saveFile(response.data ?? Uint8List.fromList([]));
}
Future<Profile> saveFile(Uint8List bytes) async {
@@ -178,7 +211,9 @@ extension ProfileExtension on Profile {
throw message;
}
final file = await getFile();
await file.writeAsBytes(bytes);
await Isolate.run(() async {
return await file.writeAsBytes(bytes);
});
return copyWith(lastUpdateDate: DateTime.now());
}
}

View File

@@ -1,4 +1,5 @@
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:flutter/material.dart';
@@ -246,3 +247,54 @@ abstract class ProfileOverrideModel with _$ProfileOverrideModel {
OverrideData? overrideData,
}) = _ProfileOverrideModel;
}
@freezed
abstract class SetupState with _$SetupState {
const factory SetupState({
required String? profileId,
required int? profileLastUpdateDate,
required OverwriteType overwriteType,
required List<Rule> addedRules,
required String? scriptContent,
required bool overrideDns,
required Dns dns,
}) = _SetupState;
}
extension SetupStateExt on SetupState {
bool needSetup(SetupState? lastSetupState) {
if (lastSetupState == null) {
return true;
}
if (profileId != lastSetupState.profileId) {
return true;
}
if (profileLastUpdateDate != lastSetupState.profileLastUpdateDate) {
return true;
}
if (overwriteType != lastSetupState.overwriteType) {
if (!ruleEquality.equals(addedRules, lastSetupState.addedRules) ||
scriptContent != lastSetupState.scriptContent) {
return true;
}
} else {
if (overwriteType == OverwriteType.script) {
if (scriptContent != lastSetupState.scriptContent) {
return true;
}
}
if (overwriteType == OverwriteType.standard) {
if (!ruleEquality.equals(addedRules, lastSetupState.addedRules)) {
return true;
}
}
}
if (overrideDns != lastSetupState.overrideDns) {
return true;
}
if (overrideDns == true && dns != lastSetupState.dns) {
return true;
}
return false;
}
}

View File

@@ -5,9 +5,7 @@ part 'generated/widget.freezed.dart';
@freezed
abstract class ActivateState with _$ActivateState {
const factory ActivateState({
required bool active,
}) = _ActivateState;
const factory ActivateState({required bool active}) = _ActivateState;
}
@freezed
@@ -16,9 +14,18 @@ abstract class CommonMessage with _$CommonMessage {
required String id,
required String text,
@Default(Duration(seconds: 3)) Duration duration,
MessageActionState? actionState,
}) = _CommonMessage;
}
@freezed
abstract class MessageActionState with _$MessageActionState {
const factory MessageActionState({
required String actionText,
required VoidCallback action,
}) = _MessageActionState;
}
@freezed
abstract class AppBarState with _$AppBarState {
const factory AppBarState({
@@ -32,6 +39,7 @@ abstract class AppBarState with _$AppBarState {
abstract class AppBarSearchState with _$AppBarSearchState {
const factory AppBarSearchState({
required Function(String) onSearch,
@Default(true) bool autoAddSearch,
@Default(null) String? query,
}) = _AppBarSearchState;
}

View File

@@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:re_editor/re_editor.dart';
import 'package:re_highlight/languages/javascript.dart';
import 'package:re_highlight/languages/json.dart';
import 'package:re_highlight/languages/yaml.dart';
import 'package:re_highlight/styles/atom-one-light.dart';
@@ -48,11 +49,14 @@ class _EditorPageState extends ConsumerState<EditorPage> {
late CodeLineEditingController _controller;
late CodeFindController _findController;
late TextEditingController _titleController;
final _focusNode = FocusNode();
late FocusNode _focusNode;
late bool readOnly = false;
@override
void initState() {
super.initState();
readOnly = widget.onSave == null;
_focusNode = FocusNode(canRequestFocus: !readOnly);
_controller = CodeLineEditingController.fromText(widget.content);
_findController = CodeFindController(_controller);
_titleController = TextEditingController(text: widget.title);
@@ -113,25 +117,19 @@ class _EditorPageState extends ConsumerState<EditorPage> {
_findController.findMode();
}
Future<void> _handleImport() async {
final option = await globalState.showCommonDialog<ImportOption>(
child: _ImportOptionsDialog(),
);
if (option == null) {
return;
}
if (option == ImportOption.file) {
final file = await picker.pickerFile();
if (file == null) {
return;
}
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
_controller.text = res;
Future<void> _handleImportFormFile() async {
final file = await picker.pickerFile();
if (file == null) {
return;
}
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
_controller.text = res;
}
Future<void> _handleImportFormUrl() async {
final url = await globalState.showCommonDialog(
child: InputDialog(
title: '导入',
title: appLocalizations.import,
value: '',
labelText: appLocalizations.url,
validator: (value) {
@@ -149,7 +147,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
return;
}
final res = await request.getTextResponseForUrl(url);
_controller.text = res.data;
_controller.text = res.data ?? '';
}
@override
@@ -183,7 +181,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
autofocus: false,
),
actions: genActions([
if (widget.onSave != null)
if (!readOnly)
_wrapController(
(value) => _wrapTitleController(
(value) => IconButton(
@@ -198,21 +196,16 @@ class _EditorPageState extends ConsumerState<EditorPage> {
);
}
: null,
icon: const Icon(Icons.save_sharp),
icon: const Icon(Icons.save),
),
),
),
if (widget.supportRemoteDownload)
IconButton(
onPressed: _handleImport,
icon: Icon(Icons.arrow_downward),
),
_wrapController(
(value) => CommonPopupBox(
targetBuilder: (open) {
return IconButton(
onPressed: () {
open(offset: Offset(-20, 20));
open(offset: Offset(0, 0));
},
icon: const Icon(Icons.more_vert),
);
@@ -234,6 +227,21 @@ class _EditorPageState extends ConsumerState<EditorPage> {
label: appLocalizations.redo,
onPressed: _controller.canRedo ? _controller.redo : null,
),
if (widget.supportRemoteDownload && !readOnly)
PopupMenuItemData(
icon: Icons.arrow_downward,
label: appLocalizations.externalFetch,
subItems: [
PopupMenuItemData(
label: appLocalizations.importUrl,
onPressed: _handleImportFormUrl,
),
PopupMenuItemData(
label: appLocalizations.importFile,
onPressed: _handleImportFormFile,
),
],
),
],
),
),
@@ -241,6 +249,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
]),
),
body: CodeEditor(
readOnly: readOnly,
findController: _findController,
findBuilder: (context, controller, readOnly) => FindPanel(
controller: controller,
@@ -256,7 +265,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
child: child,
);
},
toolbarController: ContextMenuControllerImpl(),
toolbarController: ContextMenuControllerImpl(readOnly),
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
@@ -284,6 +293,8 @@ class _EditorPageState extends ConsumerState<EditorPage> {
'yaml': CodeHighlightThemeMode(mode: langYaml),
if (widget.languages.contains(Language.javaScript))
'javascript': CodeHighlightThemeMode(mode: langJavascript),
if (widget.languages.contains(Language.json))
'json': CodeHighlightThemeMode(mode: langJson),
},
theme: atomOneLightTheme,
),
@@ -294,7 +305,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
}
}
const double _kDefaultFindPanelHeight = 52;
const double _kDefaultFindPanelHeight = 56;
class FindPanel extends StatelessWidget implements PreferredSizeWidget {
final CodeFindController controller;
@@ -340,49 +351,49 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
} else {
result = '${value.result!.index + 1}/${value.result!.matches.length}';
}
final bar = Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isMobileView) ...[
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 360),
child: _buildFindInput(context, value),
final bar = CommonMinIconButtonTheme(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isMobileView) ...[
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 360),
child: _buildFindInput(context, value),
),
SizedBox(width: 12),
],
Text(result, style: context.textTheme.bodyMedium),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 2,
children: [
_buildIconButton(
onPressed: value.result == null
? null
: () {
controller.previousMatch();
},
icon: Icons.arrow_upward,
),
_buildIconButton(
onPressed: value.result == null
? null
: () {
controller.nextMatch();
},
icon: Icons.arrow_downward,
),
SizedBox(width: 2),
IconButton.filledTonal(
onPressed: controller.close,
icon: Icon(Icons.close, size: 16),
),
],
),
),
SizedBox(width: 12),
],
Text(result, style: context.textTheme.bodyMedium),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 6,
children: [
_buildIconButton(
onPressed: value.result == null
? null
: () {
controller.previousMatch();
},
icon: Icons.arrow_upward,
),
_buildIconButton(
onPressed: value.result == null
? null
: () {
controller.nextMatch();
},
icon: Icons.arrow_downward,
),
SizedBox(width: 2),
IconButton.filledTonal(
visualDensity: VisualDensity.compact,
onPressed: controller.close,
style: IconButton.styleFrom(padding: EdgeInsets.zero),
icon: Icon(Icons.close, size: 16),
),
],
),
),
],
),
);
if (isMobileView) {
return Column(
@@ -484,18 +495,16 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
}
Widget _buildIconButton({required IconData icon, VoidCallback? onPressed}) {
return IconButton(
visualDensity: VisualDensity.compact,
onPressed: onPressed,
style: IconButton.styleFrom(padding: EdgeInsets.all(0)),
icon: Icon(icon, size: 16),
);
return IconButton(onPressed: onPressed, icon: Icon(icon, size: 16));
}
}
class ContextMenuControllerImpl implements SelectionToolbarController {
OverlayEntry? _overlayEntry;
bool _isFirstRender = true;
bool readOnly = false;
ContextMenuControllerImpl(this.readOnly);
void _removeOverLayEntry() {
_overlayEntry?.remove();
@@ -532,11 +541,12 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
label: appLocalizations.copy,
onPressed: controller.copy,
),
PopupMenuItemData(
label: appLocalizations.paste,
onPressed: controller.paste,
),
if (isNotEmpty)
if (!readOnly)
PopupMenuItemData(
label: appLocalizations.paste,
onPressed: controller.paste,
),
if (isNotEmpty && !readOnly)
PopupMenuItemData(
label: appLocalizations.cut,
onPressed: controller.cut,

View File

@@ -225,44 +225,52 @@ class ProxiesStyleSetting extends _$ProxiesStyleSetting
}
@riverpod
class ScriptState extends _$ScriptState with AutoDisposeNotifierMixin {
class Scripts extends _$Scripts with AutoDisposeNotifierMixin {
@override
ScriptProps build() {
return globalState.config.scriptProps;
List<Script> build() {
return globalState.config.scripts;
}
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(scriptProps: value);
globalState.config = globalState.config.copyWith(scripts: value);
}
void setScript(Script script) {
final list = List<Script>.from(state.scripts);
final list = List<Script>.from(state);
final index = list.indexWhere((item) => item.id == script.id);
if (index != -1) {
list[index] = script;
} else {
list.add(script);
}
value = state.copyWith(scripts: list);
}
void setId(String id) {
value = state.copyWith(currentId: state.currentId != id ? id : null);
value = list;
}
void del(String id) {
final list = List<Script>.from(state.scripts);
final list = List<Script>.from(state);
final index = list.indexWhere((item) => item.label == id);
if (index != -1) {
list.removeAt(index);
}
final nextId = id == state.currentId ? null : state.currentId;
state = state.copyWith(scripts: list, currentId: nextId);
state = list;
}
bool isExits(String label) {
return state.scripts.indexWhere((item) => item.label == label) != -1;
return state.indexWhere((item) => item.label == label) != -1;
}
}
@riverpod
class Rules extends _$Rules with AutoDisposeNotifierMixin {
@override
List<Rule> build() {
return globalState.config.rules;
}
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(rules: value);
}
}

View File

@@ -591,52 +591,103 @@ abstract class _$ProxiesStyleSetting extends $Notifier<ProxiesStyle> {
}
}
@ProviderFor(ScriptState)
const scriptStateProvider = ScriptStateProvider._();
@ProviderFor(Scripts)
const scriptsProvider = ScriptsProvider._();
final class ScriptStateProvider
extends $NotifierProvider<ScriptState, ScriptProps> {
const ScriptStateProvider._()
final class ScriptsProvider extends $NotifierProvider<Scripts, List<Script>> {
const ScriptsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'scriptStateProvider',
name: r'scriptsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$scriptStateHash();
String debugGetCreateSourceHash() => _$scriptsHash();
@$internal
@override
ScriptState create() => ScriptState();
Scripts create() => Scripts();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ScriptProps value) {
Override overrideWithValue(List<Script> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ScriptProps>(value),
providerOverride: $SyncValueProvider<List<Script>>(value),
);
}
}
String _$scriptStateHash() => r'4770c34c3d24451fef95e372450e4a333b419977';
String _$scriptsHash() => r'2880a30711a7c46392b1ccc95f2cabbaeb2808c3';
abstract class _$ScriptState extends $Notifier<ScriptProps> {
ScriptProps build();
abstract class _$Scripts extends $Notifier<List<Script>> {
List<Script> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ScriptProps, ScriptProps>;
final ref = this.ref as $Ref<List<Script>, List<Script>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ScriptProps, ScriptProps>,
ScriptProps,
AnyNotifier<List<Script>, List<Script>>,
List<Script>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
@ProviderFor(Rules)
const rulesProvider = RulesProvider._();
final class RulesProvider extends $NotifierProvider<Rules, List<Rule>> {
const RulesProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'rulesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$rulesHash();
@$internal
@override
Rules create() => Rules();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<Rule> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<Rule>>(value),
);
}
}
String _$rulesHash() => r'ca7fdb6c8b9c5071002ac950494ec7c20937aa1b';
abstract class _$Rules extends $Notifier<List<Rule>> {
List<Rule> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<List<Rule>, List<Rule>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<Rule>, List<Rule>>,
List<Rule>,
Object?,
Object?
>;

View File

@@ -48,7 +48,7 @@ final class ConfigStateProvider
}
}
String _$configStateHash() => r'1f4ea3cc8f6461ba734e7e0c5d7295bfa4fd5afb';
String _$configStateHash() => r'0eb72e2cf30d1d0de694d28a3ec3c7658e825e92';
@ProviderFor(currentGroupsState)
const currentGroupsStateProvider = CurrentGroupsStateProvider._();
@@ -742,7 +742,7 @@ final class ProxiesListStateProvider
}
}
String _$proxiesListStateHash() => r'b16ad96516ece78f6cb22f558a0535000b784317';
String _$proxiesListStateHash() => r'32a748d651a6372b96931aae2100afc0529a83c9';
@ProviderFor(proxiesTabState)
const proxiesTabStateProvider = ProxiesTabStateProvider._();
@@ -784,7 +784,7 @@ final class ProxiesTabStateProvider
}
}
String _$proxiesTabStateHash() => r'143b106d74da618327cbac48af15078efd8cabee';
String _$proxiesTabStateHash() => r'510372c724217a41788129bc75fa1c4ec37c73c0';
@ProviderFor(isStart)
const isStartProvider = IsStartProvider._();
@@ -2180,7 +2180,7 @@ final class NeedSetupProvider
}
}
String _$needSetupHash() => r'25352164c340a5fb02add21246062dd1287595fb';
String _$needSetupHash() => r'6ea0d2be3df2046bbfa4e6c5d751727f06e7a4b3';
@ProviderFor(currentBrightness)
const currentBrightnessProvider = CurrentBrightnessProvider._();
@@ -2357,30 +2357,23 @@ final class AndroidStateProvider
String _$androidStateHash() => r'9f527fbb00c7e0c177f023e77d2f23458543d72f';
@ProviderFor(Query)
const queryProvider = QueryFamily._();
const queryProvider = QueryProvider._();
final class QueryProvider extends $NotifierProvider<Query, String> {
const QueryProvider._({
required QueryFamily super.from,
required QueryTag super.argument,
}) : super(
retry: null,
name: r'queryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
const QueryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'queryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$queryHash();
@override
String toString() {
return r'queryProvider'
''
'($argument)';
}
@$internal
@override
Query create() => Query();
@@ -2392,46 +2385,16 @@ final class QueryProvider extends $NotifierProvider<Query, String> {
providerOverride: $SyncValueProvider<String>(value),
);
}
@override
bool operator ==(Object other) {
return other is QueryProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$queryHash() => r'64c25c898d6d63f468d7e36fd591d390621c5624';
final class QueryFamily extends $Family
with $ClassFamilyOverride<Query, String, String, String, QueryTag> {
const QueryFamily._()
: super(
retry: null,
name: r'queryProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
QueryProvider call(QueryTag id) => QueryProvider._(argument: id, from: this);
@override
String toString() => r'queryProvider';
}
String _$queryHash() => r'e99b2a2439872f88f09fee8d63f0cc7fb4852186';
abstract class _$Query extends $Notifier<String> {
late final _$args = ref.$arg as QueryTag;
QueryTag get id => _$args;
String build(QueryTag id);
String build();
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final created = build();
final ref = this.ref as $Ref<String, String>;
final element =
ref.element
@@ -2444,3 +2407,382 @@ abstract class _$Query extends $Notifier<String> {
element.handleValue(ref, created);
}
}
@ProviderFor(overlayTopOffset)
const overlayTopOffsetProvider = OverlayTopOffsetProvider._();
final class OverlayTopOffsetProvider
extends $FunctionalProvider<double, double, double>
with $Provider<double> {
const OverlayTopOffsetProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'overlayTopOffsetProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$overlayTopOffsetHash();
@$internal
@override
$ProviderElement<double> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
double create(Ref ref) {
return overlayTopOffset(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(double value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<double>(value),
);
}
}
String _$overlayTopOffsetHash() => r'b2462f67acbd88b7a881dfe4c6353e68ba49961d';
@ProviderFor(SelectedIds)
const selectedIdsProvider = SelectedIdsProvider._();
final class SelectedIdsProvider
extends $NotifierProvider<SelectedIds, Set<String>> {
const SelectedIdsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedIdsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedIdsHash();
@$internal
@override
SelectedIds create() => SelectedIds();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Set<String> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Set<String>>(value),
);
}
}
String _$selectedIdsHash() => r'c22de28608456be15d03cc7911274ea215caf952';
abstract class _$SelectedIds extends $Notifier<Set<String>> {
Set<String> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<Set<String>, Set<String>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Set<String>, Set<String>>,
Set<String>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
@ProviderFor(profile)
const profileProvider = ProfileFamily._();
final class ProfileProvider
extends $FunctionalProvider<Profile?, Profile?, Profile?>
with $Provider<Profile?> {
const ProfileProvider._({
required ProfileFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'profileProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileHash();
@override
String toString() {
return r'profileProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<Profile?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Profile? create(Ref ref) {
final argument = this.argument as String;
return profile(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Profile? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Profile?>(value),
);
}
@override
bool operator ==(Object other) {
return other is ProfileProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$profileHash() => r'6992b7e6f32b24f2a876e2a2cab24fcd8e39d30d';
final class ProfileFamily extends $Family
with $FunctionalFamilyOverride<Profile?, String> {
const ProfileFamily._()
: super(
retry: null,
name: r'profileProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ProfileProvider call(String profileId) =>
ProfileProvider._(argument: profileId, from: this);
@override
String toString() => r'profileProvider';
}
@ProviderFor(profileOverwrite)
const profileOverwriteProvider = ProfileOverwriteFamily._();
final class ProfileOverwriteProvider
extends $FunctionalProvider<Overwrite?, Overwrite?, Overwrite?>
with $Provider<Overwrite?> {
const ProfileOverwriteProvider._({
required ProfileOverwriteFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'profileOverwriteProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileOverwriteHash();
@override
String toString() {
return r'profileOverwriteProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<Overwrite?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Overwrite? create(Ref ref) {
final argument = this.argument as String;
return profileOverwrite(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Overwrite? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Overwrite?>(value),
);
}
@override
bool operator ==(Object other) {
return other is ProfileOverwriteProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$profileOverwriteHash() => r'9d64c5546ff9236c7c9b0d6536bafdb57ffe40a5';
final class ProfileOverwriteFamily extends $Family
with $FunctionalFamilyOverride<Overwrite?, String> {
const ProfileOverwriteFamily._()
: super(
retry: null,
name: r'profileOverwriteProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ProfileOverwriteProvider call(String profileId) =>
ProfileOverwriteProvider._(argument: profileId, from: this);
@override
String toString() => r'profileOverwriteProvider';
}
@ProviderFor(AccessControlState)
const accessControlStateProvider = AccessControlStateProvider._();
final class AccessControlStateProvider
extends $NotifierProvider<AccessControlState, AccessControl> {
const AccessControlStateProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'accessControlStateProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$accessControlStateHash();
@$internal
@override
AccessControlState create() => AccessControlState();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AccessControl value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AccessControl>(value),
);
}
}
String _$accessControlStateHash() =>
r'f7e23637439b8b6c80744d8fa83498edf15acc11';
abstract class _$AccessControlState extends $Notifier<AccessControl> {
AccessControl build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<AccessControl, AccessControl>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AccessControl, AccessControl>,
AccessControl,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
@ProviderFor(setupState)
const setupStateProvider = SetupStateFamily._();
final class SetupStateProvider
extends $FunctionalProvider<SetupState, SetupState, SetupState>
with $Provider<SetupState> {
const SetupStateProvider._({
required SetupStateFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'setupStateProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$setupStateHash();
@override
String toString() {
return r'setupStateProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<SetupState> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
SetupState create(Ref ref) {
final argument = this.argument as String;
return setupState(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(SetupState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<SetupState>(value),
);
}
@override
bool operator ==(Object other) {
return other is SetupStateProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$setupStateHash() => r'f4a7cd47c996bb6de04ee84716d59feca6bb7bc9';
final class SetupStateFamily extends $Family
with $FunctionalFamilyOverride<SetupState, String> {
const SetupStateFamily._()
: super(
retry: null,
name: r'setupStateProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SetupStateProvider call(String profileId) =>
SetupStateProvider._(argument: profileId, from: this);
@override
String toString() => r'setupStateProvider';
}

View File

@@ -23,15 +23,15 @@ Config configState(Ref ref) {
final networkProps = ref.watch(networkSettingProvider);
final vpnProps = ref.watch(vpnSettingProvider);
final proxiesStyle = ref.watch(proxiesStyleSettingProvider);
final scriptProps = ref.watch(scriptStateProvider);
final scripts = ref.watch(scriptsProvider);
final hotKeyActions = ref.watch(hotKeyActionsProvider);
final dav = ref.watch(appDAVSettingProvider);
final windowProps = ref.watch(windowSettingProvider);
final rules = ref.watch(rulesProvider);
return Config(
dav: dav,
windowProps: windowProps,
hotKeyActions: hotKeyActions,
scriptProps: scriptProps,
proxiesStyle: proxiesStyle,
vpnProps: vpnProps,
networkProps: networkProps,
@@ -41,6 +41,8 @@ Config configState(Ref ref) {
appSetting: appSetting,
themeProps: themeProps,
patchClashConfig: patchClashConfig,
scripts: scripts,
rules: rules,
);
}
@@ -169,7 +171,6 @@ VpnState vpnState(Ref ref) {
final stack = ref.watch(
patchClashConfigProvider.select((state) => state.tun.stack),
);
return VpnState(stack: stack, vpnProps: vpnProps);
}
@@ -272,7 +273,9 @@ GroupsState filterGroupsState(Ref ref, String query) {
@riverpod
ProxiesListState proxiesListState(Ref ref) {
final query = ref.watch(queryProvider(QueryTag.proxies));
final query = ref.watch(
queryMapProvider.select((state) => state[QueryTag.proxies] ?? ''),
);
final currentGroups = ref.watch(filterGroupsStateProvider(query));
final currentUnfoldSet = ref.watch(unfoldSetProvider);
final cardType = ref.watch(
@@ -290,7 +293,9 @@ ProxiesListState proxiesListState(Ref ref) {
@riverpod
ProxiesTabState proxiesTabState(Ref ref) {
final query = ref.watch(queryProvider(QueryTag.proxies));
final query = ref.watch(
queryMapProvider.select((state) => state[QueryTag.proxies] ?? ''),
);
final currentGroups = ref.watch(filterGroupsStateProvider(query));
final currentGroupName = ref.watch(
currentProfileProvider.select((state) => state?.currentGroupName),
@@ -575,9 +580,9 @@ ColorScheme genColorScheme(
@riverpod
VM4<String?, String?, Dns?, bool> needSetup(Ref ref) {
final profileId = ref.watch(currentProfileIdProvider);
final content = ref.watch(
scriptStateProvider.select((state) => state.currentScript?.content),
);
// final content = ref.watch(
// scriptsProvider.select((state) => state.currentScript?.content),
// );
final overrideDns = ref.watch(overrideDnsProvider);
final dns = overrideDns == true
? ref.watch(patchClashConfigProvider.select((state) => state.dns))
@@ -585,7 +590,7 @@ VM4<String?, String?, Dns?, bool> needSetup(Ref ref) {
final appendSystemDns = ref.watch(
networkSettingProvider.select((state) => state.appendSystemDns),
);
return VM4(profileId, content, dns, appendSystemDns);
return VM4(profileId, '', dns, appendSystemDns);
}
@riverpod
@@ -644,8 +649,61 @@ AndroidState androidState(Ref ref) {
}
@riverpod
class Query extends _$Query {
class Query extends _$Query with AutoDisposeNotifierMixin {
@override
String build(QueryTag id) =>
ref.watch(queryMapProvider.select((state) => state[id] ?? ''));
String build() => '';
}
@riverpod
double overlayTopOffset(Ref ref) {
final isMobileView = ref.watch(isMobileViewProvider);
final version = ref.watch(versionProvider);
ref.watch(viewSizeProvider);
double top = kHeaderHeight;
if ((version <= 10 || !isMobileView) && system.isMacOS || !system.isDesktop) {
top = 0;
}
return kToolbarHeight + top;
}
@riverpod
class SelectedIds extends _$SelectedIds with AutoDisposeNotifierMixin {
@override
Set<String> build() => {};
}
@riverpod
Profile? profile(Ref ref, String profileId) {
return ref.watch(
profilesProvider.select((state) => state.getProfile(profileId)),
);
}
@riverpod
Overwrite? profileOverwrite(Ref ref, String profileId) {
return ref.watch(
profileProvider(profileId).select((state) => state?.overwrite),
);
}
@riverpod
class AccessControlState extends _$AccessControlState
with AutoDisposeNotifierMixin {
@override
AccessControl build() => AccessControl();
}
@riverpod
SetupState setupState(Ref ref, String profileId) {
ref.watch(
profileProvider(profileId).select(
(state) =>
VM3(a: state?.id, b: state?.lastUpdateDate, c: state?.overwrite),
),
);
ref.watch(patchClashConfigProvider.select((state) => state.dns));
ref.watch(overrideDnsProvider);
ref.watch(scriptsProvider);
ref.watch(rulesProvider);
return globalState.getSetupState(profileId);
}

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' show Pointer;
import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:isolate';
@@ -49,6 +49,8 @@ class GlobalState {
bool isInit = false;
bool isUserDisconnected = false;
bool isService = false;
SetupState? lastSetupState;
VpnState? lastVpnState;
bool get isStart => startTime != null && startTime!.isBeforeNow;
@@ -89,7 +91,7 @@ class GlobalState {
final profileIds = config.profiles.map((item) => item.id);
final providersRootPath = await appPath.getProvidersRootPath();
final profilesRootPath = await appPath.profilesPath;
Isolate.run(() async {
final entities = await Isolate.run<List<FileSystemEntity>>(() async {
final profilesDir = Directory(profilesRootPath);
final providersDir = Directory(providersRootPath);
final List<FileSystemEntity> entities = [];
@@ -103,17 +105,18 @@ class GlobalState {
if (await providersDir.exists()) {
entities.addAll(providersDir.listSync());
}
final deleteFutures = entities.map((entity) async {
if (!profileIds.contains(basenameWithoutExtension(entity.path))) {
final res = await coreController.deleteFile(entity.path);
if (res.isNotEmpty) {
throw res;
}
}
return true;
});
await Future.wait(deleteFutures);
return entities;
});
final deleteFutures = entities.map((entity) async {
if (!profileIds.contains(basenameWithoutExtension(entity.path))) {
final res = await coreController.deleteFile(entity.path);
if (res.isNotEmpty) {
throw res;
}
}
return true;
});
await Future.wait(deleteFutures);
}
Future<void> _initDynamicColor() async {
@@ -188,6 +191,7 @@ class GlobalState {
BuildContext? context,
String? title,
String? confirmText,
String? cancelText,
bool cancelable = true,
bool? dismissible,
}) async {
@@ -204,7 +208,7 @@ class GlobalState {
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(appLocalizations.cancel),
child: Text(cancelText ?? appLocalizations.cancel),
),
TextButton(
onPressed: () {
@@ -253,6 +257,7 @@ class GlobalState {
required Widget child,
BuildContext? context,
bool? dismissible,
bool filter = true,
}) async {
return await showModal<T>(
useRootNavigator: false,
@@ -262,15 +267,15 @@ class GlobalState {
barrierDismissible: dismissible ?? true,
),
builder: (_) => child,
filter: commonFilter,
filter: filter ? commonFilter : null,
);
}
void showNotifier(String text) {
void showNotifier(String text, {MessageActionState? actionState}) {
if (text.isEmpty) {
return;
}
navigatorKey.currentContext?.showNotifier(text);
navigatorKey.currentContext?.showNotifier(text, actionState: actionState);
}
Future<void> openUrl(String url) async {
@@ -302,31 +307,19 @@ class GlobalState {
return params;
}
Future<void> genConfigFile(ClashConfig pathConfig) async {
final configFilePath = await appPath.configFilePath;
var config = {};
Future<Map> getConfigMap(String profileId) async {
var res = {};
try {
config = await patchRawConfig(patchConfig: pathConfig);
final setupState = globalState.getSetupState(profileId);
res = await makeRealConfig(
setupState: setupState,
patchConfig: config.patchClashConfig,
);
} catch (e) {
globalState.showNotifier(e.toString());
config = {};
}
final res = await Isolate.run<String>(() async {
try {
final res = json.encode(config);
final file = File(configFilePath);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(res);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
res = {};
}
return res;
}
Future<void> genValidateFile(String path, String data) async {
@@ -374,144 +367,280 @@ class GlobalState {
);
}
Future<Map<String, dynamic>> patchRawConfig({
Future<String> setupConfig({
required SetupState setupState,
required ClashConfig patchConfig,
VoidCallback? preloadInvoke,
}) async {
final config = await makeRealConfig(
setupState: setupState,
patchConfig: patchConfig,
);
final configFilePath = await appPath.configFilePath;
final res = await Isolate.run<String>(() async {
try {
final res = yaml.encode(config);
final file = File(configFilePath);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(res);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
}
final params = await globalState.getSetupParams();
return await coreController.setupConfig(
params: params,
setupState: setupState,
preloadInvoke: preloadInvoke,
);
}
Future<Map<String, dynamic>> makeRealConfig({
required SetupState setupState,
required ClashConfig patchConfig,
}) async {
final profile = config.currentProfile;
if (profile == null) {
final profileId = setupState.profileId;
if (profileId?.isNotEmpty != true) {
return {};
}
final profileId = profile.id;
final configMap = await getProfileConfig(profileId);
final rawConfig = await handleEvaluate(configMap);
final configMap = await getProfileConfig(profileId!);
String? scriptContent;
final List<Rule> addedRules = [];
if (setupState.overwriteType == OverwriteType.script) {
scriptContent = setupState.scriptContent;
} else {
addedRules.addAll(setupState.addedRules);
}
final defaultUA = packageInfo.ua;
final appendSystemDns = config.networkProps.appendSystemDns;
final realPatchConfig = patchConfig.copyWith(
tun: patchConfig.tun.getRealTun(config.networkProps.routeMode),
);
rawConfig['external-controller'] = realPatchConfig.externalController.value;
rawConfig['external-ui'] = '';
rawConfig['interface-name'] = '';
rawConfig['external-ui-url'] = '';
rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent;
rawConfig['unified-delay'] = realPatchConfig.unifiedDelay;
rawConfig['ipv6'] = realPatchConfig.ipv6;
rawConfig['log-level'] = realPatchConfig.logLevel.name;
rawConfig['port'] = 0;
rawConfig['socks-port'] = 0;
rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval;
rawConfig['mixed-port'] = realPatchConfig.mixedPort;
rawConfig['port'] = realPatchConfig.port;
rawConfig['socks-port'] = realPatchConfig.socksPort;
rawConfig['redir-port'] = realPatchConfig.redirPort;
rawConfig['tproxy-port'] = realPatchConfig.tproxyPort;
rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name;
rawConfig['allow-lan'] = realPatchConfig.allowLan;
rawConfig['mode'] = realPatchConfig.mode.name;
if (rawConfig['tun'] == null) {
rawConfig['tun'] = {};
}
rawConfig['tun']['enable'] = realPatchConfig.tun.enable;
rawConfig['tun']['device'] = realPatchConfig.tun.device;
rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack;
rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name;
rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress;
rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute;
rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name;
if (rawConfig['sniffer']?['sniff'] != null) {
for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) {
if (value['ports'] != null && value['ports'] is List) {
value['ports'] =
value['ports']?.map((item) => item.toString()).toList() ?? [];
}
}
}
if (rawConfig['profile'] == null) {
rawConfig['profile'] = {};
}
if (rawConfig['proxy-providers'] != null) {
final proxyProviders = rawConfig['proxy-providers'] as Map;
for (final key in proxyProviders.keys) {
final proxyProvider = proxyProviders[key];
if (proxyProvider['type'] != 'http') {
continue;
}
if (proxyProvider['url'] != null) {
proxyProvider['path'] = await appPath.getProvidersFilePath(
profile.id,
'proxies',
proxyProvider['url'],
);
}
}
}
if (rawConfig['rule-providers'] != null) {
final ruleProviders = rawConfig['rule-providers'] as Map;
for (final key in ruleProviders.keys) {
final ruleProvider = ruleProviders[key];
if (ruleProvider['type'] != 'http') {
continue;
}
if (ruleProvider['url'] != null) {
ruleProvider['path'] = await appPath.getProvidersFilePath(
profile.id,
'rules',
ruleProvider['url'],
);
}
}
}
rawConfig['profile']['store-selected'] = false;
rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
rawConfig['global-ua'] = realPatchConfig.globalUa;
if (rawConfig['hosts'] == null) {
rawConfig['hosts'] = {};
}
for (final host in realPatchConfig.hosts.entries) {
rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators;
}
if (rawConfig['dns'] == null) {
rawConfig['dns'] = {};
}
final isEnableDns = rawConfig['dns']['enable'] == true;
final overrideDns = globalState.config.overrideDns;
final systemDns = 'system://';
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, systemDns],
),
false => realPatchConfig.dns,
};
rawConfig['dns'] = dns.toJson();
rawConfig['dns']['nameserver-policy'] = {};
for (final entry in dns.nameserverPolicy.entries) {
rawConfig['dns']['nameserver-policy'][entry.key] =
entry.value.splitByMultipleSeparators;
}
Map<String, dynamic> rawConfig = configMap;
if (scriptContent?.isNotEmpty == true) {
rawConfig = await handleEvaluate(scriptContent!, rawConfig);
}
if (config.networkProps.appendSystemDns) {
final List<dynamic> nameserver = rawConfig['dns']['nameserver'] ?? [];
if (!nameserver.contains(systemDns)) {
rawConfig['dns']['nameserver'] = [...nameserver, systemDns];
}
final directory = await appPath.profilesPath;
String getProvidersFilePathInner(String type, String url) {
return join(directory, 'providers', profileId, type, url.toMd5());
}
List rules = [];
if (rawConfig['rules'] != null) {
rules = rawConfig['rules'];
}
rawConfig.remove('rules');
final overrideData = profile.overrideData;
if (overrideData.enable && config.scriptProps.currentScript == null) {
if (overrideData.rule.type == OverrideRuleType.override) {
rules = overrideData.runningRule;
} else {
rules = [...overrideData.runningRule, ...rules];
final res = await Isolate.run<Map<String, dynamic>>(() async {
rawConfig['external-controller'] =
realPatchConfig.externalController.value;
rawConfig['external-ui'] = '';
rawConfig['interface-name'] = '';
rawConfig['external-ui-url'] = '';
rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent;
rawConfig['unified-delay'] = realPatchConfig.unifiedDelay;
rawConfig['ipv6'] = realPatchConfig.ipv6;
rawConfig['log-level'] = realPatchConfig.logLevel.name;
rawConfig['port'] = 0;
rawConfig['socks-port'] = 0;
rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval;
rawConfig['mixed-port'] = realPatchConfig.mixedPort;
rawConfig['port'] = realPatchConfig.port;
rawConfig['socks-port'] = realPatchConfig.socksPort;
rawConfig['redir-port'] = realPatchConfig.redirPort;
rawConfig['tproxy-port'] = realPatchConfig.tproxyPort;
rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name;
rawConfig['allow-lan'] = realPatchConfig.allowLan;
rawConfig['mode'] = realPatchConfig.mode.name;
if (rawConfig['tun'] == null) {
rawConfig['tun'] = {};
}
rawConfig['tun']['enable'] = realPatchConfig.tun.enable;
rawConfig['tun']['device'] = realPatchConfig.tun.device;
rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack;
rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name;
rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress;
rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute;
rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name;
if (rawConfig['sniffer']?['sniff'] != null) {
for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) {
if (value['ports'] != null && value['ports'] is List) {
value['ports'] =
value['ports']?.map((item) => item.toString()).toList() ?? [];
}
}
}
if (rawConfig['profile'] == null) {
rawConfig['profile'] = {};
}
if (rawConfig['proxy-providers'] != null) {
final proxyProviders = rawConfig['proxy-providers'] as Map;
for (final key in proxyProviders.keys) {
final proxyProvider = proxyProviders[key];
if (proxyProvider['type'] != 'http') {
continue;
}
if (proxyProvider['url'] != null) {
proxyProvider['path'] = getProvidersFilePathInner(
'proxies',
proxyProvider['url'],
);
}
}
}
if (rawConfig['rule-providers'] != null) {
final ruleProviders = rawConfig['rule-providers'] as Map;
for (final key in ruleProviders.keys) {
final ruleProvider = ruleProviders[key];
if (ruleProvider['type'] != 'http') {
continue;
}
if (ruleProvider['url'] != null) {
ruleProvider['path'] = getProvidersFilePathInner(
'rules',
ruleProvider['url'],
);
}
}
}
rawConfig['profile']['store-selected'] = false;
rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA;
if (rawConfig['hosts'] == null) {
rawConfig['hosts'] = {};
}
for (final host in realPatchConfig.hosts.entries) {
rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators;
}
if (rawConfig['dns'] == null) {
rawConfig['dns'] = {};
}
final isEnableDns = rawConfig['dns']['enable'] == true;
final systemDns = 'system://';
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, systemDns],
),
false => realPatchConfig.dns,
};
rawConfig['dns'] = dns.toJson();
rawConfig['dns']['nameserver-policy'] = {};
for (final entry in dns.nameserverPolicy.entries) {
rawConfig['dns']['nameserver-policy'][entry.key] =
entry.value.splitByMultipleSeparators;
}
}
if (appendSystemDns) {
final List<String> nameserver = List<String>.from(
rawConfig['dns']['nameserver'] ?? [],
);
if (!nameserver.contains(systemDns)) {
rawConfig['dns']['nameserver'] = [...nameserver, systemDns];
}
}
List<String> rules = [];
if (rawConfig['rules'] != null) {
rules = List<String>.from(rawConfig['rules']);
}
rawConfig.remove('rules');
if (addedRules.isNotEmpty) {
final parsedNewRules = addedRules
.map((item) => ParsedRule.parseString(item.value))
.toList();
final hasMatchPlaceholder = parsedNewRules.any(
(item) => item.ruleTarget?.toUpperCase() == 'MATCH',
);
String? replacementTarget;
if (hasMatchPlaceholder) {
for (int i = rules.length - 1; i >= 0; i--) {
final parsed = ParsedRule.parseString(rules[i]);
if (parsed.ruleAction == RuleAction.MATCH) {
final target = parsed.ruleTarget;
if (target != null && target.isNotEmpty) {
replacementTarget = target;
break;
}
}
}
}
final List<String> finalAddedRules;
if (replacementTarget?.isNotEmpty == true) {
finalAddedRules = [];
for (int i = 0; i < parsedNewRules.length; i++) {
final parsed = parsedNewRules[i];
if (parsed.ruleTarget?.toUpperCase() == 'MATCH') {
finalAddedRules.add(
parsed.copyWith(ruleTarget: replacementTarget).value,
);
} else {
finalAddedRules.add(addedRules[i].value);
}
}
} else {
finalAddedRules = addedRules.map((e) => e.value).toList();
}
rules = [...finalAddedRules, ...rules];
}
rawConfig['rules'] = rules;
return rawConfig;
});
return res;
}
Future<Map<String, dynamic>> handleEvaluate(
String scriptContent,
Map<String, dynamic> config,
) async {
if (config['proxy-providers'] == null) {
config['proxy-providers'] = {};
}
rawConfig['rule'] = rules;
return rawConfig;
final configJs = json.encode(config);
final runtime = getJavascriptRuntime();
final res = await runtime.evaluateAsync('''
$scriptContent
main($configJs)
''');
if (res.isError) {
throw res.stringResult;
}
final value = switch (res.rawResult is ffi.Pointer) {
true => runtime.convertValue<Map<String, dynamic>>(res),
false => Map<String, dynamic>.from(res.rawResult),
};
return value ?? config;
}
SetupState getSetupState(String? profileId) {
final profile = config.profiles.getProfile(profileId);
final profileState = VM3(
a: profile?.id,
b: profile?.lastUpdateDate,
c: profile?.overwrite,
);
final overwrite = profileState.c;
final scriptContent = config.scripts
.get(overwrite?.scriptOverwrite.scriptId)
?.content;
final standardOverwrite =
overwrite?.standardOverwrite ?? StandardOverwrite();
final rules = config.rules;
final globalAddedRules = rules.where(
(item) => !standardOverwrite.disabledRuleIds.contains(item.id),
);
final addedRules = [...standardOverwrite.addedRules, ...globalAddedRules];
return SetupState(
profileId: profileId,
profileLastUpdateDate: profile?.lastUpdateDate?.millisecondsSinceEpoch,
overwriteType: profile?.overwrite.type ?? OverwriteType.standard,
addedRules: addedRules,
scriptContent: scriptContent,
overrideDns: config.overrideDns,
dns: config.patchClashConfig.dns,
);
}
Future<Map<String, dynamic>> getProfileConfig(String profileId) async {
@@ -520,32 +649,6 @@ class GlobalState {
configMap.remove('rule');
return configMap;
}
Future<Map<String, dynamic>> handleEvaluate(
Map<String, dynamic> config,
) async {
final currentScript = globalState.config.scriptProps.currentScript;
if (currentScript == null) {
return config;
}
if (config['proxy-providers'] == null) {
config['proxy-providers'] = {};
}
final configJs = json.encode(config);
final runtime = getJavascriptRuntime();
final res = await runtime.evaluateAsync('''
${currentScript.content}
main($configJs)
''');
if (res.isError) {
throw res.stringResult;
}
final value = switch (res.rawResult is Pointer) {
true => runtime.convertValue<Map<String, dynamic>>(res),
false => Map<String, dynamic>.from(res.rawResult),
};
return value ?? config;
}
}
final globalState = GlobalState();

View File

@@ -4,6 +4,7 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -24,17 +25,12 @@ class AboutView extends StatelessWidget {
const AboutView({super.key});
Future<void> _checkUpdate(BuildContext context) async {
final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return;
final data = await globalState.appController.safeRun<Map<String, dynamic>?>(
request.checkForUpdate,
title: appLocalizations.checkUpdate,
needLoading: true,
);
globalState.appController.checkUpdateResultHandle(
data: data,
handleError: true,
);
globalState.appController.checkUpdateResultHandle(data: data, isUser: true);
}
List<Widget> _buildMoreSection(BuildContext context) {
@@ -170,9 +166,12 @@ class AboutView extends StatelessWidget {
..._buildContributorsSection(),
..._buildMoreSection(context),
];
return Padding(
padding: kMaterialListPadding.copyWith(top: 16, bottom: 16),
child: generateListView(items),
return BaseScaffold(
title: appLocalizations.about,
body: Padding(
padding: kMaterialListPadding.copyWith(top: 16, bottom: 16),
child: generateListView(items),
),
);
}
}

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
@@ -20,17 +19,26 @@ class AccessView extends ConsumerStatefulWidget {
}
class _AccessViewState extends ConsumerState<AccessView> {
List<String> acceptList = [];
List<String> rejectList = [];
final GlobalKey<CommonScaffoldState> _scaffoldKey = GlobalKey();
late ScrollController _controller;
List<String>? _pinedList;
bool _isInit = false;
AccessControlMode? _lastMode;
final _completer = Completer();
@override
void initState() {
super.initState();
_updateInitList();
_controller = ScrollController();
_completer.complete(globalState.appController.getPackages());
WidgetsBinding.instance.addPostFrameCallback((_) {
final accessControl = ref.read(
vpnSettingProvider.select((state) => state.accessControl),
);
ref.read(accessControlStateProvider.notifier).value = accessControl;
_isInit = true;
});
}
@override
@@ -39,65 +47,37 @@ class _AccessViewState extends ConsumerState<AccessView> {
super.dispose();
}
void _updateInitList() {
acceptList = globalState.config.vpnProps.accessControl.acceptList;
rejectList = globalState.config.vpnProps.accessControl.rejectList;
}
Widget _buildSearchButton() {
return IconButton(
tooltip: appLocalizations.search,
onPressed: () {
showSearch(
context: context,
delegate: AccessControlSearchDelegate(
acceptList: acceptList,
rejectList: rejectList,
),
).then(
(_) => setState(() {
_updateInitList();
}),
);
},
icon: const Icon(Icons.search),
);
}
Widget _buildSelectedAllButton({
required bool isSelectedAll,
required List<String> allValueList,
}) {
onPressed() {
ref.read(vpnSettingProvider.notifier).updateState((state) {
final isAccept =
state.accessControl.mode == AccessControlMode.acceptSelected;
ref.read(accessControlStateProvider.notifier).update((state) {
final newSet = Set<String>.from(state.currentList);
final isSelectedAll = newSet.containsAll(allValueList);
if (isSelectedAll) {
return switch (isAccept) {
true => state.copyWith.accessControl(acceptList: []),
false => state.copyWith.accessControl(rejectList: []),
};
newSet.removeAll(allValueList);
} else {
return switch (isAccept) {
true => state.copyWith.accessControl(acceptList: allValueList),
false => state.copyWith.accessControl(rejectList: allValueList),
};
newSet.addAll(allValueList);
}
return state.copyWithNewList(newSet.toList());
});
}
return FadeRotationScaleBox(
alignment: Alignment.centerRight,
child: isSelectedAll
? IconButton(
? FloatingActionButton.extended(
key: ValueKey(true),
tooltip: appLocalizations.cancelSelectAll,
onPressed: onPressed,
label: Text(appLocalizations.cancelSelectAll),
icon: const Icon(Icons.deselect),
)
: IconButton(
: FloatingActionButton.extended(
key: ValueKey(false),
tooltip: appLocalizations.selectAll,
onPressed: onPressed,
label: Text(appLocalizations.selectAll),
icon: const Icon(Icons.select_all),
),
);
@@ -105,12 +85,11 @@ class _AccessViewState extends ConsumerState<AccessView> {
Future<void> _intelligentSelected() async {
final packageNames = ref.read(
packageListSelectorStateProvider.select(
(state) => state.list.map((item) => item.packageName),
),
packagesProvider.select((state) => state.map((item) => item.packageName)),
);
final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return;
if (packageNames.isEmpty) {
return;
}
final selectedPackageNames =
(await globalState.appController.safeRun<List<String>>(
needLoading: true,
@@ -126,211 +105,232 @@ class _AccessViewState extends ConsumerState<AccessView> {
.where((item) => selectedPackageNames.contains(item))
.toList();
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => state.copyWith.accessControl(
acceptList: acceptList,
rejectList: rejectList,
),
.read(accessControlStateProvider.notifier)
.update(
(state) =>
state.copyWith(acceptList: acceptList, rejectList: rejectList),
);
}
Widget _buildSettingButton() {
return IconButton(
onPressed: () async {
final res = await showSheet<int>(
context: context,
props: SheetProps(isScrollControlled: true),
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: AccessControlPanel(),
title: appLocalizations.proxiesSetting,
);
},
Future<void> _handleToSetting() async {
await showSheet<int>(
context: context,
props: SheetProps(isScrollControlled: true),
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: AccessControlPanel(),
title: appLocalizations.accessControlSettings,
);
if (res == 1) {
_intelligentSelected();
}
},
icon: const Icon(Icons.tune),
);
}
void _handleSelected(List<String> valueList, Package package, bool? value) {
if (value == true) {
valueList.add(package.packageName);
} else {
valueList.remove(package.packageName);
}
ref.read(vpnSettingProvider.notifier).updateState((state) {
return switch (state.accessControl.mode ==
AccessControlMode.acceptSelected) {
true => state.copyWith.accessControl(acceptList: valueList),
false => state.copyWith.accessControl(rejectList: valueList),
};
void _handleSelected(String packageName) {
ref.read(accessControlStateProvider.notifier).update((state) {
final newSet = Set<String>.from(state.currentList)
..addOrRemove(packageName);
return state.copyWithNewList(newSet.toList());
});
}
@override
Widget build(BuildContext context) {
final state = ref.watch(packageListSelectorStateProvider);
final accessControl = state.accessControl;
final accessControlMode = accessControl.mode;
final packages = state.getSortList(
accessControlMode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
void _handleToggle() {
ref.read(accessControlStateProvider.notifier).update((state) {
return state.copyWith(enable: !state.enable);
});
}
void _handleSearch() {
_scaffoldKey.currentState?.handleToSearch();
}
Future<void> _handleBack() async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.saveChanges),
);
final currentList = accessControl.currentList;
final packageNameList = packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
final describe = accessControlMode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc;
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
flex: 0,
child: ListItem.switchItem(
title: Text(appLocalizations.appAccessControl),
delegate: SwitchDelegate(
value: accessControl.enable,
onChanged: (enable) {
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => state.copyWith.accessControl(enable: enable),
);
},
),
if (res == true) {
_handleSave();
}
if (mounted) {
Navigator.of(context).pop();
}
}
void _handleSave() {
final accessControl = ref.read(accessControlStateProvider);
ref
.read(vpnSettingProvider.notifier)
.update((state) => state.copyWith(accessControl: accessControl));
}
Widget _buildConfirm() {
return Consumer(
builder: (_, ref, child) {
final accessControl = ref.watch(accessControlStateProvider);
final noSave = ref.watch(
vpnSettingProvider.select(
(state) => state.accessControl == accessControl,
),
);
if (noSave) {
return SizedBox();
}
return child!;
},
child: CommonPopScope(
onPop: (_) {
_handleBack();
return false;
},
child: CommonMinFilledButtonTheme(
child: FilledButton.tonal(
onPressed: _handleSave,
child: Text(context.appLocalizations.save),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Divider(height: 12),
),
);
}
Future<void> _exportToClipboard() async {
await globalState.appController.safeRun(() {
final currentList = ref.read(
accessControlStateProvider.select((state) => state.currentList),
);
Clipboard.setData(ClipboardData(text: currentList.join('\n')));
});
}
Future<void> _importFormClipboard() async {
await globalState.appController.safeRun(() async {
final data = await Clipboard.getData('text/plain');
final text = data?.text;
if (text == null) return;
final list = text.split('\n');
ref
.read(accessControlStateProvider.notifier)
.update((state) => state.copyWithNewList(list.toSet().toList()));
});
}
List<Widget> _buildActions({required bool enable}) {
return [
_buildConfirm(),
CommonPopupBox(
targetBuilder: (open) {
return IconButton(
onPressed: () {
open(offset: Offset(0, 0));
},
icon: Icon(Icons.more_vert),
);
},
popup: CommonPopupMenu(
items: [
PopupMenuItemData(
icon: Icons.swap_horiz,
label: enable
? appLocalizations.turnOff
: appLocalizations.turnOn,
onPressed: _handleToggle,
),
PopupMenuItemData(
icon: Icons.search,
label: appLocalizations.search,
onPressed: _handleSearch,
),
PopupMenuItemData(
icon: Icons.tune,
label: appLocalizations.settings,
onPressed: _handleToSetting,
),
PopupMenuItemData(
icon: Icons.emergency_outlined,
label: appLocalizations.action,
subItems: [
PopupMenuItemData(
icon: Icons.auto_awesome,
label: appLocalizations.intelligentSelected,
onPressed: _intelligentSelected,
),
PopupMenuItemData(
icon: Icons.content_copy,
label: appLocalizations.clipboardExport,
onPressed: _exportToClipboard,
),
PopupMenuItemData(
icon: Icons.paste,
label: appLocalizations.clipboardImport,
onPressed: _importFormClipboard,
),
],
),
],
),
Flexible(
child: DisabledMask(
status: !accessControl.enable,
child: Column(
),
];
}
Widget _buildContent({
required List<Package> packages,
required List<String> valueList,
}) {
return FutureBuilder(
future: _completer.future,
builder: (_, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());
}
return packages.isEmpty
? NullStatus(label: appLocalizations.noData)
: CommonScrollBar(
controller: _controller,
child: ListView.builder(
controller: _controller,
itemCount: packages.length,
itemExtent: 72,
itemBuilder: (_, index) {
final package = packages[index];
return PackageListItem(
key: Key(package.packageName),
package: package,
value: valueList.contains(package.packageName),
onChanged: (value) {
_handleSelected(package.packageName);
},
);
},
),
);
},
);
}
Widget _buildBannerBar(AccessControlMode mode, int count) {
final describe = mode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc;
final textStyle = context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.onPrimary,
);
return MaterialBanner(
content: Text(describe),
actions: [
Card.filled(
color: context.colorScheme.primary,
elevation: 0,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(14),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ActivateBox(
active: accessControl.enable,
child: Padding(
padding: const EdgeInsets.only(
top: 4,
bottom: 4,
left: 16,
right: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Text(
appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(
context,
).colorScheme.primary,
),
),
),
const Flexible(child: SizedBox(width: 8)),
Flexible(
child: Text(
'${valueList.length}',
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(
context,
).colorScheme.primary,
),
),
),
],
),
),
Flexible(child: Text(describe)),
],
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(child: _buildSearchButton()),
Flexible(
child: _buildSelectedAllButton(
isSelectedAll:
valueList.length == packageNameList.length,
allValueList: packageNameList,
),
),
Flexible(child: _buildSettingButton()),
],
),
],
),
),
),
Expanded(
flex: 1,
child: FutureBuilder(
future: _completer.future,
builder: (_, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());
}
return packages.isEmpty
? NullStatus(label: appLocalizations.noData)
: CommonScrollBar(
controller: _controller,
child: ListView.builder(
controller: _controller,
itemCount: packages.length,
itemExtent: 72,
itemBuilder: (_, index) {
final package = packages[index];
return PackageListItem(
key: Key(package.packageName),
package: package,
value: valueList.contains(
package.packageName,
),
isActive: accessControl.enable,
onChanged: (value) {
_handleSelected(
valueList,
package,
value,
);
},
);
},
),
);
},
),
),
Text(appLocalizations.selected, style: textStyle),
SizedBox(width: 4),
Flexible(child: Text('$count', style: textStyle)),
],
),
),
@@ -338,180 +338,115 @@ class _AccessViewState extends ConsumerState<AccessView> {
],
);
}
void _onSearch(String value) {
ref.read(queryProvider.notifier).value = value;
_pinedList = null;
}
@override
Widget build(BuildContext context) {
final query = ref.watch(
queryProvider.select((state) => state.toLowerCase()),
);
final packages = ref.watch(packagesProvider);
final accessControl = ref.watch(accessControlStateProvider);
if (_isInit) {
if (_lastMode != accessControl.mode) {
_lastMode = accessControl.mode;
_pinedList = accessControl.currentList;
} else {
_pinedList ??= accessControl.currentList;
}
}
final packagesSorted = packages
.getSortList(pinedList: _pinedList ?? [], sortType: accessControl.sort)
.where(
(package) =>
package.label.toLowerCase().contains(query) ||
package.packageName.contains(query),
)
.toList();
final mode = accessControl.mode;
final currentList = accessControl.currentList;
final packageNameList = packagesSorted.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return CommonScaffold(
key: _scaffoldKey,
searchState: AppBarSearchState(onSearch: _onSearch, autoAddSearch: false),
title: appLocalizations.appAccessControl,
actions: _buildActions(enable: accessControl.enable),
body: DisabledMask(
status: !accessControl.enable,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildBannerBar(mode, valueList.length),
SizedBox(height: 8),
Expanded(
child: _buildContent(
packages: packagesSorted,
valueList: valueList,
),
),
],
),
),
floatingActionButton: _buildSelectedAllButton(
isSelectedAll: valueList.length == packageNameList.length,
allValueList: packageNameList,
),
);
}
}
class PackageListItem extends StatelessWidget {
final Package package;
final bool value;
final bool isActive;
final void Function(bool?) onChanged;
const PackageListItem({
super.key,
required this.package,
required this.value,
required this.isActive,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return FadeScaleEnterBox(
child: ActivateBox(
active: isActive,
child: ListItem.checkbox(
leading: SizedBox(
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: app?.getPackageIcon(package.packageName),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
),
title: Text(
package.label,
style: const TextStyle(overflow: TextOverflow.ellipsis),
maxLines: 1,
),
subtitle: Text(
package.packageName,
style: const TextStyle(overflow: TextOverflow.ellipsis),
maxLines: 1,
),
delegate: CheckboxDelegate(value: value, onChanged: onChanged),
return ListItem.checkbox(
leading: SizedBox(
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: app?.getPackageIcon(package.packageName),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
),
);
}
}
class AccessControlSearchDelegate extends SearchDelegate {
List<String> acceptList = [];
List<String> rejectList = [];
AccessControlSearchDelegate({
required this.acceptList,
required this.rejectList,
});
@override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
return;
}
query = '';
},
icon: const Icon(Icons.clear),
title: Text(
package.label,
style: const TextStyle(overflow: TextOverflow.ellipsis),
maxLines: 1,
),
const SizedBox(width: 8),
];
}
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
subtitle: Text(
package.packageName,
style: const TextStyle(overflow: TextOverflow.ellipsis),
maxLines: 1,
),
delegate: CheckboxDelegate(value: value, onChanged: onChanged),
);
}
void _handleSelected(
WidgetRef ref,
List<String> valueList,
Package package,
bool? value,
) {
if (value == true) {
valueList.add(package.packageName);
} else {
valueList.remove(package.packageName);
}
ref.read(vpnSettingProvider.notifier).updateState((state) {
return switch (state.accessControl.mode ==
AccessControlMode.acceptSelected) {
true => state.copyWith.accessControl(acceptList: valueList),
false => state.copyWith.accessControl(rejectList: valueList),
};
});
}
Widget _packageList() {
final lowQuery = query.toLowerCase();
return Consumer(
builder: (context, ref, _) {
final vm3 = ref.watch(
packageListSelectorStateProvider.select(
(state) => VM3(
a: state.getSortList(
state.accessControl.mode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
),
b: state.accessControl.enable,
c: state.accessControl.currentList,
),
),
);
final packages = vm3.a;
final queryPackages = packages
.where(
(package) =>
package.label.toLowerCase().contains(lowQuery) ||
package.packageName.contains(lowQuery),
)
.toList();
final isAccessControl = vm3.b;
final currentList = vm3.c;
final packageNameList = packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return DisabledMask(
status: !isAccessControl,
child: ListView.builder(
itemCount: queryPackages.length,
itemBuilder: (_, index) {
final package = queryPackages[index];
return PackageListItem(
key: Key(package.packageName),
package: package,
value: valueList.contains(package.packageName),
isActive: isAccessControl,
onChanged: (value) {
_handleSelected(ref, valueList, package, value);
},
);
},
),
);
},
);
}
@override
Widget buildResults(BuildContext context) {
return buildSuggestions(context);
}
@override
Widget buildSuggestions(BuildContext context) {
return _packageList();
}
}
class AccessControlPanel extends ConsumerStatefulWidget {
@@ -554,6 +489,7 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
List<Widget> _buildModeSetting() {
return generateSection(
isFirst: true,
title: appLocalizations.mode,
items: [
SingleChildScrollView(
@@ -562,7 +498,7 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
child: Consumer(
builder: (_, ref, _) {
final accessControlMode = ref.watch(
vpnSettingProvider.select((state) => state.accessControl.mode),
accessControlStateProvider.select((state) => state.mode),
);
return Wrap(
spacing: 16,
@@ -576,11 +512,8 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
isSelected: accessControlMode == item,
onPressed: () {
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith.accessControl(mode: item),
);
.read(accessControlStateProvider.notifier)
.update((state) => state.copyWith(mode: item));
},
),
],
@@ -602,7 +535,7 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
child: Consumer(
builder: (_, ref, _) {
final accessSortType = ref.watch(
vpnSettingProvider.select((state) => state.accessControl.sort),
accessControlStateProvider.select((state) => state.sort),
);
return Wrap(
spacing: 16,
@@ -616,11 +549,8 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
isSelected: accessSortType == item,
onPressed: () {
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith.accessControl(sort: item),
);
.read(accessControlStateProvider.notifier)
.update((state) => state.copyWith(sort: item));
},
),
],
@@ -642,10 +572,10 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
child: Consumer(
builder: (_, ref, _) {
final vm2 = ref.watch(
vpnSettingProvider.select(
accessControlStateProvider.select(
(state) => VM2(
a: state.accessControl.isFilterSystemApp,
b: state.accessControl.isFilterNonInternetApp,
a: state.isFilterSystemApp,
b: state.isFilterNonInternetApp,
),
),
);
@@ -657,11 +587,10 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
isSelected: vm2.a == false,
onPressed: () {
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => state.copyWith.accessControl(
isFilterSystemApp: !vm2.a,
),
.read(accessControlStateProvider.notifier)
.update(
(state) =>
state.copyWith(isFilterSystemApp: !vm2.a),
);
},
),
@@ -670,11 +599,10 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
isSelected: vm2.b == false,
onPressed: () {
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => state.copyWith.accessControl(
isFilterNonInternetApp: !vm2.b,
),
.read(accessControlStateProvider.notifier)
.update(
(state) =>
state.copyWith(isFilterNonInternetApp: !vm2.b),
);
},
),
@@ -687,66 +615,6 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
);
}
Future<void> _copyToClipboard() async {
await globalState.appController.safeRun(() {
final data = globalState.config.vpnProps.accessControl.toJson();
Clipboard.setData(ClipboardData(text: json.encode(data)));
});
if (!mounted) return;
Navigator.of(context).pop();
}
Future<void> _pasteToClipboard() async {
await globalState.appController.safeRun(() async {
final data = await Clipboard.getData('text/plain');
final text = data?.text;
if (text == null) return;
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => state.copyWith(
accessControl: AccessControl.fromJson(json.decode(text)),
),
);
});
if (!mounted) return;
Navigator.of(context).pop();
}
List<Widget> _buildActionSetting() {
return generateSection(
title: appLocalizations.action,
items: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
runSpacing: 16,
spacing: 16,
children: [
CommonChip(
avatar: const Icon(Icons.auto_awesome),
label: appLocalizations.intelligentSelected,
onPressed: () {
Navigator.of(context).pop(1);
},
),
CommonChip(
avatar: const Icon(Icons.paste),
label: appLocalizations.clipboardImport,
onPressed: _pasteToClipboard,
),
CommonChip(
avatar: const Icon(Icons.content_copy),
label: appLocalizations.clipboardExport,
onPressed: _copyToClipboard,
),
],
),
),
],
);
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -759,7 +627,6 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
..._buildModeSetting(),
..._buildSortSetting(),
..._buildSourceSetting(),
..._buildActionSetting(),
],
),
),

View File

@@ -282,15 +282,18 @@ class ApplicationSettingView extends StatelessWidget {
if (system.isAndroid) CrashlyticsItem(),
AutoCheckUpdateItem(),
];
return ListView.separated(
itemBuilder: (_, index) {
final item = items[index];
return item;
},
separatorBuilder: (_, _) {
return const Divider(height: 0);
},
itemCount: items.length,
return BaseScaffold(
title: appLocalizations.application,
body: ListView.separated(
itemBuilder: (_, index) {
final item = items[index];
return item;
},
separatorBuilder: (_, _) {
return const Divider(height: 0);
},
itemCount: items.length,
),
);
}
}

View File

@@ -10,6 +10,7 @@ import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -156,141 +157,144 @@ class BackupAndRecovery extends ConsumerWidget {
Widget build(BuildContext context, ref) {
final dav = ref.watch(appDAVSettingProvider);
final client = dav != null ? DAVClient(dav) : null;
return ListView(
children: [
ListHeader(title: appLocalizations.remote),
if (dav == null)
ListItem(
leading: const Icon(Icons.account_box),
title: Text(appLocalizations.noInfo),
subtitle: Text(appLocalizations.pleaseBindWebDAV),
trailing: FilledButton.tonal(
onPressed: () {
_showAddWebDAV(dav);
},
child: Text(appLocalizations.bind),
),
)
else ...[
ListItem(
leading: const Icon(Icons.account_box),
title: TooltipText(
text: Text(
dav.user,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(appLocalizations.connectivity),
FutureBuilder<bool>(
future: client!.pingCompleter.future,
builder: (_, snapshot) {
return Center(
child: FadeThroughBox(
child:
snapshot.connectionState != ConnectionState.done
? const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 1,
),
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
),
),
trailing: FilledButton.tonal(
onPressed: () {
_showAddWebDAV(dav);
},
child: Text(appLocalizations.edit),
),
),
const SizedBox(height: 4),
ListItem.input(
title: Text(appLocalizations.file),
subtitle: Text(dav.fileName),
delegate: InputDelegate(
title: appLocalizations.file,
value: dav.fileName,
resetValue: defaultDavFileName,
onChanged: (value) {
_handleChange(value, ref);
},
),
),
ListItem(
onTap: () {
_backupOnWebDAV(client);
},
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.remoteBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnWebDAV(context, client);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.remoteRecoveryDesc),
),
],
ListHeader(title: appLocalizations.local),
ListItem(
onTap: () {
_backupOnLocal(context);
},
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.localBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnLocal(context);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.localRecoveryDesc),
),
ListHeader(title: appLocalizations.options),
Consumer(
builder: (_, ref, _) {
final recoveryStrategy = ref.watch(
appSettingProvider.select((state) => state.recoveryStrategy),
);
return ListItem(
onTap: () {
_handleUpdateRecoveryStrategy(ref);
},
title: Text(appLocalizations.recoveryStrategy),
trailing: FilledButton(
return BaseScaffold(
title: appLocalizations.backupAndRecovery,
body: ListView(
children: [
ListHeader(title: appLocalizations.remote),
if (dav == null)
ListItem(
leading: const Icon(Icons.account_box),
title: Text(appLocalizations.noInfo),
subtitle: Text(appLocalizations.pleaseBindWebDAV),
trailing: FilledButton.tonal(
onPressed: () {
_handleUpdateRecoveryStrategy(ref);
_showAddWebDAV(dav);
},
child: Text(
Intl.message('recoveryStrategy_${recoveryStrategy.name}'),
child: Text(appLocalizations.bind),
),
)
else ...[
ListItem(
leading: const Icon(Icons.account_box),
title: TooltipText(
text: Text(
dav.user,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
);
},
),
],
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(appLocalizations.connectivity),
FutureBuilder<bool>(
future: client!.pingCompleter.future,
builder: (_, snapshot) {
return Center(
child: FadeThroughBox(
child:
snapshot.connectionState != ConnectionState.done
? const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 1,
),
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
),
),
trailing: FilledButton.tonal(
onPressed: () {
_showAddWebDAV(dav);
},
child: Text(appLocalizations.edit),
),
),
const SizedBox(height: 4),
ListItem.input(
title: Text(appLocalizations.file),
subtitle: Text(dav.fileName),
delegate: InputDelegate(
title: appLocalizations.file,
value: dav.fileName,
resetValue: defaultDavFileName,
onChanged: (value) {
_handleChange(value, ref);
},
),
),
ListItem(
onTap: () {
_backupOnWebDAV(client);
},
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.remoteBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnWebDAV(context, client);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.remoteRecoveryDesc),
),
],
ListHeader(title: appLocalizations.local),
ListItem(
onTap: () {
_backupOnLocal(context);
},
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.localBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnLocal(context);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.localRecoveryDesc),
),
ListHeader(title: appLocalizations.options),
Consumer(
builder: (_, ref, _) {
final recoveryStrategy = ref.watch(
appSettingProvider.select((state) => state.recoveryStrategy),
);
return ListItem(
onTap: () {
_handleUpdateRecoveryStrategy(ref);
},
title: Text(appLocalizations.recoveryStrategy),
trailing: FilledButton(
onPressed: () {
_handleUpdateRecoveryStrategy(ref);
},
child: Text(
Intl.message('recoveryStrategy_${recoveryStrategy.name}'),
),
),
);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,140 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/features/features.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AddedRulesView extends ConsumerStatefulWidget {
const AddedRulesView({super.key});
@override
ConsumerState<AddedRulesView> createState() => _AddedRulesViewState();
}
class _AddedRulesViewState extends ConsumerState<AddedRulesView> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
Future<void> _handleAddOrUpdate([Rule? rule]) async {
final res = await globalState.showCommonDialog<Rule>(
child: AddOrEditRuleDialog(rule: rule),
);
if (res == null) {
return;
}
ref.read(rulesProvider.notifier).update((state) => state.updateWith(res));
}
void _handleSelected(String ruleId) {
ref.read(selectedIdsProvider.notifier).update((selectedRules) {
final newSelectedRules = Set<String>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
});
}
void _handleSelectAll() {
final ids = ref.read(rulesProvider).map((item) => item.id).toSet();
ref.read(selectedIdsProvider.notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
Future<void> _handleDelete() async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
),
);
if (res != true) {
return;
}
final selectedRules = ref.read(selectedIdsProvider);
ref.read(rulesProvider.notifier).update((rules) {
final newRules = List<Rule>.from(
rules.where((item) => !selectedRules.contains(item.id)),
);
return newRules;
});
ref.read(selectedIdsProvider.notifier).value = {};
}
@override
Widget build(BuildContext context) {
final rules = ref.watch(rulesProvider);
final selectedRules = ref.watch(selectedIdsProvider);
return BaseScaffold(
title: appLocalizations.addedRules,
actions: [
if (selectedRules.isNotEmpty) ...[
CommonMinIconButtonTheme(
child: IconButton.filledTonal(
onPressed: _handleDelete,
icon: Icon(Icons.delete),
),
),
SizedBox(width: 2),
],
CommonMinFilledButtonTheme(
child: selectedRules.isNotEmpty
? FilledButton(
onPressed: _handleSelectAll,
child: Text(appLocalizations.selectAll),
)
: FilledButton.tonal(
onPressed: () {
_handleAddOrUpdate();
},
child: Text(appLocalizations.add),
),
),
SizedBox(width: 8),
],
body: rules.isEmpty
? NullStatus(
label: appLocalizations.nullTip(appLocalizations.rule),
illustration: RuleEmptyIllustration(),
)
: ReorderableList(
padding: EdgeInsets.all(16),
itemBuilder: (context, index) {
final rule = rules[index];
return ReorderableDelayedDragStartListener(
key: ObjectKey(rule),
index: index,
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
rule: rule,
isSelected: selectedRules.contains(rule.id),
onSelected: _handleSelected,
onEdit: (Rule rule) {
_handleAddOrUpdate(rule);
},
),
);
},
itemCount: rules.length,
onReorder: (int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final newRules = List<Rule>.from(rules);
final item = newRules.removeAt(oldIndex);
newRules.insert(newIndex, item);
ref.read(rulesProvider.notifier).value = newRules;
},
),
);
}
}

View File

@@ -0,0 +1,90 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/config/dns.dart';
import 'package:fl_clash/views/config/network.dart';
import 'package:fl_clash/views/config/scripts.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'added_rules.dart';
class AdvancedConfigView extends StatelessWidget {
const AdvancedConfigView({super.key});
@override
Widget build(BuildContext context) {
final appLocalizations = context.appLocalizations;
List<Widget> items = [
ListItem.open(
title: Text(appLocalizations.network),
subtitle: Text(appLocalizations.networkDesc),
leading: const Icon(Icons.vpn_key),
delegate: OpenDelegate(
blur: false,
widget: BaseScaffold(
title: appLocalizations.network,
body: const NetworkListView(),
),
),
),
ListItem.open(
title: Text('DNS'),
subtitle: Text(appLocalizations.dnsDesc),
leading: const Icon(Icons.dns),
delegate: OpenDelegate(
widget: BaseScaffold(
title: 'DNS',
actions: [
Consumer(
builder: (_, ref, _) {
return IconButton(
onPressed: () async {
final res = await globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(text: appLocalizations.resetTip),
);
if (res != true) {
return;
}
ref
.read(patchClashConfigProvider.notifier)
.updateState(
(state) => state.copyWith(dns: defaultDns),
);
},
tooltip: appLocalizations.reset,
icon: const Icon(Icons.replay),
);
},
),
],
body: const DnsListView(),
),
blur: false,
),
),
ListItem.open(
title: Text(appLocalizations.addedRules),
subtitle: Text(appLocalizations.controlGlobalAddedRules),
leading: const Icon(Icons.library_books),
delegate: OpenDelegate(widget: const AddedRulesView(), blur: false),
),
ListItem.open(
title: Text(appLocalizations.script),
subtitle: Text(appLocalizations.overrideScript),
leading: const Icon(Icons.rocket, fontWeight: FontWeight.w900),
delegate: OpenDelegate(widget: const ScriptsView(), blur: false),
),
];
return BaseScaffold(
title: appLocalizations.advancedConfig,
body: generateListView(
items.separated(const Divider(height: 0)).toList(),
),
);
}
}

View File

@@ -1,112 +1,16 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/config/dns.dart';
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/views/config/general.dart';
import 'package:fl_clash/views/config/network.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ConfigView extends StatefulWidget {
class ConfigView extends StatelessWidget {
const ConfigView({super.key});
@override
State<ConfigView> createState() => _ConfigViewState();
}
class _ConfigViewState extends State<ConfigView> {
@override
Widget build(BuildContext context) {
List<Widget> items = [
ListItem.open(
title: Text(appLocalizations.general),
subtitle: Text(appLocalizations.generalDesc),
leading: const Icon(Icons.build),
delegate: OpenDelegate(
title: appLocalizations.general,
widget: generateListView(generalItems),
blur: false,
),
),
ListItem.open(
title: Text(appLocalizations.network),
subtitle: Text(appLocalizations.networkDesc),
leading: const Icon(Icons.vpn_key),
delegate: OpenDelegate(
title: appLocalizations.network,
blur: false,
actions: [
Consumer(
builder: (_, ref, _) {
return IconButton(
onPressed: () async {
final res = await globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(text: appLocalizations.resetTip),
);
if (res != true) {
return;
}
ref
.read(vpnSettingProvider.notifier)
.updateState(
(state) => defaultVpnProps.copyWith(
accessControl: state.accessControl,
),
);
ref
.read(patchClashConfigProvider.notifier)
.updateState(
(state) => state.copyWith(tun: defaultTun),
);
},
tooltip: appLocalizations.reset,
icon: const Icon(Icons.replay),
);
},
),
],
widget: const NetworkListView(),
),
),
ListItem.open(
title: const Text('DNS'),
subtitle: Text(appLocalizations.dnsDesc),
leading: const Icon(Icons.dns),
delegate: OpenDelegate(
title: 'DNS',
actions: [
Consumer(
builder: (_, ref, _) {
return IconButton(
onPressed: () async {
final res = await globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(text: appLocalizations.resetTip),
);
if (res != true) {
return;
}
ref
.read(patchClashConfigProvider.notifier)
.updateState(
(state) => state.copyWith(dns: defaultDns),
);
},
tooltip: appLocalizations.reset,
icon: const Icon(Icons.replay),
);
},
),
],
widget: const DnsListView(),
blur: false,
),
),
];
return generateListView(items.separated(const Divider(height: 0)).toList());
return BaseScaffold(
title: appLocalizations.basicConfig,
body: generateListView(generalItems),
);
}
}

View File

@@ -219,7 +219,6 @@ class FakeIpFilterItem extends StatelessWidget {
title: Text(appLocalizations.fakeipFilter),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.fakeipFilter,
widget: Consumer(
builder: (_, ref, _) {
final fakeIpFilter = ref.watch(
@@ -257,7 +256,6 @@ class DefaultNameserverItem extends StatelessWidget {
subtitle: Text(appLocalizations.defaultNameserverDesc),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.defaultNameserver,
widget: Consumer(
builder: (_, ref, _) {
final defaultNameserver = ref.watch(
@@ -295,7 +293,6 @@ class NameserverItem extends StatelessWidget {
title: Text(appLocalizations.nameserver),
subtitle: Text(appLocalizations.nameserverDesc),
delegate: OpenDelegate(
title: appLocalizations.nameserver,
blur: false,
widget: Consumer(
builder: (_, ref, _) {
@@ -378,7 +375,6 @@ class NameserverPolicyItem extends StatelessWidget {
subtitle: Text(appLocalizations.nameserverPolicyDesc),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.nameserverPolicy,
widget: Consumer(
builder: (_, ref, _) {
final nameserverPolicy = ref.watch(
@@ -416,7 +412,6 @@ class ProxyServerNameserverItem extends StatelessWidget {
subtitle: Text(appLocalizations.proxyNameserverDesc),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.proxyNameserver,
widget: Consumer(
builder: (_, ref, _) {
final proxyServerNameserver = ref.watch(
@@ -455,7 +450,6 @@ class FallbackItem extends StatelessWidget {
subtitle: Text(appLocalizations.fallbackDesc),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.fallback,
widget: Consumer(
builder: (_, ref, _) {
final fallback = ref.watch(
@@ -552,7 +546,6 @@ class GeositeItem extends StatelessWidget {
title: const Text('Geosite'),
delegate: OpenDelegate(
blur: false,
title: 'Geosite',
widget: Consumer(
builder: (_, ref, _) {
final geosite = ref.watch(
@@ -590,7 +583,6 @@ class IpcidrItem extends StatelessWidget {
title: Text(appLocalizations.ipcidr),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.ipcidr,
widget: Consumer(
builder: (_, ref, _) {
final ipcidr = ref.watch(
@@ -628,7 +620,6 @@ class DomainItem extends StatelessWidget {
title: Text(appLocalizations.domain),
delegate: OpenDelegate(
blur: false,
title: appLocalizations.domain,
widget: Consumer(
builder: (_, ref, _) {
final domain = ref.watch(

View File

@@ -209,7 +209,6 @@ class HostsItem extends StatelessWidget {
subtitle: Text(appLocalizations.hostsDesc),
delegate: OpenDelegate(
blur: false,
title: 'Hosts',
widget: Consumer(
builder: (_, ref, _) {
final hosts = ref.watch(

View File

@@ -1,8 +1,6 @@
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/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -208,32 +206,6 @@ class BypassDomainItem extends StatelessWidget {
subtitle: Text(appLocalizations.bypassDomainDesc),
delegate: OpenDelegate(
blur: false,
actions: [
Consumer(
builder: (_, ref, _) {
return IconButton(
onPressed: () async {
final res = await globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(text: appLocalizations.resetTip),
);
if (res != true) {
return;
}
ref
.read(networkSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(bypassDomain: defaultBypassDomain),
);
},
tooltip: appLocalizations.reset,
icon: const Icon(Icons.replay),
);
},
),
],
title: appLocalizations.bypassDomain,
widget: Consumer(
builder: (_, ref, _) {
final bypassDomain = ref.watch(
@@ -328,7 +300,6 @@ class RouteAddressItem extends ConsumerWidget {
delegate: OpenDelegate(
blur: false,
maxWidth: 360,
title: appLocalizations.routeAddress,
widget: Consumer(
builder: (_, ref, _) {
final routeAddress = ref.watch(

View File

@@ -10,6 +10,7 @@ import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/null_status.dart';
import 'package:fl_clash/widgets/popup.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:fl_clash/widgets/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -30,85 +31,65 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
if (res != true) {
return;
}
ref.read(scriptStateProvider.notifier).del(label);
ref.read(scriptsProvider.notifier).del(label);
}
Widget _buildContent() {
return Consumer(
builder: (_, ref, _) {
final vm2 = ref.watch(
scriptStateProvider.select(
(state) => VM2(a: state.currentId, b: state.scripts),
),
);
final currentId = vm2.a;
final scripts = vm2.b;
final scripts = ref.watch(scriptsProvider);
if (scripts.isEmpty) {
return NullStatus(
illustration: ScriptEmptyIllustration(),
label: appLocalizations.nullTip(appLocalizations.script),
);
}
return RadioGroup(
onChanged: (value) {
if (value == null) {
return;
}
ref.read(scriptStateProvider.notifier).setId(value);
},
groupValue: currentId,
child: ListView.builder(
padding: kMaterialListPadding.copyWith(bottom: 16 + 64),
itemCount: scripts.length,
itemBuilder: (_, index) {
final script = scripts[index];
return Container(
padding: kTabLabelPadding,
margin: EdgeInsets.symmetric(vertical: 6),
child: CommonCard(
type: CommonCardType.filled,
radius: 16,
child: ListItem.radio(
padding: const EdgeInsets.only(left: 12, right: 12),
title: Text(script.label),
delegate: RadioDelegate(
value: script.id,
onTab: () {
ref.read(scriptStateProvider.notifier).setId(script.id);
},
),
trailing: CommonPopupBox(
targetBuilder: (open) {
return IconButton(
return ListView.builder(
padding: kMaterialListPadding.copyWith(bottom: 16 + 64),
itemCount: scripts.length,
itemBuilder: (_, index) {
final script = scripts[index];
return Container(
padding: kTabLabelPadding,
margin: EdgeInsets.symmetric(vertical: 6),
child: CommonCard(
type: CommonCardType.filled,
radius: 18,
child: ListItem(
padding: const EdgeInsets.only(left: 16, right: 12),
title: Text(script.label),
trailing: CommonPopupBox(
targetBuilder: (open) {
return IconButton(
onPressed: () {
open();
},
icon: Icon(Icons.more_vert),
);
},
popup: CommonPopupMenu(
items: [
PopupMenuItemData(
icon: Icons.edit,
label: appLocalizations.edit,
onPressed: () {
open();
_handleToEditor(script: script);
},
icon: Icon(Icons.more_vert),
);
},
popup: CommonPopupMenu(
items: [
PopupMenuItemData(
icon: Icons.edit,
label: appLocalizations.edit,
onPressed: () {
_handleToEditor(script: script);
},
),
PopupMenuItemData(
icon: Icons.delete,
label: appLocalizations.delete,
onPressed: () {
_handleDelScript(script.label);
},
),
],
),
),
PopupMenuItemData(
icon: Icons.delete,
label: appLocalizations.delete,
onPressed: () {
_handleDelScript(script.label);
},
),
],
),
),
),
);
},
),
),
);
},
);
},
);
@@ -134,9 +115,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
return appLocalizations.emptyTip(appLocalizations.name);
}
if (value != script?.label) {
final isExits = ref
.read(scriptStateProvider.notifier)
.isExits(value);
final isExits = ref.read(scriptsProvider.notifier).isExits(value);
if (isExits) {
return appLocalizations.existsTip(appLocalizations.name);
}
@@ -152,7 +131,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
}
if (newScript.label != script?.label) {
final isExits = ref
.read(scriptStateProvider.notifier)
.read(scriptsProvider.notifier)
.isExits(newScript.label);
if (isExits) {
globalState.showMessage(
@@ -163,7 +142,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
return;
}
}
ref.read(scriptStateProvider.notifier).setScript(newScript);
ref.read(scriptsProvider.notifier).setScript(newScript);
if (mounted) {
Navigator.of(context).pop();
}
@@ -214,12 +193,17 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
@override
Widget build(BuildContext context) {
return CommonScaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
_handleToEditor();
},
child: Icon(Icons.add),
),
actions: [
CommonMinFilledButtonTheme(
child: FilledButton.tonal(
onPressed: () {
_handleToEditor();
},
child: Text(appLocalizations.add),
),
),
SizedBox(width: 8),
],
body: _buildContent(),
title: appLocalizations.script,
);

View File

@@ -99,6 +99,7 @@ class _ConnectionsViewState extends ConsumerState<ConnectionsView> {
if (connections.isEmpty) {
return NullStatus(
label: appLocalizations.nullTip(appLocalizations.connections),
illustration: ConnectionEmptyIllustration(),
);
}
final items = connections

View File

@@ -2,6 +2,7 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -36,32 +37,42 @@ class DeveloperView extends ConsumerWidget {
}
},
),
ListItem(
title: Text(appLocalizations.crashTest),
minVerticalPadding: 14,
onTap: () {
// coreController.crash();
if (kDebugMode) {
if (kDebugMode)
ListItem(
title: Text(appLocalizations.crashTest),
minVerticalPadding: 14,
onTap: () async {
final res = await globalState.showMessage(
message: TextSpan(text: appLocalizations.confirmForceCrashCore),
);
if (res != true) {
return;
}
coreController.crash();
}
},
),
},
),
ListItem(
title: Text(appLocalizations.clearData),
minVerticalPadding: 14,
onTap: () async {
final res = await globalState.showMessage(
message: TextSpan(text: appLocalizations.confirmClearAllData),
);
if (res != true) {
return;
}
await globalState.appController.handleClear();
},
),
// ListItem(
// title: Text('Loading'),
// minVerticalPadding: 14,
// onTap: () {
// ref.read(loadingProvider.notifier).value = !ref.read(
// loadingProvider,
// );
// },
// ),
ListItem(
title: Text(appLocalizations.loadTest),
minVerticalPadding: 14,
onTap: () {
ref.read(loadingProvider.notifier).value = !ref.read(
loadingProvider,
);
},
),
],
);
}
@@ -71,31 +82,34 @@ class DeveloperView extends ConsumerWidget {
final enable = ref.watch(
appSettingProvider.select((state) => state.developerMode),
);
return SingleChildScrollView(
padding: baseInfoEdgeInsets,
child: Column(
children: [
CommonCard(
type: CommonCardType.filled,
radius: 18,
child: ListItem.switchItem(
padding: const EdgeInsets.only(left: 16, right: 16),
title: Text(appLocalizations.developerMode),
delegate: SwitchDelegate(
value: enable,
onChanged: (value) {
ref
.read(appSettingProvider.notifier)
.updateState(
(state) => state.copyWith(developerMode: value),
);
},
return BaseScaffold(
title: appLocalizations.developerMode,
body: SingleChildScrollView(
padding: baseInfoEdgeInsets,
child: Column(
children: [
CommonCard(
type: CommonCardType.filled,
radius: 18,
child: ListItem.switchItem(
padding: const EdgeInsets.only(left: 16, right: 16),
title: Text(appLocalizations.developerMode),
delegate: SwitchDelegate(
value: enable,
onChanged: (value) {
ref
.read(appSettingProvider.notifier)
.updateState(
(state) => state.copyWith(developerMode: value),
);
},
),
),
),
),
SizedBox(height: 16),
_getDeveloperList(context, ref),
],
SizedBox(height: 16),
_getDeveloperList(context, ref),
],
),
),
);
}

View File

@@ -6,6 +6,7 @@ import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -37,30 +38,35 @@ class HotKeyView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: HotAction.values.length,
itemBuilder: (_, index) {
final hotAction = HotAction.values[index];
return Consumer(
builder: (_, ref, _) {
final hotKeyAction = ref.watch(getHotKeyActionProvider(hotAction));
return ListItem(
title: Text(IntlExt.actionMessage(hotAction.name)),
subtitle: Text(
getSubtitle(hotKeyAction),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.primary,
return BaseScaffold(
title: appLocalizations.hotkeyManagement,
body: ListView.builder(
itemCount: HotAction.values.length,
itemBuilder: (_, index) {
final hotAction = HotAction.values[index];
return Consumer(
builder: (_, ref, _) {
final hotKeyAction = ref.watch(
getHotKeyActionProvider(hotAction),
);
return ListItem(
title: Text(IntlExt.actionMessage(hotAction.name)),
subtitle: Text(
getSubtitle(hotKeyAction),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.primary,
),
),
),
onTap: () {
globalState.showCommonDialog(
child: HotKeyRecorder(hotKeyAction: hotKeyAction),
);
},
);
},
);
},
onTap: () {
globalState.showCommonDialog(
child: HotKeyRecorder(hotKeyAction: hotKeyAction),
);
},
);
},
);
},
),
);
}
}
@@ -152,55 +158,58 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
@override
Widget build(BuildContext context) {
return Focus(
onKeyEvent: (_, _) {
return KeyEventResult.handled;
},
autofocus: true,
child: CommonDialog(
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
actions: [
TextButton(
onPressed: () {
_handleRemove();
return BaseScaffold(
title: appLocalizations.hotkeyManagement,
body: Focus(
onKeyEvent: (_, _) {
return KeyEventResult.handled;
},
autofocus: true,
child: CommonDialog(
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
actions: [
TextButton(
onPressed: () {
_handleRemove();
},
child: Text(appLocalizations.remove),
),
const SizedBox(width: 8),
TextButton(
onPressed: () {
_handleConfirm();
},
child: Text(appLocalizations.confirm),
),
],
child: ValueListenableBuilder(
valueListenable: hotKeyActionNotifier,
builder: (_, hotKeyAction, _) {
final key = hotKeyAction.key;
final modifiers = hotKeyAction.modifiers;
return SizedBox(
width: dialogCommonWidth,
child: key != null
? Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final modifier in modifiers)
KeyboardKeyBox(
keyboardKey: modifier.physicalKeys.first,
),
if (modifiers.isNotEmpty)
Text('+', style: context.textTheme.titleMedium),
KeyboardKeyBox(keyboardKey: PhysicalKeyboardKey(key)),
],
)
: Text(
appLocalizations.pressKeyboard,
style: context.textTheme.titleMedium,
),
);
},
child: Text(appLocalizations.remove),
),
const SizedBox(width: 8),
TextButton(
onPressed: () {
_handleConfirm();
},
child: Text(appLocalizations.confirm),
),
],
child: ValueListenableBuilder(
valueListenable: hotKeyActionNotifier,
builder: (_, hotKeyAction, _) {
final key = hotKeyAction.key;
final modifiers = hotKeyAction.modifiers;
return SizedBox(
width: dialogCommonWidth,
child: key != null
? Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final modifier in modifiers)
KeyboardKeyBox(
keyboardKey: modifier.physicalKeys.first,
),
if (modifiers.isNotEmpty)
Text('+', style: context.textTheme.titleMedium),
KeyboardKeyBox(keyboardKey: PhysicalKeyboardKey(key)),
],
)
: Text(
appLocalizations.pressKeyboard,
style: context.textTheme.titleMedium,
),
);
},
),
),
);

View File

@@ -137,6 +137,7 @@ class _LogsViewState extends ConsumerState<LogsView> {
final logs = state.list;
if (logs.isEmpty) {
return NullStatus(
illustration: LogEmptyIllustration(),
label: appLocalizations.nullTip(appLocalizations.logs),
);
}

View File

@@ -137,14 +137,14 @@ class _OverrideProfileViewState extends ConsumerState<OverrideProfileView> {
SliverToBoxAdapter(
child: Consumer(
builder: (_, ref, child) {
final scriptMode = ref.watch(
scriptStateProvider.select(
(state) => state.realId != null,
),
);
if (!scriptMode) {
return SizedBox();
}
// final scriptMode = ref.watch(
// scriptStateProvider.select(
// (state) => state.realId != null,
// ),
// );
// if (!scriptMode) {
// return SizedBox();
// }
return child!;
},
child: ListItem(

View File

@@ -0,0 +1,501 @@
// ignore_for_file: deprecated_member_use
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/features/features.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/config/scripts.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class OverwriteView extends StatefulWidget {
final String profileId;
const OverwriteView({super.key, required this.profileId});
@override
State<OverwriteView> createState() => _OverwriteViewState();
}
class _OverwriteViewState extends State<OverwriteView> {
@override
Widget build(BuildContext context) {
return CommonScaffold(
title: appLocalizations.override,
body: CustomScrollView(
slivers: [_Title(widget.profileId), _Content(widget.profileId)],
),
);
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.appController.checkNeedSetup();
});
}
}
class _Title extends ConsumerWidget {
final String profileId;
const _Title(this.profileId);
String _getTitle(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standard,
OverwriteType.script => appLocalizations.script,
};
}
IconData _getIcon(OverwriteType type) {
return switch (type) {
OverwriteType.standard => Icons.stars,
OverwriteType.script => Icons.rocket,
};
}
String _getDesc(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standardModeDesc,
OverwriteType.script => appLocalizations.scriptModeDesc,
};
}
void _handleChange(WidgetRef ref, OverwriteType type) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
return state.copyWith.overwrite(type: type);
});
}
@override
Widget build(context, ref) {
final overwriteType = ref.watch(
profileOverwriteProvider(
profileId,
).select((state) => state?.type ?? OverwriteType.standard),
);
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader(info: Info(label: appLocalizations.overrideMode)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
spacing: 16,
children: [
for (final type in OverwriteType.values)
CommonCard(
isSelected: overwriteType == type,
onPressed: () {
_handleChange(ref, type);
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(_getIcon(type)),
const SizedBox(width: 8),
Flexible(child: Text(_getTitle(type))),
],
),
),
),
],
),
),
SizedBox(height: 12),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
_getDesc(overwriteType),
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurfaceVariant.opacity80,
),
),
),
],
),
);
}
}
class _Content extends ConsumerWidget {
final String profileId;
const _Content(this.profileId);
@override
Widget build(BuildContext context, ref) {
final type = ref.watch(
profileOverwriteProvider(
profileId,
).select((state) => state?.type ?? OverwriteType.standard),
);
return switch (type) {
OverwriteType.standard => _StandardContent(profileId),
OverwriteType.script => _ScriptContent(profileId),
};
}
}
class _StandardContent extends ConsumerWidget {
final String profileId;
const _StandardContent(this.profileId);
Future<void> _handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
final res = await globalState.showCommonDialog<Rule>(
child: AddOrEditRuleDialog(rule: rule),
);
if (res == null) {
return;
}
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
final newAddedRules = state.overwrite.standardOverwrite.addedRules
.updateWith(res);
return state.copyWith.overwrite.standardOverwrite(
addedRules: newAddedRules,
);
});
}
void _handleSelected(WidgetRef ref, String ruleId) {
ref.read(selectedIdsProvider.notifier).update((selectedRules) {
final newSelectedRules = Set<String>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
});
}
void _handleSelectAll(WidgetRef ref) {
final ids = ref
.read(
profileOverwriteProvider(
profileId,
).select((state) => state?.standardOverwrite.addedRules ?? []),
)
.map((item) => item.id)
.toSet();
ref.read(selectedIdsProvider.notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
Future<void> _handleDelete(WidgetRef ref) async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
),
);
if (res != true) {
return;
}
final selectedRules = ref.read(selectedIdsProvider);
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
final newAddedRules = state.overwrite.standardOverwrite.addedRules
.where((item) => !selectedRules.contains(item.id))
.toList();
return state.copyWith.overwrite.standardOverwrite(
addedRules: newAddedRules,
);
});
ref.read(selectedIdsProvider.notifier).value = {};
}
@override
Widget build(BuildContext context, ref) {
final standardOverwrite = ref.watch(
profileOverwriteProvider(
profileId,
).select((state) => state?.standardOverwrite),
);
final selectedRules = ref.watch(selectedIdsProvider);
final addedRules = standardOverwrite?.addedRules ?? [];
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(
info: Info(label: appLocalizations.addedRules),
actions: [
if (selectedRules.isNotEmpty) ...[
CommonMinIconButtonTheme(
child: IconButton.filledTonal(
onPressed: () {
_handleDelete(ref);
},
icon: Icon(Icons.delete),
),
),
SizedBox(width: 8),
],
CommonMinFilledButtonTheme(
child: selectedRules.isNotEmpty
? FilledButton(
onPressed: () {
_handleSelectAll(ref);
},
child: Text(appLocalizations.selectAll),
)
: FilledButton.tonal(
onPressed: () {
_handleAddOrUpdate(ref);
},
child: Text(appLocalizations.add),
),
),
],
),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList.builder(
itemCount: addedRules.length,
itemBuilder: (_, index) {
final rule = addedRules[index];
return RuleItem(
isEditing: selectedRules.isNotEmpty,
isSelected: selectedRules.contains(rule.id),
rule: rule,
onSelected: (id) {
_handleSelected(ref, id);
},
onEdit: (rule) {
_handleAddOrUpdate(ref, rule);
},
);
},
),
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
appLocalizations.controlGlobalAddedRules,
style: context.textTheme.bodyLarge,
),
),
SizedBox(width: 4),
Icon(Icons.arrow_forward, size: 18),
],
),
),
onPressed: () {
BaseNavigator.push(
context,
_EditGlobalAddedRules(profileId: profileId),
);
},
),
),
),
],
);
}
}
class _ScriptContent extends ConsumerWidget {
final String profileId;
const _ScriptContent(this.profileId);
void _handleChange(WidgetRef ref, String scriptId) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
String? newScriptId = scriptId;
if (newScriptId == state.overwrite.scriptOverwrite.scriptId) {
newScriptId = null;
}
return state.copyWith.overwrite.scriptOverwrite(scriptId: newScriptId);
});
}
@override
Widget build(BuildContext context, ref) {
final scriptId = ref.watch(
profileOverwriteProvider(
profileId,
).select((state) => state?.scriptOverwrite.scriptId),
);
final scripts = ref.watch(scriptsProvider);
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(info: Info(label: appLocalizations.overrideScript)),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList.builder(
itemCount: scripts.length,
itemBuilder: (_, index) {
final script = scripts[index];
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
type: CommonCardType.filled,
radius: 18,
child: ListTile(
minLeadingWidth: 0,
minTileHeight: 0,
minVerticalPadding: 0,
contentPadding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
).copyWith(left: 12),
title: Row(
children: [
Radio(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
toggleable: true,
value: script.id,
groupValue: scriptId,
onChanged: (_) {
_handleChange(ref, script.id);
},
),
SizedBox(width: 8),
Flexible(child: Text(script.label)),
],
),
onTap: () {
_handleChange(ref, script.id);
},
),
),
);
},
),
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
appLocalizations.goToConfigureScript,
style: context.textTheme.bodyLarge,
),
),
SizedBox(width: 4),
Icon(Icons.arrow_forward, size: 18),
],
),
),
onPressed: () {
BaseNavigator.push(context, const ScriptsView());
},
),
),
),
],
);
}
}
class _EditGlobalAddedRules extends ConsumerWidget {
final String profileId;
const _EditGlobalAddedRules({required this.profileId});
void _handleChange(WidgetRef ref, String ruleId) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
final newDisabledRuleIds = Set<String>.from(
state.overwrite.standardOverwrite.disabledRuleIds,
)..addOrRemove(ruleId);
return state.copyWith.overwrite.standardOverwrite(
disabledRuleIds: newDisabledRuleIds.toList(),
);
});
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final disabledRuleIds = ref.watch(
profileOverwriteProvider(
profileId,
).select((state) => state?.standardOverwrite.disabledRuleIds ?? []),
);
final rules = ref.watch(rulesProvider);
return BaseScaffold(
title: appLocalizations.editGlobalRules,
body: rules.isEmpty
? NullStatus(
label: appLocalizations.nullTip(appLocalizations.rule),
illustration: RuleEmptyIllustration(),
)
: ListView.builder(
padding: EdgeInsets.all(16),
itemBuilder: (context, index) {
final rule = rules[index];
return RuleStatusItem(
status: !disabledRuleIds.contains(rule.id),
rule: rule,
onChange: (_) {
_handleChange(ref, rule.id);
},
);
},
itemCount: rules.length,
),
);
}
}

View File

@@ -1,18 +1,20 @@
import 'dart:isolate';
import 'dart:ui';
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/pages/editor.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/profiles/edit_profile.dart';
import 'package:fl_clash/views/profiles/override_profile.dart';
import 'package:fl_clash/views/profiles/scripts.dart';
import 'package:fl_clash/views/profiles/overwrite.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'add_profile.dart';
import 'add.dart';
import 'edit.dart';
class ProfilesView extends StatefulWidget {
const ProfilesView({super.key});
@@ -77,27 +79,27 @@ class _ProfilesViewState extends State<ProfilesView> {
},
icon: const Icon(Icons.sync),
),
IconButton(
onPressed: () {
showExtend(
context,
builder: (_, type) {
return ScriptsView();
},
);
},
icon: Consumer(
builder: (context, ref, _) {
final isScriptMode = ref.watch(
scriptStateProvider.select((state) => state.realId != null),
);
return Icon(
Icons.functions,
color: isScriptMode ? context.colorScheme.primary : null,
);
},
),
),
// IconButton(
// onPressed: () {
// showExtend(
// context,
// builder: (_, type) {
// return ScriptsView();
// },
// );
// },
// icon: Consumer(
// builder: (context, ref, _) {
// final isScriptMode = ref.watch(
// scriptStateProvider.select((state) => state.realId != null),
// );
// return Icon(
// Icons.functions,
// color: isScriptMode ? context.colorScheme.primary : null,
// );
// },
// ),
// ),
IconButton(
onPressed: () {
final profiles = globalState.config.profiles;
@@ -134,7 +136,10 @@ class _ProfilesViewState extends State<ProfilesView> {
profilesSelectorStateProvider,
);
if (profilesSelectorState.profiles.isEmpty) {
return NullStatus(label: appLocalizations.nullProfileDesc);
return NullStatus(
label: appLocalizations.nullProfileDesc,
illustration: ProfileEmptyIllustration(),
);
}
return Align(
alignment: Alignment.topCenter,
@@ -202,6 +207,22 @@ class ProfileItem extends StatelessWidget {
await globalState.appController.deleteProfile(profile.id);
}
Future<void> _handlePreview(BuildContext context) async {
final config = await globalState.getConfigMap(profile.id);
final content = await Isolate.run(() {
return yaml.encode(config);
});
if (!context.mounted) {
return;
}
final previewPage = EditorPage(
title: profile.label ?? profile.id,
content: content,
);
BaseNavigator.push<String>(context, previewPage);
}
Future updateProfile() async {
final appController = globalState.appController;
if (profile.type == ProfileType.file) return;
@@ -252,16 +273,12 @@ class ProfileItem extends StatelessWidget {
];
}
// _handleCopyLink(BuildContext context) async {
// await Clipboard.setData(
// ClipboardData(
// text: profile.url,
// ),
// );
// if (context.mounted) {
// context.showNotifier(appLocalizations.copySuccess);
// }
// }
Future<void> _handleCopyLink(BuildContext context) async {
await Clipboard.setData(ClipboardData(text: profile.url));
if (context.mounted) {
context.showNotifier(appLocalizations.copySuccess);
}
}
Future<void> _handleExportFile(BuildContext context) async {
final res = await globalState.appController.safeRun<bool>(
@@ -283,8 +300,7 @@ class ProfileItem extends StatelessWidget {
}
void _handlePushGenProfilePage(BuildContext context, String id) {
final overrideProfileView = OverrideProfileView(profileId: id);
BaseNavigator.push(context, overrideProfileView);
BaseNavigator.push(context, OverwriteView(profileId: id));
}
@override
@@ -319,6 +335,13 @@ class ProfileItem extends StatelessWidget {
_handleShowEditExtendPage(context);
},
),
PopupMenuItemData(
icon: Icons.visibility_outlined,
label: appLocalizations.preview,
onPressed: () {
_handlePreview(context);
},
),
if (profile.type == ProfileType.url) ...[
PopupMenuItemData(
icon: Icons.sync_alt_sharp,
@@ -329,18 +352,46 @@ class ProfileItem extends StatelessWidget {
),
],
PopupMenuItemData(
icon: Icons.extension_outlined,
label: appLocalizations.override,
onPressed: () {
_handlePushGenProfilePage(context, profile.id);
},
),
PopupMenuItemData(
icon: Icons.file_copy_outlined,
label: appLocalizations.exportFile,
onPressed: () {
_handleExportFile(context);
},
icon: Icons.emergency_outlined,
label: appLocalizations.more,
subItems: [
PopupMenuItemData(
icon: Icons.extension_outlined,
label: appLocalizations.override,
onPressed: () {
_handlePushGenProfilePage(context, profile.id);
},
),
// PopupMenuItemData(
// icon: Icons.extension_outlined,
// label: appLocalizations.override + "1",
// onPressed: () {
// final overrideProfileView = OverrideProfileView(
// profileId: profile.id,
// );
// BaseNavigator.push(
// context,
// overrideProfileView,
// );
// },
// ),
if (profile.type == ProfileType.url) ...[
PopupMenuItemData(
icon: Icons.copy,
label: appLocalizations.copyLink,
onPressed: () {
_handleCopyLink(context);
},
),
],
PopupMenuItemData(
icon: Icons.file_copy_outlined,
label: appLocalizations.exportFile,
onPressed: () {
_handleExportFile(context);
},
),
],
),
PopupMenuItemData(
danger: true,
@@ -447,19 +498,34 @@ class _ReorderableProfilesSheetState extends State<ReorderableProfilesSheet> {
return AdaptiveSheetScaffold(
type: widget.type,
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
globalState.appController.setProfiles(profiles);
},
icon: Icon(Icons.save),
),
if (widget.type == SheetType.bottomSheet)
IconButton.filledTonal(
onPressed: () {
Navigator.of(context).pop();
globalState.appController.setProfiles(profiles);
},
style: IconButton.styleFrom(
visualDensity: VisualDensity.comfortable,
tapTargetSize: MaterialTapTargetSize.padded,
padding: EdgeInsets.all(8),
iconSize: 20,
),
icon: Icon(Icons.check),
)
else
IconButton.filledTonal(
icon: Icon(Icons.check),
onPressed: () {
Navigator.of(context).pop();
globalState.appController.setProfiles(profiles);
},
),
],
body: Padding(
padding: EdgeInsets.only(bottom: 32, top: 16),
padding: EdgeInsets.only(bottom: 16, top: 0),
child: ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: const EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(horizontal: 16),
proxyDecorator: proxyDecorator,
onReorder: (oldIndex, newIndex) {
setState(() {

View File

@@ -290,6 +290,7 @@ class _ProxiesListViewState extends State<ProxiesListView> {
ref.watch(themeSettingProvider.select((state) => state.textScale));
if (state.groups.isEmpty) {
return NullStatus(
illustration: ProxyEmptyIllustration(),
label: appLocalizations.nullTip(appLocalizations.proxies),
);
}
@@ -504,7 +505,10 @@ class _ListHeaderState extends State<ListHeader> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(groupName, style: context.textTheme.titleMedium),
EmojiText(
groupName,
style: context.textTheme.titleMedium,
),
const SizedBox(height: 4),
Flexible(
flex: 1,
@@ -572,6 +576,9 @@ class _ListHeaderState extends State<ListHeader> {
onPressed: () {
widget.onScrollToSelected(groupName);
},
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: 19,
icon: const Icon(Icons.adjust),
),
@@ -581,6 +588,9 @@ class _ListHeaderState extends State<ListHeader> {
visualDensity: VisualDensity.compact,
padding: EdgeInsets.all(2),
onPressed: _delayTest,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
icon: const Icon(Icons.network_ping),
),
const SizedBox(width: 6),
@@ -590,6 +600,9 @@ class _ListHeaderState extends State<ListHeader> {
visualDensity: VisualDensity.compact,
padding: EdgeInsets.all(2),
iconSize: 24,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
_handleChange(groupName);
},

View File

@@ -20,6 +20,7 @@ class ProxiesView extends ConsumerStatefulWidget {
}
class _ProxiesViewState extends ConsumerState<ProxiesView> {
final GlobalKey<CommonScaffoldState> _scaffoldKey = GlobalKey();
final GlobalKey<ProxiesTabViewState> _proxiesTabKey = GlobalKey();
bool _hasProviders = false;
bool _isTab = false;
@@ -37,7 +38,7 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
targetBuilder: (open) {
return IconButton(
onPressed: () {
open(offset: Offset(0, 20));
open(offset: Offset(0, 0));
},
icon: Icon(Icons.more_vert),
);
@@ -137,6 +138,14 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
},
fireImmediately: true,
);
ref.listenManual(
currentPageLabelProvider.select((state) => state == PageLabel.proxies),
(prev, next) {
if (prev != next && next == false) {
_scaffoldKey.currentState?.handleExitSearching();
}
},
);
}
@override
@@ -145,6 +154,8 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
proxiesStyleSettingProvider.select((state) => state.type),
);
return CommonScaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
floatingActionButton: _buildFAB(),
actions: _buildActions(),
title: appLocalizations.proxies,

View File

@@ -50,6 +50,7 @@ class ProxiesSetting extends StatelessWidget {
List<Widget> _buildStyleSetting() {
return generateSection(
isFirst: true,
title: appLocalizations.style,
items: [
SingleChildScrollView(

View File

@@ -174,6 +174,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
final groups = state.groups;
if (groups.isEmpty) {
return NullStatus(
illustration: ProxyEmptyIllustration(),
label: appLocalizations.nullTip(appLocalizations.proxies),
);
}
@@ -217,7 +218,19 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
overlayColor: const WidgetStatePropertyAll(
Colors.transparent,
),
tabs: [for (final group in groups) Tab(text: group.name)],
tabs: [
for (final group in groups)
Tab(
child: Builder(
builder: (context) {
return EmojiText(
group.name,
style: DefaultTextStyle.of(context).style,
);
},
),
),
],
),
if (value) Positioned(right: 0, child: child!),
],

View File

@@ -37,21 +37,20 @@ class ThemeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final items = [
_ThemeModeItem(),
_PrimaryColorItem(),
_PrueBlackItem(),
_TextScaleFactorItem(),
const SizedBox(height: 64),
];
return ListView.separated(
itemCount: items.length,
itemBuilder: (_, index) {
return items[index];
},
separatorBuilder: (_, _) {
return SizedBox(height: 24);
},
return BaseScaffold(
title: appLocalizations.theme,
body: CustomScrollView(
slivers: [
_ThemeModeItem(),
SliverToBoxAdapter(child: SizedBox(height: 16)),
_PrimaryColorItem(),
SliverToBoxAdapter(child: SizedBox(height: 16)),
_PrueBlackItem(),
SliverToBoxAdapter(child: SizedBox(height: 16)),
_TextScaleFactorItem(),
SliverToBoxAdapter(child: SizedBox(height: 32)),
],
),
);
}
}
@@ -105,46 +104,48 @@ class _ThemeModeItem extends ConsumerWidget {
themeMode: ThemeMode.dark,
),
];
return ItemCard(
info: Info(
label: appLocalizations.themeMode,
iconData: Icons.brightness_high,
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 56,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: themeModeItems.length,
itemBuilder: (_, index) {
final themeModeItem = themeModeItems[index];
return CommonCard(
isSelected: themeModeItem.themeMode == themeMode,
onPressed: () {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(themeMode: themeModeItem.themeMode),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(child: Icon(themeModeItem.iconData)),
const SizedBox(width: 8),
Flexible(child: Text(themeModeItem.label)),
],
return SliverToBoxAdapter(
child: ItemCard(
info: Info(
label: appLocalizations.themeMode,
iconData: Icons.brightness_high,
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 56,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: themeModeItems.length,
itemBuilder: (_, index) {
final themeModeItem = themeModeItems[index];
return CommonCard(
isSelected: themeModeItem.themeMode == themeMode,
onPressed: () {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(themeMode: themeModeItem.themeMode),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(child: Icon(themeModeItem.iconData)),
const SizedBox(width: 8),
Flexible(child: Text(themeModeItem.label)),
],
),
),
),
);
},
separatorBuilder: (_, _) {
return const SizedBox(width: 16);
},
);
},
separatorBuilder: (_, _) {
return const SizedBox(width: 16);
},
),
),
),
);
@@ -176,7 +177,7 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
return state.copyWith(
primaryColors: defaultPrimaryColors,
primaryColor: defaultPrimaryColor,
schemeVariant: DynamicSchemeVariant.tonalSpot,
schemeVariant: DynamicSchemeVariant.content,
);
});
}
@@ -266,7 +267,11 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
state.primaryColors,
state.schemeVariant,
state.primaryColor == defaultPrimaryColor &&
intListEquality.equals(state.primaryColors, defaultPrimaryColors),
intListEquality.equals(
state.primaryColors,
defaultPrimaryColors,
) &&
state.schemeVariant == DynamicSchemeVariant.content,
),
),
);
@@ -275,125 +280,132 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
final schemeVariant = vm4.c;
final isEquals = vm4.d;
return CommonPopScope(
onPop: (context) {
if (_removablePrimaryColor != null) {
setState(() {
_removablePrimaryColor = null;
});
return false;
}
return true;
},
child: ItemCard(
info: Info(label: appLocalizations.themeColor, iconData: Icons.palette),
actions: genActions([
if (_removablePrimaryColor == null)
FilledButton(
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
return SliverToBoxAdapter(
child: CommonPopScope(
onPop: (context) {
if (_removablePrimaryColor != null) {
setState(() {
_removablePrimaryColor = null;
});
return false;
}
return true;
},
child: ItemCard(
info: Info(
label: appLocalizations.themeColor,
iconData: Icons.palette,
),
actions: genActions([
if (_removablePrimaryColor == null)
FilledButton(
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: _handleChangeSchemeVariant,
child: Text(Intl.message('${schemeVariant.name}Scheme')),
),
onPressed: _handleChangeSchemeVariant,
child: Text(Intl.message('${schemeVariant.name}Scheme')),
),
if (_removablePrimaryColor != null)
FilledButton(
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
if (_removablePrimaryColor != null)
FilledButton(
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
),
if (_removablePrimaryColor == null && !isEquals)
IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
),
], space: 8),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: LayoutBuilder(
builder: (_, constraints) {
final columns = _calcColumns(constraints.maxWidth);
final itemWidth =
(constraints.maxWidth - (columns - 1) * 16) / columns;
return Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final color in primaryColors)
Container(
clipBehavior: Clip.none,
width: itemWidth,
height: itemWidth,
child: Stack(
alignment: Alignment.center,
if (_removablePrimaryColor == null && !isEquals)
IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
),
], space: 8),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: LayoutBuilder(
builder: (_, constraints) {
final columns = _calcColumns(constraints.maxWidth);
final itemWidth =
(constraints.maxWidth - (columns - 1) * 16) / columns;
return Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final color in primaryColors)
Container(
clipBehavior: Clip.none,
children: [
EffectGestureDetector(
child: ColorSchemeBox(
isSelected: color == primaryColor,
primaryColor: color != null ? Color(color) : null,
onPressed: () {
width: itemWidth,
height: itemWidth,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
EffectGestureDetector(
child: ColorSchemeBox(
isSelected: color == primaryColor,
primaryColor: color != null
? Color(color)
: null,
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(primaryColor: color),
);
},
),
onLongPress: () {
setState(() {
_removablePrimaryColor = null;
_removablePrimaryColor = color;
});
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(primaryColor: color),
);
},
),
onLongPress: () {
setState(() {
_removablePrimaryColor = color;
});
},
),
if (_removablePrimaryColor != null &&
_removablePrimaryColor == color)
Container(
color: Colors.white.opacity0,
padding: EdgeInsets.all(8),
child: IconButton.filledTonal(
onPressed: _handleDel,
padding: EdgeInsets.all(12),
iconSize: 30,
icon: Icon(
color: context.colorScheme.primary,
Icons.delete,
if (_removablePrimaryColor != null &&
_removablePrimaryColor == color)
Container(
color: Colors.white.opacity0,
padding: EdgeInsets.all(8),
child: IconButton.filledTonal(
onPressed: _handleDel,
padding: EdgeInsets.all(12),
iconSize: 30,
icon: Icon(
color: context.colorScheme.primary,
Icons.delete,
),
),
),
),
],
),
),
if (_removablePrimaryColor == null)
Container(
width: itemWidth,
height: itemWidth,
padding: EdgeInsets.all(4),
child: IconButton.filledTonal(
onPressed: _handleAdd,
iconSize: 32,
icon: Icon(
color: context.colorScheme.primary,
Icons.add,
],
),
),
),
],
);
},
if (_removablePrimaryColor == null)
Container(
width: itemWidth,
height: itemWidth,
padding: EdgeInsets.all(4),
child: IconButton.filledTonal(
onPressed: _handleAdd,
iconSize: 32,
icon: Icon(
color: context.colorScheme.primary,
Icons.add,
),
),
),
],
);
},
),
),
),
),
@@ -409,22 +421,24 @@ class _PrueBlackItem extends ConsumerWidget {
final prueBlack = ref.watch(
themeSettingProvider.select((state) => state.pureBlack),
);
return ListItem.switchItem(
leading: Icon(Icons.contrast),
horizontalTitleGap: 12,
title: Text(
appLocalizations.pureBlackMode,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
return SliverToBoxAdapter(
child: ListItem.switchItem(
leading: Icon(Icons.contrast),
horizontalTitleGap: 12,
title: Text(
appLocalizations.pureBlackMode,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate(
value: prueBlack,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState((state) => state.copyWith(pureBlack: value));
},
),
),
delegate: SwitchDelegate(
value: prueBlack,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState((state) => state.copyWith(pureBlack: value));
},
),
);
}
@@ -439,72 +453,74 @@ class _TextScaleFactorItem extends ConsumerWidget {
themeSettingProvider.select((state) => state.textScale),
);
final String process = '${(textScale.scale * 100).round()}%';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: 8),
child: ListItem.switchItem(
leading: Icon(Icons.text_fields),
horizontalTitleGap: 12,
title: Text(
appLocalizations.textScale,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: 8),
child: ListItem.switchItem(
leading: Icon(Icons.text_fields),
horizontalTitleGap: 12,
title: Text(
appLocalizations.textScale,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate(
value: textScale.enable,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) => state.copyWith.textScale(enable: value),
);
},
),
),
delegate: SwitchDelegate(
value: textScale.enable,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) => state.copyWith.textScale(enable: value),
);
},
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
spacing: 32,
children: [
Expanded(
child: DisabledMask(
status: !textScale.enable,
child: ActivateBox(
active: textScale.enable,
child: SliderTheme(
data: _SliderDefaultsM3(context),
child: Slider(
padding: EdgeInsets.zero,
min: minTextScale,
max: maxTextScale,
value: textScale.scale,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith.textScale(scale: value),
);
},
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
spacing: 32,
children: [
Expanded(
child: DisabledMask(
status: !textScale.enable,
child: ActivateBox(
active: textScale.enable,
child: SliderTheme(
data: _SliderDefaultsM3(context),
child: Slider(
padding: EdgeInsets.zero,
min: minTextScale,
max: maxTextScale,
value: textScale.scale,
onChanged: (value) {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith.textScale(scale: value),
);
},
),
),
),
),
),
),
Padding(
padding: EdgeInsets.only(right: 4),
child: Text(process, style: context.textTheme.titleMedium),
),
],
Padding(
padding: EdgeInsets.only(right: 4),
child: Text(process, style: context.textTheme.titleMedium),
),
],
),
),
),
],
],
),
);
}
}

View File

@@ -17,6 +17,7 @@ import 'package:intl/intl.dart';
import 'package:path/path.dart' show dirname, join;
import 'backup_and_recovery.dart';
import 'config/advanced.dart';
import 'developer.dart';
import 'theme.dart';
@@ -35,11 +36,7 @@ class _ToolViewState extends ConsumerState<ToolsView> {
subtitle: navigationItem.description != null
? Text(Intl.message(navigationItem.description!))
: null,
delegate: OpenDelegate(
title: Intl.message(navigationItem.label.name),
widget: navigationItem.builder(context),
wrap: false,
),
delegate: OpenDelegate(widget: navigationItem.builder(context)),
);
}
@@ -58,7 +55,7 @@ class _ToolViewState extends ConsumerState<ToolsView> {
List<Widget> _getOtherList(bool enableDeveloperMode) {
return generateSection(
title: appLocalizations.other,
title: context.appLocalizations.other,
items: [
_DisclaimerItem(),
if (enableDeveloperMode) _DeveloperItem(),
@@ -69,16 +66,17 @@ class _ToolViewState extends ConsumerState<ToolsView> {
List<Widget> _getSettingList() {
return generateSection(
title: appLocalizations.settings,
title: context.appLocalizations.settings,
items: [
_LocaleItem(),
_ThemeItem(),
_BackupItem(),
if (system.isDesktop) _HotkeyItem(),
if (system.isWindows) _LoopbackItem(),
if (system.isAndroid) _AccessItem(),
_ConfigItem(),
_SettingItem(),
const _LocaleItem(),
const _ThemeItem(),
const _BackupItem(),
if (system.isDesktop) const _HotkeyItem(),
if (system.isWindows) const _LoopbackItem(),
if (system.isAndroid) const _AccessItem(),
const _ConfigItem(),
const _AdvancedConfigItem(),
const _SettingItem(),
],
);
}
@@ -99,7 +97,7 @@ class _ToolViewState extends ConsumerState<ToolsView> {
}
return Column(
children: [
ListHeader(title: appLocalizations.more),
ListHeader(title: context.appLocalizations.more),
_buildNavigationMenu(state.navigationItems),
],
);
@@ -109,7 +107,7 @@ class _ToolViewState extends ConsumerState<ToolsView> {
..._getOtherList(vm2.b),
];
return CommonScaffold(
title: appLocalizations.tools,
title: context.appLocalizations.tools,
body: ListView.builder(
key: toolsStoreKey,
itemCount: items.length,
@@ -133,14 +131,14 @@ class _LocaleItem extends ConsumerWidget {
final locale = ref.watch(
appSettingProvider.select((state) => state.locale),
);
final subTitle = locale ?? appLocalizations.defaultText;
final subTitle = locale ?? context.appLocalizations.defaultText;
final currentLocale = utils.getLocaleForString(locale);
return ListItem<Locale?>.options(
leading: const Icon(Icons.language_outlined),
title: Text(appLocalizations.language),
title: Text(context.appLocalizations.language),
subtitle: Text(Intl.message(subTitle)),
delegate: OptionsDelegate(
title: appLocalizations.language,
title: context.appLocalizations.language,
options: [null, ...AppLocalizations.delegate.supportedLocales],
onChanged: (Locale? locale) {
ref
@@ -163,12 +161,9 @@ class _ThemeItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.style),
title: Text(appLocalizations.theme),
subtitle: Text(appLocalizations.themeDesc),
delegate: OpenDelegate(
title: appLocalizations.theme,
widget: const ThemeView(),
),
title: Text(context.appLocalizations.theme),
subtitle: Text(context.appLocalizations.themeDesc),
delegate: OpenDelegate(widget: const ThemeView()),
);
}
}
@@ -180,12 +175,9 @@ class _BackupItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.cloud_sync),
title: Text(appLocalizations.backupAndRecovery),
subtitle: Text(appLocalizations.backupAndRecoveryDesc),
delegate: OpenDelegate(
title: appLocalizations.backupAndRecovery,
widget: const BackupAndRecovery(),
),
title: Text(context.appLocalizations.backupAndRecovery),
subtitle: Text(context.appLocalizations.backupAndRecoveryDesc),
delegate: OpenDelegate(widget: const BackupAndRecovery()),
);
}
}
@@ -197,12 +189,9 @@ class _HotkeyItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.keyboard),
title: Text(appLocalizations.hotkeyManagement),
subtitle: Text(appLocalizations.hotkeyManagementDesc),
delegate: OpenDelegate(
title: appLocalizations.hotkeyManagement,
widget: const HotKeyView(),
),
title: Text(context.appLocalizations.hotkeyManagement),
subtitle: Text(context.appLocalizations.hotkeyManagementDesc),
delegate: OpenDelegate(widget: const HotKeyView()),
);
}
}
@@ -214,8 +203,8 @@ class _LoopbackItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem(
leading: const Icon(Icons.lock),
title: Text(appLocalizations.loopback),
subtitle: Text(appLocalizations.loopbackDesc),
title: Text(context.appLocalizations.loopback),
subtitle: Text(context.appLocalizations.loopbackDesc),
onTap: () {
windows?.runas(
'"${join(dirname(Platform.resolvedExecutable), "EnableLoopback.exe")}"',
@@ -233,12 +222,9 @@ class _AccessItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.view_list),
title: Text(appLocalizations.accessControl),
subtitle: Text(appLocalizations.accessControlDesc),
delegate: OpenDelegate(
title: appLocalizations.appAccessControl,
widget: const AccessView(),
),
title: Text(context.appLocalizations.accessControl),
subtitle: Text(context.appLocalizations.accessControlDesc),
delegate: OpenDelegate(widget: const AccessView()),
);
}
}
@@ -250,12 +236,23 @@ class _ConfigItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.edit),
title: Text(appLocalizations.basicConfig),
subtitle: Text(appLocalizations.basicConfigDesc),
delegate: OpenDelegate(
title: appLocalizations.basicConfig,
widget: const ConfigView(),
),
title: Text(context.appLocalizations.basicConfig),
subtitle: Text(context.appLocalizations.basicConfigDesc),
delegate: OpenDelegate(widget: const ConfigView()),
);
}
}
class _AdvancedConfigItem extends StatelessWidget {
const _AdvancedConfigItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.build),
title: Text(context.appLocalizations.advancedConfig),
subtitle: Text(context.appLocalizations.advancedConfigDesc),
delegate: OpenDelegate(widget: const AdvancedConfigView()),
);
}
}
@@ -267,12 +264,9 @@ class _SettingItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.settings),
title: Text(appLocalizations.application),
subtitle: Text(appLocalizations.applicationDesc),
delegate: OpenDelegate(
title: appLocalizations.application,
widget: const ApplicationSettingView(),
),
title: Text(context.appLocalizations.application),
subtitle: Text(context.appLocalizations.applicationDesc),
delegate: OpenDelegate(widget: const ApplicationSettingView()),
);
}
}
@@ -284,7 +278,7 @@ class _DisclaimerItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem(
leading: const Icon(Icons.gavel),
title: Text(appLocalizations.disclaimer),
title: Text(context.appLocalizations.disclaimer),
onTap: () async {
final isDisclaimerAccepted = await globalState.appController
.showDisclaimer();
@@ -303,11 +297,8 @@ class _InfoItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.info),
title: Text(appLocalizations.about),
delegate: OpenDelegate(
title: appLocalizations.about,
widget: const AboutView(),
),
title: Text(context.appLocalizations.about),
delegate: OpenDelegate(widget: const AboutView()),
);
}
}
@@ -319,11 +310,8 @@ class _DeveloperItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.developer_board),
title: Text(appLocalizations.developerMode),
delegate: OpenDelegate(
title: appLocalizations.developerMode,
widget: const DeveloperView(),
),
title: Text(context.appLocalizations.developerMode),
delegate: OpenDelegate(widget: const DeveloperView()),
);
}
}

View File

@@ -0,0 +1,279 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
enum CrossSlideState { showFirst, showSecond }
typedef AnimatedCrossSlideBuilder =
Widget Function(
Widget topChild,
Key topChildKey,
Widget bottomChild,
Key bottomChildKey,
);
class AnimatedCrossSlide extends StatefulWidget {
const AnimatedCrossSlide({
super.key,
required this.firstChild,
required this.secondChild,
this.firstCurve = Curves.linear,
this.secondCurve = Curves.linear,
this.sizeCurve = Curves.linear,
this.alignment = Alignment.topCenter,
required this.crossSlideState,
required this.duration,
this.reverseDuration,
this.layoutBuilder = defaultLayoutBuilder,
this.excludeBottomFocus = true,
});
final Widget firstChild;
final Widget secondChild;
final CrossSlideState crossSlideState;
final Duration duration;
final Duration? reverseDuration;
final Curve firstCurve;
final Curve secondCurve;
final Curve sizeCurve;
final AlignmentGeometry alignment;
final AnimatedCrossSlideBuilder layoutBuilder;
final bool excludeBottomFocus;
static Widget defaultLayoutBuilder(
Widget topChild,
Key topChildKey,
Widget bottomChild,
Key bottomChildKey,
) {
return Stack(
clipBehavior: Clip.none,
children: <Widget>[
Positioned(
key: bottomChildKey,
left: 0.0,
top: 0.0,
right: 0.0,
child: bottomChild,
),
Positioned(key: topChildKey, child: topChild),
],
);
}
@override
State<AnimatedCrossSlide> createState() => _AnimatedCrossSlideState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
EnumProperty<CrossSlideState>('crossSlideState', crossSlideState),
);
properties.add(
DiagnosticsProperty<AlignmentGeometry>(
'alignment',
alignment,
defaultValue: Alignment.topCenter,
),
);
properties.add(
IntProperty('duration', duration.inMilliseconds, unit: 'ms'),
);
properties.add(
IntProperty(
'reverseDuration',
reverseDuration?.inMilliseconds,
unit: 'ms',
defaultValue: null,
),
);
}
}
class _AnimatedCrossSlideState extends State<AnimatedCrossSlide>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _firstAnimation;
late Animation<double> _secondAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this,
);
if (widget.crossSlideState == CrossSlideState.showSecond) {
_controller.value = 1.0;
}
_firstAnimation = _initAnimation(widget.firstCurve, true);
_secondAnimation = _initAnimation(widget.secondCurve, false);
_controller.addStatusListener((AnimationStatus status) {
setState(() {});
});
}
Animation<double> _initAnimation(Curve curve, bool inverted) {
Animation<double> result = _controller.drive(CurveTween(curve: curve));
if (inverted) {
result = result.drive(Tween<double>(begin: 1.0, end: 0.0));
}
return result;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(AnimatedCrossSlide oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration) {
_controller.duration = widget.duration;
}
if (widget.reverseDuration != oldWidget.reverseDuration) {
_controller.reverseDuration = widget.reverseDuration;
}
if (widget.firstCurve != oldWidget.firstCurve) {
_firstAnimation = _initAnimation(widget.firstCurve, true);
}
if (widget.secondCurve != oldWidget.secondCurve) {
_secondAnimation = _initAnimation(widget.secondCurve, false);
}
if (widget.crossSlideState != oldWidget.crossSlideState) {
switch (widget.crossSlideState) {
case CrossSlideState.showFirst:
_controller.reverse();
case CrossSlideState.showSecond:
_controller.forward();
}
}
}
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
);
final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
);
@override
Widget build(BuildContext context) {
const Key kFirstChildKey = ValueKey<CrossFadeState>(
CrossFadeState.showFirst,
);
const Key kSecondChildKey = ValueKey<CrossFadeState>(
CrossFadeState.showSecond,
);
final Key topKey;
Widget topChild;
final Animation<double> topAnimation;
final Animation<double> bottomAnimation;
final Animation<Offset> topSlideAnimation;
final Animation<Offset> bottomSlideAnimation;
final Key bottomKey;
Widget bottomChild;
final secondSlideAnimation = _secondAnimation.drive(_kRightMiddleTween);
final firstSlideAnimation = _firstAnimation.drive(_kMiddleLeftTween);
if (_controller.isForwardOrCompleted) {
topKey = kSecondChildKey;
topChild = widget.secondChild;
topAnimation = _secondAnimation;
topSlideAnimation = secondSlideAnimation;
bottomKey = kFirstChildKey;
bottomChild = widget.firstChild;
bottomAnimation = _firstAnimation;
bottomSlideAnimation = firstSlideAnimation;
} else {
topKey = kFirstChildKey;
topChild = widget.firstChild;
topAnimation = _firstAnimation;
topSlideAnimation = firstSlideAnimation;
bottomKey = kSecondChildKey;
bottomChild = widget.secondChild;
bottomAnimation = _secondAnimation;
bottomSlideAnimation = secondSlideAnimation;
}
bottomChild = TickerMode(
key: bottomKey,
enabled: _controller.isAnimating,
child: IgnorePointer(
child: ExcludeSemantics(
child: ExcludeFocus(
excluding: widget.excludeBottomFocus,
child: SlideTransition(
position: bottomSlideAnimation,
child: FadeTransition(
opacity: bottomAnimation,
child: bottomChild,
),
),
),
),
),
);
topChild = TickerMode(
key: topKey,
enabled: true, // Top widget always has its animations enabled.
child: IgnorePointer(
ignoring: false,
child: ExcludeSemantics(
excluding: false,
child: ExcludeFocus(
excluding: false,
child: SlideTransition(
position: topSlideAnimation,
child: FadeTransition(opacity: topAnimation, child: topChild),
),
),
),
),
);
return ClipRect(
child: AnimatedSize(
alignment: widget.alignment,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
curve: widget.sizeCurve,
child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey),
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(
EnumProperty<CrossSlideState>('crossSlideState', widget.crossSlideState),
);
description.add(
DiagnosticsProperty<AnimationController>(
'controller',
_controller,
showName: false,
),
);
description.add(
DiagnosticsProperty<AlignmentGeometry>(
'alignment',
widget.alignment,
defaultValue: Alignment.topCenter,
),
);
}
}

View File

@@ -1,8 +1,9 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'fade_box.dart';
import 'text.dart';
class Info {
@@ -15,7 +16,7 @@ class Info {
class InfoHeader extends StatelessWidget {
final Info info;
final List<Widget> actions;
final EdgeInsetsGeometry? padding;
final EdgeInsets? padding;
const InfoHeader({
super.key,
@@ -26,8 +27,12 @@ class InfoHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
EdgeInsetsGeometry nextPadding = (padding ?? baseInfoEdgeInsets);
if (actions.isNotEmpty) {
nextPadding = nextPadding.subtract(EdgeInsets.symmetric(vertical: 8.ap));
}
return Padding(
padding: padding ?? baseInfoEdgeInsets,
padding: nextPadding,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -61,11 +66,15 @@ class InfoHeader extends StatelessWidget {
),
),
const SizedBox(width: 8),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [...actions],
),
if (actions.isNotEmpty)
SizedBox(
height: globalState.measure.titleSmallHeight + 16.ap,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [...actions],
),
),
],
),
);
@@ -84,11 +93,13 @@ class CommonCard extends StatelessWidget {
this.padding,
this.enterAnimated = false,
this.info,
this.onLongPress,
}) : isSelected = isSelected ?? false;
final bool enterAnimated;
final bool isSelected;
final void Function()? onPressed;
final void Function()? onLongPress;
final Widget? selectWidget;
final Widget child;
final EdgeInsets? padding;
@@ -172,7 +183,7 @@ class CommonCard extends StatelessWidget {
}
final card = OutlinedButton(
onLongPress: null,
onLongPress: onLongPress,
clipBehavior: Clip.antiAlias,
style: ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.zero),

View File

@@ -69,11 +69,13 @@ class FadeThroughBox extends StatelessWidget {
class FadeRotationScaleBox extends StatelessWidget {
final Widget child;
final AlignmentGeometry? alignment;
const FadeRotationScaleBox({super.key, required this.child});
const FadeRotationScaleBox({super.key, required this.child, this.alignment});
@override
Widget build(BuildContext context) {
final realAlignment = alignment ?? Alignment.center;
return AnimatedSwitcher(
duration: commonDuration,
switchInCurve: Curves.easeOutBack,
@@ -87,6 +89,13 @@ class FadeRotationScaleBox extends StatelessWidget {
),
);
},
layoutBuilder: (currentChild, previousChildren) => Stack(
alignment: realAlignment,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
),
child: child,
);
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_svg/svg.dart';
class CommonTargetIcon extends StatelessWidget {
@@ -59,7 +60,7 @@ class _ImageCacheWidgetState extends State<ImageCacheWidget> {
@override
void initState() {
super.initState();
_imageFuture = LocalImageCacheManager().getSingleFile(widget.src);
_imageFuture = DefaultCacheManager().getSingleFile(widget.src);
}
@override

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