Compare commits

...

1 Commits

Author SHA1 Message Date
chen08209 670a98d55f Add custom overwrite
Optimize windows ipc

Optimize windows arm64

Optimize some details

Update core
2026-05-11 14:31:52 +08:00
320 changed files with 31514 additions and 7224 deletions
+93
View File
@@ -0,0 +1,93 @@
Scan `lib/` for hardcoded Chinese strings and internationalize them following the project's ARB conventions.
## Instructions
You are performing i18n extraction for a Flutter project that uses the Flutter Intl plugin with ARB files.
### Project localization context
- **ARB files**: `arb/intl_en.arb`, `arb/intl_zh_CN.arb`, `arb/intl_ja.arb`, `arb/intl_ru.arb`
- **Key conventions**: camelCase, flat keys (no nesting, no `@` metadata), semantic naming
- Short labels: `rule`, `global`, `direct`, `edit`, `confirm`
- Descriptive suffix `Desc`: `themeDesc`, `tunDesc`, `logsDesc`
- Validation suffix: `profileNameNullValidationDesc`
- **Generated code**: `lib/l10n/l10n.dart` (DO NOT edit generated files)
- **Access patterns**:
- In widgets with BuildContext: `context.appLocalizations.key` (import `common.dart`)
- In controllers/providers/non-widget code: `currentAppLocalizations.key` (import `app_localizations.dart`)
### Step 1: Find hardcoded Chinese strings
Use Grep to search `lib/` for Chinese characters. The regex pattern for Chinese characters is `[\x{4e00}-\x{9fff}]`.
Important filters:
- **Exclude** `lib/l10n/` (generated localization files)
- **Exclude** lines that already use `appLocalizations` or `currentAppLocalizations`
- **Exclude** false positives that are NOT Chinese text:
- Arrow symbols: `↑` `↓` `←` `→` (Unicode arrows, not CJK)
- Middle dot: `·` (U+00B7, not CJK)
- Command symbol: `⌘` (U+2318, not CJK)
- Other CJK punctuation that is used as UI symbols, not translatable text
For each match, read the surrounding context (5-10 lines) to understand:
- What the string represents (label, error message, tooltip, dialog text, etc.)
- Whether it's inside a widget (has `BuildContext` available) or non-widget code
- Whether it appears in a `Text()`, `TextSpan()`, `title:`, `label:`, or other text position
### Step 2: Generate ARB keys
For each hardcoded Chinese string:
1. **Choose a key name** following project conventions:
- Use the Chinese meaning to derive an English camelCase key
- Keep it concise: `createConfig` not `createAConfigurationForTheProfile`
- Use `Desc` suffix for descriptive/explanatory text
- Use existing similar keys as reference (check `arb/intl_en.arb` for patterns)
2. **Determine the English translation** (the value for `intl_en.arb`)
3. **Determine the Chinese translation** (the value for `intl_zh_CN.arb`) — this is the original hardcoded string
4. **For ja and ru**: use the English translation as a placeholder (translators will fill these in later)
### Step 3: Update ARB files
For each new key, add it to all 4 ARB files. Read each ARB file first to find the right insertion point (alphabetical order is not required, but appending near the end is clean).
Format: add a new line like `"keyName": "value",` before the closing `}`.
### Step 4: Replace hardcoded strings in Dart files
Replace each hardcoded Chinese string with the appropriate localization call:
- If inside a widget BuildContext: `context.appLocalizations.keyName`
- If in controller/provider code: `currentAppLocalizations.keyName`
Ensure the correct import exists:
- For `context.appLocalizations`: the file should import `common.dart` (check if it already does)
- For `currentAppLocalizations`: the file should import `app_localizations.dart` (check if it already does)
If the file uses string interpolation (e.g., `'创建 $name 配置'`), convert to the ARB parameterized format:
- In ARB: `"keyName": "Create $name config"`
- In Dart: `currentAppLocalizations.keyName(name)`
### Step 5: Report changes
After all modifications, output a summary:
1. List each file modified and what changed
2. List each new ARB key added with its translations
3. Remind the user to run code generation if needed:
```
dart run build_runner build --delete-conflicting-outputs
```
or trigger Flutter Intl regeneration in their IDE.
### Important notes
- Only extract strings that are user-facing UI text. Do NOT extract:
- Comments (lines starting with `//`)
- Log messages (unless they appear in the UI)
- Variable names or identifiers
- String literals used as keys/identifiers (not displayed to users)
- If a Chinese string is split across multiple lines or concatenations, merge them into a single ARB entry
- If a string contains dynamic content (variables, URLs), use ARB parameter syntax `$variableName`
+35 -5
View File
@@ -8,7 +8,33 @@ env:
IS_STABLE: ${{ !contains(github.ref, '-') }}
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: 3.41.9
cache: true
- name: Install dependencies
run: flutter pub get
- name: Analyze
run: flutter analyze --no-fatal-infos
- name: Run tests
run: flutter test --reporter expanded
build:
needs: [ test ]
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -27,9 +53,9 @@ jobs:
- platform: macos
os: macos-latest
arch: arm64
# - platform: windows
# os: windows-11-arm
# arch: arm64
- platform: windows
os: windows-11-arm
arch: arm64
- platform: linux
os: ubuntu-24.04-arm
arch: arm64
@@ -64,19 +90,23 @@ jobs:
cache-dependency-path: |
core/go.sum
- name: Enable git long paths (Windows)
if: startsWith(matrix.os, 'windows')
run: git config --global core.longpaths 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
flutter-version: 3.35.7
flutter-version: 3.41.9
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
flutter-version: 3.44.0-0.1.pre
cache: true
- name: Get Flutter Dependency
+2 -3
View File
@@ -41,9 +41,8 @@ app.*.symbols
# Obfuscation related
app.*.map.json
#AI generated
CLAUDE.md
/.claude
# Claude Code - ignore user-specific settings only
.claude/settings.local.json
# Android Studio will place build artifacts here
+182
View File
@@ -0,0 +1,182 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
FlClash is a multi-platform proxy client based on ClashMeta (mihomo), built with Flutter. Supports Android, Windows,
macOS, and Linux. Material You design with Surfboard-like UI.
## Common Development Commands
### Building
```bash
# Update submodules first (ClashMeta Go core lives in core/Clash.Meta/)
git submodule update --init --recursive
# Build core + Flutter app via setup.dart
dart ./setup.dart android --arch arm64
dart ./setup.dart android --arch arm --arch-name armeabi-v7a
dart ./setup.dart android --arch amd64 --arch-name x86_64
dart ./setup.dart macos --arch arm64
dart ./setup.dart macos --arch amd64
dart ./setup.dart linux --arch amd64
dart ./setup.dart windows --arch amd64
# Build only the Go core (skip Flutter packaging)
dart ./setup.dart android --arch arm64 --out core
dart ./setup.dart macos --arch arm64 --out core
```
### Flutter Development
```bash
flutter pub get
flutter run # Run on connected device/desktop
flutter test # Run all tests (use flutter test, not dart test — models pull in Flutter types)
```
### Code Generation
Required after modifying models, providers, or database schema:
```bash
dart run build_runner build --delete-conflicting-outputs
dart run build_runner watch # Continuous regeneration
```
Code generation covers: Riverpod providers (`riverpod_generator`), models (`freezed`, `json_serializable`), and database
tables (`drift_dev`).
### Testing
Tests use `package:test/test.dart` for pure Dart logic (common utils, models) and `flutter_test` for provider/widget tests.
`mocktail` is the mocking framework.
```bash
flutter test test/models/ # Model serialization & extension round-trip tests
flutter test test/core/ # CoreController tests (mocked CoreHandlerInterface)
flutter test test/providers/ # Riverpod provider tests (config & app state notifiers)
flutter test test/common/ # Utility function tests (utils, string, iterable, fixed, etc.)
flutter test test/database/ # Database type converter tests
```
**Mocking `CoreHandlerInterface`:** Use `CoreController.test(mock)` to inject a mock interface. Call
`CoreController.resetInstance()` in `tearDown` to clean up the singleton between tests. Remember to
`registerFallbackValue()` for freezed params used with `any()` matchers.
**Provider tests:** Use `ProviderContainer` directly (no widget tree needed for simple notifiers). The Riverpod
generated `update()` method takes a callback: `notifier.update((state) => newValue)`.
**Model round-trip tests:** Always go through `jsonEncode`/`jsonDecode` when testing freezed models with
nested objects — `toJson()` stores child objects directly (not as maps), so direct `fromJson(toJson())`
fails for nested freezed types.
### Build Dependencies
**Linux:** `sudo apt-get install libayatana-appindicator3-dev libkeybinder-3.0-dev`
**Windows:** GCC and Inno Setup. `ANDROID_NDK` env var for Android builds.
**macOS:** `npm install -g appdmg` for DMG creation.
## Architecture
### Core Integration (Go ClashMeta <-> Flutter)
This is the most important architectural concept. The Go proxy core (`core/`) operates in two modes:
- **Android (lib mode):** Go core compiled as C shared library (`libclash.so`) via `go build -buildmode=c-shared` with
CGO. Flutter calls it via FFI through the `service` plugin. Dart-side: `lib/core/lib.dart` (`CoreLib` class).
- **Desktop (core mode):** Go core runs as a separate process with `CGO_ENABLED=0`. Flutter communicates via
JSON-over-socket (Unix socket on macOS/Linux, TCP on Windows). Dart-side: `lib/core/service.dart` (`CoreService`
class).
`lib/core/controller.dart` (`CoreController`) selects the implementation based on platform. `lib/core/interface.dart`
defines the shared `CoreHandlerInterface`.
Go core key files: `core/hub.go` (handler functions), `core/action.go` (dispatch), `core/lib.go` (CGO exports),
`core/server.go` (socket server).
### State Management (Riverpod)
Provider files in `lib/providers/`:
- `app.dart` - Runtime/UI state (logs, traffic, delays, loading, navigation)
- `config.dart` - Persistent config providers (app settings, theme, VPN, proxy style)
- `state.dart` - Derived/computed providers (navigation, proxy, tray, color scheme)
- `action.dart` - Business logic notifiers (setup, backup, core lifecycle, proxy selection)
- `database.dart` - Drift database provider wrappers
`globalState` (`lib/state.dart`) is a singleton holding app lifecycle, timers, theme, and the start/stop state.
Providers are generated into `lib/providers/generated/`.
### Database (Drift/SQLite)
Type-safe SQLite via Drift in `lib/database/`. Tables: `Profiles`, `Scripts`, `ProxyGroups`, `GlobalRules`,
`ProfileAddedRules`, `ProfileCustomRules`, `ProfileDisabledRuleIds`, `Icons`, `Links`. Uses fractional indexing for
ordering.
### Manager Stack (Widget Tree)
Managers are nested InheritedWidgets/StatefulWidgets in `lib/application.dart`:
```
AppEnvManager > StatusManager > ThemeManager
> [Desktop: WindowManager > TrayManager > HotKeyManager > ProxyManager]
> ConnectivityManager > CoreManager > AppStateManager
> [Mobile: AndroidManager > VpnManager | Desktop: WindowHeaderContainer]
```
Each manager in `lib/manager/` handles a specific platform concern. Desktop-only managers are conditionally inserted.
### Core Controller + Actions
`lib/core/controller.dart` (`CoreController`) is a singleton facade over `CoreHandlerInterface`. All 25+ public methods
delegate to the platform-specific interface (Android FFI or desktop socket). Has `@visibleForTesting` constructor and
`resetInstance()` for test injection.
Business logic lives in Riverpod notifier classes in `lib/providers/action.dart` (~960 lines, should be split):
- `CommonAction` — update check, common UI operations
- `SetupAction` — config setup, TUN management
- `BackupAction` — backup/restore with WebDAV sync
- `CoreAction` — core lifecycle (init, connect, restart, shutdown)
- `SystemAction` — system integration (tray, exit, brightness)
- `StoreAction` — profile storage operations
- `ThemeAction` — theme state updates
- `ProxiesAction` — group management, proxy selection
- `ProfilesAction` — profile CRUD, auto-update, import
### Platform Managers (`lib/manager/`)
Desktop: `WindowManager`, `TrayManager`, `HotKeyManager`, `ProxyManager`
Mobile: `AndroidManager`, `TileManager`, `VpnManager`
Shared: `ConnectivityManager`, `CoreManager`, `AppStateManager`, `StatusManager`, `ThemeManager`
### Local Plugins (`plugins/`)
- `proxy` - System proxy configuration
- `rust_api` - Flutter Rust Bridge FFI plugin (named pipe / local socket communication)
- `tray_manager` - System tray (forked/custom)
- `wifi_ssid` - Wi-Fi SSID detection
- `window_ext` - Window extensions
- `flutter_distributor` - App packaging/distribution
### Rust Helper Service (`services/helper/`)
Windows-only privileged helper for starting the core as admin and managing TUN. Built with
`cargo build --release --features windows-service`. Token-based auth with Flutter app.
### Localization
ARB files in `arb/`. Generated via `flutter_intl` into `lib/l10n/`. Use `AppLocalizations.of(context)!` for strings.
**Supported locales:** `en`, `zh_CN`, `ja`, `ru`
**Access patterns:**
- In widgets with BuildContext: `context.appLocalizations.key` (import `common.dart`)
- In controllers/providers/non-widget code: `currentAppLocalizations.key` (import `app_localizations.dart`)
+34 -1
View File
@@ -1,10 +1,43 @@
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- build/**
- lib/l10n/intl/**
- lib/**/generated/**
- plugins/**
errors:
invalid_annotation_target: ignore
linter:
rules:
prefer_single_quotes: true
# Style
prefer_single_quotes: true
require_trailing_commas: true
sort_child_properties_last: true
# Avoid unnecessary code
unnecessary_new: true
unnecessary_this: true
unnecessary_const: true
avoid_print: true
avoid_unnecessary_containers: true
sized_box_for_whitespace: true
# Prefer best practices
prefer_const_constructors: true
prefer_const_declarations: true
prefer_final_locals: true
prefer_final_in_for_each: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_interpolation_to_compose_strings: true
# Type safety
always_declare_return_types: true
annotate_overrides: true
use_key_in_widget_constructors: true
no_leading_underscores_for_local_identifiers: true
# Async
use_build_context_synchronously: true
unnecessary_await_in_return: true
+2
View File
@@ -16,12 +16,14 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
<application
android:name=".Application"
@@ -9,6 +9,8 @@ import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.net.VpnService
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
@@ -157,6 +159,18 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
result.success(true)
}
"isBatteryOptimizationDisabled" -> {
result.success(isBatteryOptimizationDisabled())
}
"openBatteryOptimizationSettings" -> {
result.success(openBatteryOptimizationSettings())
}
"openAppSettings" -> {
result.success(openAppSettings())
}
else -> {
result.notImplemented()
}
@@ -196,6 +210,36 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
GlobalState.application.showToast(message)
}
private fun isBatteryOptimizationDisabled(): Boolean {
val powerManager = getSystemService(GlobalState.application, PowerManager::class.java)
return powerManager?.isIgnoringBatteryOptimizations(GlobalState.application.packageName)
?: false
}
private fun openBatteryOptimizationSettings(): Boolean {
return try {
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
data = android.net.Uri.parse("package:${GlobalState.application.packageName}")
}
activityRef?.get()?.startActivity(intent)
true
} catch (_: Exception) {
false
}
}
private fun openAppSettings(): Boolean {
return try {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = android.net.Uri.parse("package:${GlobalState.application.packageName}")
}
activityRef?.get()?.startActivity(intent)
true
} catch (_: Exception) {
false
}
}
@Suppress("DEPRECATION")
private fun updateExcludeFromRecents(value: Boolean?) {
val am = getSystemService(GlobalState.application, ActivityManager::class.java)
+1 -1
View File
@@ -4,7 +4,7 @@ firebaseBom = "34.2.0"
minSdk = "23"
targetSdk = "36"
compileSdk = "36"
ndkVersion = "28.0.13004108"
ndkVersion = "28.2.13676358"
coreKtx = "1.17.0"
annotationJvm = "1.9.1"
coreSplashscreen = "1.0.1"
@@ -85,22 +85,24 @@ class NotificationModule(private val service: Service) : Module() {
private val notificationBuilder: NotificationCompat.Builder by lazy {
val intent = Intent().setComponent(Components.MAIN_ACTIVITY)
with(
NotificationCompat.Builder(
service, GlobalState.NOTIFICATION_CHANNEL
)
) {
setSmallIcon(R.drawable.ic)
NotificationCompat.Builder(
service, GlobalState.NOTIFICATION_CHANNEL
).apply {
setSmallIcon(R.drawable.ic_service)
setContentTitle("FlClash")
setContentIntent(intent.toPendingIntent)
setPriority(NotificationCompat.PRIORITY_HIGH)
setCategory(NotificationCompat.CATEGORY_SERVICE)
setOngoing(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
setOngoing(true)
setShowWhen(true)
setOnlyAlertOnce(true)
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// setRequestPromotedOngoing(true)
// }
}
}
+110 -1
View File
@@ -476,5 +476,114 @@
"restoreOnlyConfig": "Restore configuration files only",
"restoreAllData": "Restore all data",
"addProfile": "Add Profile",
"delayTest": "Delay Test"
"delayTest": "Delay Test",
"proxyGroupEmpty": "Proxy group is empty",
"proxyGroupNameEmpty": "Proxy group name cannot be empty",
"proxyGroupNameDuplicate": "Proxy group name is duplicate",
"confirmExitWindow": "Are you sure you want to exit the current window?",
"dataChangedSave": "Data changes detected, do you want to save?",
"selectProxyProviders": "Select proxy providers",
"proxyFilter": "Proxy filter",
"optional": "Optional",
"maxFailedTimes": "Max failed times",
"testInterval": "Test interval",
"excludeProxyFilter": "Exclude proxy filter",
"excludeType": "Exclude type",
"expectedStatus": "Expected status",
"selectProxies": "Select proxies",
"inputProxyGroupName": "Input proxy group name",
"hideFromList": "Hide from list",
"testWhenUsed": "Test when used",
"disableUDP": "Disable UDP",
"confirmDeleteProxyGroup": "Are you sure you want to delete the current proxy group?",
"ruleEmpty": "Rule is empty",
"inputRuleContent": "Input rule content",
"ruleSet": "Rule set",
"selectRuleSet": "Please select rule set",
"splitStrategy": "Split strategy",
"selectSplitStrategy": "Please select split strategy",
"selectSubRule": "Please select sub rule",
"noResolveHostname": "No resolve hostname",
"matchSourceIp": "Match source IP",
"basicInfo": "Basic info",
"additionalParameters": "Additional parameters",
"proxyType": "Proxy type",
"basicStrategy": "Basic strategy",
"editProxy": "Edit proxy",
"includeAllProxyProviders": "Include all proxy providers",
"includeAllProxyProvidersTip": "When enabled, it will override the imported proxy providers",
"addProxyProviders": "Add proxy providers",
"includeAllProxies": "Include all proxies",
"includeAllProxiesTip": "Import all proxies not containing proxy groups, additional proxy groups can be added below",
"proxiesEmpty": "Proxies is empty",
"addProxies": "Add proxies",
"addProxyGroup": "Add proxy group",
"editProxyGroup": "Edit proxy group",
"confirmOverwriteTip": "Existing data will be overwritten after confirmation",
"configDataDetected": "Data detected in configuration",
"quickFill": "Quick fill",
"iconUrl": "Icon URL",
"iconRecords": "Icon records",
"noRecords": "No records",
"custom": "Custom",
"ruleActionDomainDesc": "Match full domain",
"ruleActionDomainSuffixDesc": "Match domain suffix",
"ruleActionDomainKeywordDesc": "Match domain keyword",
"ruleActionDomainRegexDesc": "Wildcard match, only supports * and ? wildcards",
"ruleActionGeositeDesc": "Match domains within Geosite",
"ruleActionIpCidrDesc": "Match IP address range",
"ruleActionIpCidr6Desc": "Match IP address range, IP-CIDR6 is just an alias",
"ruleActionIpSuffixDesc": "Match IP suffix range",
"ruleActionIpAsnDesc": "Match IP's ASN",
"ruleActionGeoipDesc": "Match IP's country code",
"ruleActionSrcGeoipDesc": "Match source IP's country code",
"ruleActionSrcIpAsnDesc": "Match source IP's ASN",
"ruleActionSrcIpCidrDesc": "Match source IP address range",
"ruleActionSrcIpSuffixDesc": "Match source IP suffix range",
"ruleActionDstPortDesc": "Match request target port range",
"ruleActionSrcPortDesc": "Match request source port range",
"ruleActionInPortDesc": "Match inbound port",
"ruleActionInTypeDesc": "Match inbound type",
"ruleActionInUserDesc": "Match inbound username, supports multiple usernames separated by /",
"ruleActionInNameDesc": "Match inbound name",
"ruleActionProcessPathDesc": "Match using full process path",
"ruleActionProcessPathRegexDesc": "Match using process path regex",
"ruleActionProcessNameDesc": "Match using process name, matches package name on Android",
"ruleActionProcessNameRegexDesc": "Match using process name regex, matches package name on Android",
"ruleActionUidDesc": "Match Linux USER ID",
"ruleActionNetworkDesc": "Match TCP or UDP",
"ruleActionDscpDesc": "Match DSCP mark (tproxy udp inbound only)",
"ruleActionRuleSetDesc": "Reference rule set, requires rule-providers configuration",
"ruleActionAndDesc": "Logical rule AND",
"ruleActionOrDesc": "Logical rule OR",
"ruleActionNotDesc": "Logical rule NOT",
"ruleActionSubRuleDesc": "Match to sub-rule, pay attention to the use of parentheses",
"ruleActionMatchDesc": "Match all requests, no conditions needed",
"subRuleEmpty": "Sub rule is empty",
"proxyProvidersNotEmpty": "Proxy providers cannot be empty",
"contentNotEmpty": "Content cannot be empty",
"subRuleNotEmpty": "Sub rule cannot be empty",
"splitStrategyNotEmpty": "Split strategy cannot be empty",
"proxyProvidersEmpty": "Proxy providers is empty",
"timeout": "Timeout",
"invalidSubRule": "{subRule} is an invalid SUB_RULE",
"invalidPolicy": "{target} is an invalid policy",
"invalidProxyProvider": "{providerName} is an invalid proxy provider",
"invalidProxy": "{proxyName} is an invalid proxy",
"proxyGroupDetectedAbnormal": "Detected current proxy group is abnormal",
"proxyProviderDetectedAbnormal": "Detected selected proxy providers are abnormal",
"proxyDetectedAbnormal": "Detected selected proxies are abnormal",
"createProfile": "Create Profile",
"locationPermissionRequired": "Location Permission Required",
"locationPermissionGuide": "1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check {appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.",
"prerequisites": "Prerequisites",
"ignoreBatteryOptimization": "Ignore Battery Optimization",
"batteryOptimizationDesc": "To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
"locationPermission": "Location Permission",
"locationPermissionDesc": "According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
"excludeSsids": "Exclude SSIDs",
"excludeSsidsDesc": "When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
"ssidsEmpty": "SSIDs is empty",
"onDemand": "On Demand",
"onDemandDesc": "Configure the program running state for specific scenarios"
}
+111 -2
View File
@@ -477,5 +477,114 @@
"restoreOnlyConfig": "設定ファイルのみを復元する",
"restoreAllData": "すべてのデータを復元する",
"addProfile": "プロファイルを追加",
"delayTest": "遅延テスト"
}
"delayTest": "遅延テスト",
"proxyGroupEmpty": "プロキシグループが空です",
"proxyGroupNameEmpty": "プロキシグループ名は空にできません",
"proxyGroupNameDuplicate": "プロキシグループ名が重複しています",
"confirmExitWindow": "現在のウィンドウを閉じてもよろしいですか?",
"dataChangedSave": "データの変更が検出されました。保存しますか?",
"selectProxyProviders": "プロキシプロバイダーを選択",
"proxyFilter": "プロキシフィルター",
"optional": "オプション",
"maxFailedTimes": "最大失敗回数",
"testInterval": "テスト間隔",
"excludeProxyFilter": "除外プロキシフィルター",
"excludeType": "除外タイプ",
"expectedStatus": "期待されるステータス",
"selectProxies": "プロキシを選択",
"inputProxyGroupName": "プロキシグループ名を入力",
"hideFromList": "リストから隠す",
"testWhenUsed": "使用時にテスト",
"disableUDP": "UDPを無効化",
"confirmDeleteProxyGroup": "現在のプロキシグループを削除してもよろしいですか?",
"ruleEmpty": "ルールが空です",
"inputRuleContent": "ルール内容を入力",
"ruleSet": "ルールセット",
"selectRuleSet": "ルールセットを選択してください",
"splitStrategy": "分流戦略",
"selectSplitStrategy": "分流戦略を選択してください",
"selectSubRule": "サブルールを選択してください",
"noResolveHostname": "ホスト名を解決しない",
"matchSourceIp": "送信元IPをマッチング",
"basicInfo": "基本情報",
"additionalParameters": "追加パラメータ",
"proxyType": "プロキシタイプ",
"basicStrategy": "基本戦略",
"editProxy": "プロキシを編集",
"includeAllProxyProviders": "すべてのプロキシプロバイダーを含める",
"includeAllProxyProvidersTip": "有効にすると、インポートされたプロキシプロバイダーを上書きします",
"addProxyProviders": "プロキシプロバイダーを追加",
"includeAllProxies": "すべてのプロキシを含める",
"includeAllProxiesTip": "プロキシグループに含まれないすべてのプロキシをインポートします。下でさらにプロキシグループを追加できます",
"proxiesEmpty": "プロキシが空です",
"addProxies": "プロキシを追加",
"addProxyGroup": "プロキシグループを追加",
"editProxyGroup": "プロキシグループを編集",
"confirmOverwriteTip": "確認後、既存のデータは上書きされます",
"configDataDetected": "設定内にデータが検出されました",
"quickFill": "クイック入力",
"iconUrl": "アイコンURL",
"iconRecords": "アイコン履歴",
"noRecords": "履歴なし",
"custom": "カスタム",
"ruleActionDomainDesc": "完全なドメインをマッチング",
"ruleActionDomainSuffixDesc": "ドメイン接尾辞をマッチング",
"ruleActionDomainKeywordDesc": "ドメインキーワードをマッチング",
"ruleActionDomainRegexDesc": "ワイルドカードマッチング(*と?のみサポート)",
"ruleActionGeositeDesc": "Match domains within Geosite",
"ruleActionIpCidrDesc": "IPアドレス範囲をマッチング",
"ruleActionIpCidr6Desc": "IPアドレス範囲をマッチング(IP-CIDR6はエイリアスです)",
"ruleActionIpSuffixDesc": "IP接尾辞範囲をマッチング",
"ruleActionIpAsnDesc": "IPのASNをマッチング",
"ruleActionGeoipDesc": "IPの国コードをマッチング",
"ruleActionSrcGeoipDesc": "送信元IPの国コードをマッチング",
"ruleActionSrcIpAsnDesc": "送信元IPのASNをマッチング",
"ruleActionSrcIpCidrDesc": "送信元IPアドレス範囲をマッチング",
"ruleActionSrcIpSuffixDesc": "送信元IP接尾辞範囲をマッチング",
"ruleActionDstPortDesc": "宛先ポート範囲をマッチング",
"ruleActionSrcPortDesc": "送信元ポート範囲をマッチング",
"ruleActionInPortDesc": "インバウンドポートをマッチング",
"ruleActionInTypeDesc": "インバウンドタイプをマッチング",
"ruleActionInUserDesc": "インバウンドユーザー名をマッチング(/で複数指定可)",
"ruleActionInNameDesc": "インバウンド名をマッチング",
"ruleActionProcessPathDesc": "フルプロセスパスでマッチング",
"ruleActionProcessPathRegexDesc": "プロセスパス正規表現でマッチング",
"ruleActionProcessNameDesc": "プロセス名でマッチング(Androidではパッケージ名)",
"ruleActionProcessNameRegexDesc": "プロセス名正規表現でマッチング(Androidではパッケージ名)",
"ruleActionUidDesc": "Linux USER IDをマッチング",
"ruleActionNetworkDesc": "TCPまたはUDPをマッチング",
"ruleActionDscpDesc": "DSCPマークをマッチング (tproxy udp inboundのみ)",
"ruleActionRuleSetDesc": "ルールセットを参照。rule-providersの設定が必要",
"ruleActionAndDesc": "論理ルール AND",
"ruleActionOrDesc": "論理ルール OR",
"ruleActionNotDesc": "論理ルール NOT",
"ruleActionSubRuleDesc": "サブルールにマッチング。括弧の使用に注意",
"ruleActionMatchDesc": "すべてのリクエストにマッチ(条件なし)",
"subRuleEmpty": "サブルールが空です",
"proxyProvidersNotEmpty": "プロキシプロバイダーは空にできません",
"contentNotEmpty": "内容は空にできません",
"subRuleNotEmpty": "サブルールは空にできません",
"splitStrategyNotEmpty": "分流戦略は空にできません",
"proxyProvidersEmpty": "プロキシプロバイダーが空です",
"timeout": "タイムアウト",
"invalidSubRule": "{subRule} は無効なSUB_RULEです",
"invalidPolicy": "{target} は無効なポリシーです",
"invalidProxyProvider": "{providerName} は無効なプロキシプロバイダーです",
"invalidProxy": "{proxyName} は無効なプロキシです",
"proxyGroupDetectedAbnormal": "現在のプロキシグループが異常であることを検出しました",
"proxyProviderDetectedAbnormal": "選択されたプロキシプロバイダーに異常があることを検出しました",
"proxyDetectedAbnormal": "選択されたプロキシに異常があることを検出しました",
"createProfile": "Create Profile",
"locationPermissionRequired": "Location Permission Required",
"locationPermissionGuide": "1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check {appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.",
"prerequisites": "Prerequisites",
"ignoreBatteryOptimization": "Ignore Battery Optimization",
"batteryOptimizationDesc": "To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
"locationPermission": "Location Permission",
"locationPermissionDesc": "According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
"excludeSsids": "Exclude SSIDs",
"excludeSsidsDesc": "When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
"ssidsEmpty": "SSIDs is empty",
"onDemand": "On Demand",
"onDemandDesc": "Configure the program running state for specific scenarios"
}
+111 -2
View File
@@ -485,5 +485,114 @@
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
"restoreAllData": "Восстановить все данные",
"addProfile": "Добавить профиль",
"delayTest": "Тест задержки"
}
"delayTest": "Тест задержки",
"proxyGroupEmpty": "Группа прокси пуста",
"proxyGroupNameEmpty": "Имя группы прокси не может быть пустым",
"proxyGroupNameDuplicate": "Имя группы прокси дублируется",
"confirmExitWindow": "Вы уверены, что хотите выйти из текущего окна?",
"dataChangedSave": "Обнаружены изменения данных, хотите сохранить?",
"selectProxyProviders": "Выбрать провайдеров прокси",
"proxyFilter": "Фильтр прокси",
"optional": "Необязательно",
"maxFailedTimes": "Макс. количество неудач",
"testInterval": "Интервал тестирования",
"excludeProxyFilter": "Исключить фильтр прокси",
"excludeType": "Тип исключения",
"expectedStatus": "Ожидаемый статус",
"selectProxies": "Выбрать прокси",
"inputProxyGroupName": "Введите имя группы прокси",
"hideFromList": "Скрыть из списка",
"testWhenUsed": "Тестировать при использовании",
"disableUDP": "Отключить UDP",
"confirmDeleteProxyGroup": "Вы уверены, что хотите удалить текущую группу прокси?",
"ruleEmpty": "Правило пусто",
"inputRuleContent": "Введите содержимое правила",
"ruleSet": "Набор правил",
"selectRuleSet": "Пожалуйста, выберите набор правил",
"splitStrategy": "Стратегия разделения",
"selectSplitStrategy": "Пожалуйста, выберите стратегию разделения",
"selectSubRule": "Пожалуйста, выберите подправило",
"noResolveHostname": "Не разрешать имя хоста",
"matchSourceIp": "Сопоставить исходный IP",
"basicInfo": "Основная информация",
"additionalParameters": "Дополнительные параметры",
"proxyType": "Тип прокси",
"basicStrategy": "Базовая стратегия",
"editProxy": "Редактировать прокси",
"includeAllProxyProviders": "Включить всех провайдеров прокси",
"includeAllProxyProvidersTip": "При включении это переопределит импортированных провайдеров прокси",
"addProxyProviders": "Добавить провайдеров прокси",
"includeAllProxies": "Включить все прокси",
"includeAllProxiesTip": "Импорт всех прокси, не содержащих группы прокси, дополнительные группы прокси можно добавить ниже",
"proxiesEmpty": "Список прокси пуст",
"addProxies": "Добавить прокси",
"addProxyGroup": "Добавить группу прокси",
"editProxyGroup": "Редактировать группу прокси",
"confirmOverwriteTip": "Существующие данные будут перезаписаны после подтверждения",
"configDataDetected": "Данные обнаружены в конфигурации",
"quickFill": "Быстрое заполнение",
"iconUrl": "URL иконки",
"iconRecords": "История иконок",
"noRecords": "Нет записей",
"custom": "Пользовательский",
"ruleActionDomainDesc": "Сопоставить полный домен",
"ruleActionDomainSuffixDesc": "Сопоставить суффикс домена",
"ruleActionDomainKeywordDesc": "Сопоставить ключевое слово домена",
"ruleActionDomainRegexDesc": "Сопоставление по маске, поддерживает только * и ?",
"ruleActionGeositeDesc": "Сопоставить домены внутри Geosite",
"ruleActionIpCidrDesc": "Сопоставить диапазон IP-адресов",
"ruleActionIpCidr6Desc": "Сопоставить диапазон IP-адресов (IP-CIDR6 — это просто псевдоним)",
"ruleActionIpSuffixDesc": "Сопоставить диапазон суффиксов IP",
"ruleActionIpAsnDesc": "Сопоставить ASN IP-адреса",
"ruleActionGeoipDesc": "Сопоставить код страны IP-адреса",
"ruleActionSrcGeoipDesc": "Сопоставить код страны исходного IP",
"ruleActionSrcIpAsnDesc": "Сопоставить ASN исходного IP",
"ruleActionSrcIpCidrDesc": "Сопоставить диапазон исходных IP-адресов",
"ruleActionSrcIpSuffixDesc": "Сопоставить диапазон исходных суффиксов IP",
"ruleActionDstPortDesc": "Сопоставить диапазон портов назначения запроса",
"ruleActionSrcPortDesc": "Сопоставить диапазон портов источника запроса",
"ruleActionInPortDesc": "Сопоставить входящий порт",
"ruleActionInTypeDesc": "Сопоставить входящий тип",
"ruleActionInUserDesc": "Сопоставить входящее имя пользователя, поддерживает несколько имен через /",
"ruleActionInNameDesc": "Сопоставить входящее имя",
"ruleActionProcessPathDesc": "Сопоставить по полному пути процесса",
"ruleActionProcessPathRegexDesc": "Сопоставить по регулярному выражению пути процесса",
"ruleActionProcessNameDesc": "Сопоставить по имени процесса, на Android соответствует имени пакета",
"ruleActionProcessNameRegexDesc": "Сопоставить по регулярному выражению имени процесса, на Android соответствует имени пакета",
"ruleActionUidDesc": "Сопоставить Linux USER ID",
"ruleActionNetworkDesc": "Сопоставить TCP или UDP",
"ruleActionDscpDesc": "Сопоставить метку DSCP (только для tproxy udp inbound)",
"ruleActionRuleSetDesc": "Ссылка на набор правил, требуется настройка rule-providers",
"ruleActionAndDesc": "Логическое правило AND",
"ruleActionOrDesc": "Логическое правило OR",
"ruleActionNotDesc": "Логическое правило NOT",
"ruleActionSubRuleDesc": "Сопоставить с подправилом, обратите внимание на использование скобок",
"ruleActionMatchDesc": "Сопоставить все запросы, условия не требуются",
"subRuleEmpty": "Подправило пусто",
"proxyProvidersNotEmpty": "Провайдеры прокси не могут быть пустыми",
"contentNotEmpty": "Содержимое не может быть пустым",
"subRuleNotEmpty": "Подправило не может быть пустым",
"splitStrategyNotEmpty": "Стратегия разделения не может быть пустой",
"proxyProvidersEmpty": "Провайдеры прокси пусты",
"timeout": "Таймаут",
"invalidSubRule": "{subRule} является недопустимым подправилом",
"invalidPolicy": "{target} является недопустимой политикой",
"invalidProxyProvider": "{providerName} является недопустимым провайдером прокси",
"invalidProxy": "{proxyName} является недопустимым прокси",
"proxyGroupDetectedAbnormal": "Обнаружена аномалия текущей группы прокси",
"proxyProviderDetectedAbnormal": "Обнаружена аномалия выбранных провайдеров прокси",
"proxyDetectedAbnormal": "Обнаружена аномалия выбранных прокси",
"createProfile": "Create Profile",
"locationPermissionRequired": "Location Permission Required",
"locationPermissionGuide": "1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check {appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.",
"prerequisites": "Prerequisites",
"ignoreBatteryOptimization": "Ignore Battery Optimization",
"batteryOptimizationDesc": "To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
"locationPermission": "Location Permission",
"locationPermissionDesc": "According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
"excludeSsids": "Exclude SSIDs",
"excludeSsidsDesc": "When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
"ssidsEmpty": "SSIDs is empty",
"onDemand": "On Demand",
"onDemandDesc": "Configure the program running state for specific scenarios"
}
+113 -4
View File
@@ -199,7 +199,7 @@
"sort": "排序",
"columns": "列数",
"proxiesSetting": "代理设置",
"proxyGroup": "代理组",
"proxyGroup": "策略组",
"go": "前往",
"externalLink": "外部链接",
"otherContributors": "其他贡献者",
@@ -242,8 +242,8 @@
"loopback": "回环解锁工具",
"loopbackDesc": "用于UWP回环解锁",
"providers": "提供者",
"proxyProviders": "代理提供者",
"ruleProviders": "规则提供者",
"proxyProviders": "代理集",
"ruleProviders": "规则集",
"overrideDns": "覆写DNS",
"overrideDnsDesc": "开启后将覆盖配置中的DNS选项",
"status": "状态",
@@ -477,5 +477,114 @@
"restoreOnlyConfig": "仅恢复配置文件",
"restoreAllData": "恢复所有数据",
"addProfile": "添加配置",
"delayTest": "延迟测试"
"delayTest": "延迟测试",
"proxyGroupEmpty": "策略组为空",
"proxyGroupNameEmpty": "策略组名称不能为空",
"proxyGroupNameDuplicate": "策略组名称重复",
"confirmExitWindow": "确定要退出当前窗口吗?",
"dataChangedSave": "检测到数据有更改,是否保存",
"selectProxyProviders": "选择代理集",
"proxyFilter": "节点过滤器",
"optional": "可选",
"maxFailedTimes": "最大失败次数",
"testInterval": "测试间隔",
"excludeProxyFilter": "排除节点过滤器",
"excludeType": "排除类型",
"expectedStatus": "预期状态",
"selectProxies": "选择代理",
"inputProxyGroupName": "输入策略组名称",
"hideFromList": "从列表中隐藏",
"testWhenUsed": "使用时测试",
"disableUDP": "禁用UDP",
"confirmDeleteProxyGroup": "确定要删除当前策略组吗?",
"ruleEmpty": "规则为空",
"inputRuleContent": "输入规则内容",
"ruleSet": "规则集",
"selectRuleSet": "请选择规则集",
"splitStrategy": "分流策略",
"selectSplitStrategy": "请选择分流策略",
"selectSubRule": "请选择子规则",
"noResolveHostname": "不解析主机名",
"matchSourceIp": "匹配来源IP",
"basicInfo": "基础信息",
"additionalParameters": "附加参数",
"proxyType": "代理类型",
"basicStrategy": "基础策略",
"editProxy": "编辑代理",
"includeAllProxyProviders": "包含所有代理集",
"includeAllProxyProvidersTip": "开启后将覆盖引入的代理集",
"addProxyProviders": "添加代理集",
"includeAllProxies": "包含所有代理",
"includeAllProxiesTip": "引入不包含策略组的所有代理,可在下方额外添加策略组",
"proxiesEmpty": "代理为空",
"addProxies": "添加代理",
"addProxyGroup": "添加策略组",
"editProxyGroup": "编辑策略组",
"confirmOverwriteTip": "确定后将会覆盖已有数据",
"configDataDetected": "检测到配置中存在数据",
"quickFill": "一键填入",
"iconUrl": "图标链接",
"iconRecords": "图标记录",
"noRecords": "暂无记录",
"custom": "自定义",
"ruleActionDomainDesc": "匹配完整域名",
"ruleActionDomainSuffixDesc": "匹配域名后缀",
"ruleActionDomainKeywordDesc": "匹配域名关键字",
"ruleActionDomainRegexDesc": "通配符匹配,仅支持*和?通配符",
"ruleActionGeositeDesc": "匹配 Geosite 内的域名",
"ruleActionIpCidrDesc": "匹配 IP 地址范围",
"ruleActionIpCidr6Desc": "匹配 IP 地址范围, IP-CIDR6 只是一个别名",
"ruleActionIpSuffixDesc": "匹配 IP 后缀范围",
"ruleActionIpAsnDesc": "匹配 IP 所属 ASN",
"ruleActionGeoipDesc": "匹配 IP 所属国家代码",
"ruleActionSrcGeoipDesc": "匹配来源 IP 所属国家代码",
"ruleActionSrcIpAsnDesc": "匹配来源 IP 所属 ASN",
"ruleActionSrcIpCidrDesc": "匹配来源 IP 地址范围",
"ruleActionSrcIpSuffixDesc": "匹配来源 IP 后缀范围",
"ruleActionDstPortDesc": "匹配请求目标端口范围",
"ruleActionSrcPortDesc": "匹配请求来源端口范围",
"ruleActionInPortDesc": "匹配入站端口",
"ruleActionInTypeDesc": "匹配入站类型",
"ruleActionInUserDesc": "匹配入站用户名,支持使用 / 分隔多个用户名",
"ruleActionInNameDesc": "匹配入站名称",
"ruleActionProcessPathDesc": "使用完整进程路径匹配",
"ruleActionProcessPathRegexDesc": "使用进程路径正则表达式匹配",
"ruleActionProcessNameDesc": "使用进程匹配,在Android平台可以匹配包名",
"ruleActionProcessNameRegexDesc": "使用进程名称正则表达式匹配,在Android平台可以匹配包名",
"ruleActionUidDesc": "匹配 Linux USER ID",
"ruleActionNetworkDesc": "匹配TCP或者UDP",
"ruleActionDscpDesc": "匹配DSCP标记 (仅限 tproxy udp 入站)",
"ruleActionRuleSetDesc": "引用规则集合,需配置rule-providers",
"ruleActionAndDesc": "逻辑规则 AND",
"ruleActionOrDesc": "逻辑规则 OR",
"ruleActionNotDesc": "逻辑规则 NOT",
"ruleActionSubRuleDesc": "匹配至子规则,需要注意括号的使用",
"ruleActionMatchDesc": "匹配所有请求,无需条件",
"subRuleEmpty": "子规则为空",
"proxyProvidersNotEmpty": "代理集不能为空",
"contentNotEmpty": "内容不能为空",
"subRuleNotEmpty": "子规则不能为空",
"splitStrategyNotEmpty": "分流策略不能为空",
"proxyProvidersEmpty": "代理集为空",
"timeout": "超时",
"invalidSubRule": "{subRule} 是一个无效的SUB_RULE",
"invalidPolicy": "{target} 是一个无效的策略",
"invalidProxyProvider": "{providerName} 是一个无效的代理集",
"invalidProxy": "{proxyName} 是一个无效的代理",
"proxyGroupDetectedAbnormal": "检测到当前策略组异常",
"proxyProviderDetectedAbnormal": "检测到选中的代理集存在异常",
"proxyDetectedAbnormal": "检测到选中的代理存在异常",
"createProfile": "创建配置",
"locationPermissionRequired": "需要位置权限",
"locationPermissionGuide": "1. 打开 系统设置 > 隐私与安全性\n2. 选择 定位服务\n3. 在右侧列表中找到并勾选 {appName}\n\n完成设置后,返回应用即可正常使用。感谢您的配合。",
"prerequisites": "前置条件",
"ignoreBatteryOptimization": "忽略电池优化",
"batteryOptimizationDesc": "为保证后台运行,请关闭本应用的电池优化。点击前往设置。",
"locationPermission": "位置权限",
"locationPermissionDesc": "根据系统要求,获取Wi-Fi名称需要您授予位置权限。",
"excludeSsids": "排除SSIDs",
"excludeSsidsDesc": "连接到被排除SSID的WIFI时,将会自动切换应用运行状态",
"ssidsEmpty": "SSIDs为空",
"onDemand": "按需运行",
"onDemandDesc": "配置程序特定场景运行状态"
}
+10
View File
@@ -2,6 +2,8 @@ package main
import (
"encoding/json"
"fmt"
"runtime"
"unsafe"
)
@@ -37,6 +39,14 @@ func (result ActionResult) error(data interface{}) {
}
func handleAction(action *Action, result ActionResult) {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
logError("panic in handleAction(%s): %v\n%s", action.Method, r, buf[:n])
result.error(fmt.Sprintf("internal panic: %v", r))
}
}()
switch action.Method {
case initClashMethod:
paramsString := action.Data.(string)
+10 -1
View File
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -35,6 +36,7 @@ var (
isRunning = false
runLock sync.Mutex
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
debugError = false
)
func getExternalProvidersRaw() map[string]cp.Provider {
@@ -138,7 +140,7 @@ func stopListeners() {
}
func patchSelectGroup(mapping map[string]string) {
for name, proxy := range tunnel.ProxiesWithProviders() {
for name, proxy := range tunnel.AllProxies() {
outbound, ok := proxy.(*adapter.Proxy)
if !ok {
continue
@@ -257,3 +259,10 @@ func UnmarshalJson(data []byte, v any) error {
err := decoder.Decode(v)
return err
}
func logError(format string, args ...interface{}) {
log.Errorln(format, args...)
if debugError {
fmt.Fprintf(os.Stderr, "[ERROR] "+format+"\n", args...)
}
}
+1 -1
View File
@@ -87,7 +87,7 @@ const (
asyncTestDelayMethod Method = "asyncTestDelay"
getConnectionsMethod Method = "getConnections"
closeConnectionsMethod Method = "closeConnections"
resetConnectionsMethod Method = "resetConnectionsMethod"
resetConnectionsMethod Method = "resetConnections"
closeConnectionMethod Method = "closeConnection"
getExternalProvidersMethod Method = "getExternalProviders"
getExternalProviderMethod Method = "getExternalProvider"
+13
View File
@@ -0,0 +1,13 @@
//go:build windows && !cgo
package main
import (
"io"
"github.com/Microsoft/go-winio"
)
func dial(path string) (io.ReadWriteCloser, error) {
return winio.DialPipe(path, nil)
}
+18
View File
@@ -0,0 +1,18 @@
//go:build !cgo && !windows
package main
import (
"fmt"
"io"
"net"
"strconv"
)
func dial(arg string) (io.ReadWriteCloser, error) {
_, err := strconv.Atoi(arg)
if err != nil {
return net.Dial("unix", arg)
}
return net.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", arg))
}
+21 -19
View File
@@ -1,25 +1,26 @@
module core
go 1.20
go 1.21
replace github.com/metacubex/mihomo => ./Clash.Meta
require (
github.com/Microsoft/go-winio v0.6.2
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e
golang.org/x/sync v0.11.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
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/enfein/mieru/v3 v3.26.2 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/dunglas/httpsfv v1.0.2 // indirect
github.com/enfein/mieru/v3 v3.31.0 // 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
@@ -40,7 +41,6 @@ require (
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect
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-20251104174305-5a0e9f7e361d // indirect
@@ -50,32 +50,35 @@ require (
github.com/metacubex/blake3 v0.1.0 // indirect
github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/chi v0.1.0 // indirect
github.com/metacubex/cpu v0.1.0 // indirect
github.com/metacubex/connect-ip-go v0.0.0-20260412152424-e1625567920a // indirect
github.com/metacubex/cpu v0.1.1 // indirect
github.com/metacubex/edwards25519 v1.2.0 // 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-20251227095601-261ec1326fe8 // indirect
github.com/metacubex/hkdf v0.1.0 // indirect
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.0 // indirect
github.com/metacubex/http v0.1.2 // indirect
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
github.com/metacubex/mhurl v0.1.0 // indirect
github.com/metacubex/mlkem v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/qpack v0.6.0 // indirect
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect
github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306 // 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-20260112044712-65d17608159e // indirect
github.com/metacubex/sing v0.5.7 // indirect
github.com/metacubex/sing-mux v0.3.9 // indirect
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a // 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.11 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-tun v0.4.17 // indirect
github.com/metacubex/sing-vmess v0.2.5 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
github.com/metacubex/tls v0.1.1 // indirect
github.com/metacubex/tls v0.1.5 // indirect
github.com/metacubex/utls v1.8.4 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
@@ -86,21 +89,20 @@ require (
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/lo v1.53.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
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // 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
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
+45 -36
View File
@@ -1,8 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
github.com/RyuaNerin/testingutil v0.1.0/go.mod h1:yTqj6Ta/ycHMPJHRyO12Mz3VrvTloWOsy23WOZH19AA=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -12,22 +13,23 @@ github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/enfein/mieru/v3 v3.31.0 h1:Fl2ocRCRXJzMygzdRjBHgqI996ZuIDHUmyQyovSf9sA=
github.com/enfein/mieru/v3 v3.31.0/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=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
@@ -56,9 +58,9 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -68,8 +70,6 @@ github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/4
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
@@ -88,8 +88,12 @@ github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
github.com/metacubex/connect-ip-go v0.0.0-20260412152424-e1625567920a h1:Ph5UfTWDsGruZ+v95Df1ycTflQFmpZBFg2LUvj2kx/M=
github.com/metacubex/connect-ip-go v0.0.0-20260412152424-e1625567920a/go.mod h1:xYC8Ik7/rN6no+vTRuWMEziGwm3brA0wNM/zZP9qhOQ=
github.com/metacubex/cpu v0.1.1 h1:rRV5HGmeuGzjiKI3hYbL0dCd0qGwM7VUtk4ICXD06mI=
github.com/metacubex/cpu v0.1.1/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
github.com/metacubex/edwards25519 v1.2.0 h1:pIQZLBsjQgg3Nl/c86YYFEUAbL5qQRnPq4LrgIw0KK4=
github.com/metacubex/edwards25519 v1.2.0/go.mod h1:NCQF3J/Ki7382FJuokwsywEIIEI/gro/3smyXgQJsx0=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
@@ -100,46 +104,48 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/http v0.1.2 h1:XxW+pMaGAvVveRqREbyWJJDDl9dRZQGJoyKBiPPPKs8=
github.com/metacubex/http v0.1.2/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
github.com/metacubex/mhurl v0.1.0/go.mod h1:2qpQImCbXoUs6GwJrjuEXKelPyoimsIXr07eNKZdS00=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
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/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306 h1:HlGLmLsWJMLSu0CMI9z/BmEnithB4oXM5Rom6/0Qxtg=
github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
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=
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
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-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
github.com/metacubex/sing v0.5.7 h1:8OC+fhKFSv/l9ehEhJRaZZAOuthfZo68SteBVLe8QqM=
github.com/metacubex/sing v0.5.7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.9 h1:/aoBD2+sK2qsXDlNDe3hkR0GZuFDtwIZhOeGUx9W0Yk=
github.com/metacubex/sing-mux v0.3.9/go.mod h1:8bT7ZKT3clRrJjYc/x5CRYibC1TX/bK73a3r3+2E+Fc=
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a h1:977o0ZYYbiQAGuOxql7Q6UN3rEy59OyAE0tELq4gZfI=
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
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.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
github.com/metacubex/sing-tun v0.4.11/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-tun v0.4.17 h1:ehzvPLyxG1vmjaKVeB0aEK1eqhR3reEzdbqQfM3+5XA=
github.com/metacubex/sing-tun v0.4.17/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
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-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
@@ -166,21 +172,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.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=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@@ -192,14 +199,16 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
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=
@@ -225,13 +234,13 @@ golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+8 -17
View File
@@ -102,18 +102,10 @@ func handleGetProxies() ProxiesData {
nameList := config.GetProxyNameList()
proxies := make(map[string]constant.Proxy)
for name, proxy := range tunnel.Proxies() {
proxies[name] = proxy
}
for _, p := range tunnel.Providers() {
for _, proxy := range p.Proxies() {
proxies[proxy.Name()] = proxy
}
}
proxies := tunnel.AllProxies()
hasGlobal := false
allNames := make([]string, 0, len(nameList)+1)
for _, name := range nameList {
@@ -156,7 +148,7 @@ func handleChangeProxy(data string, fn func(string string)) {
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
proxies := tunnel.AllProxies()
group, ok := proxies[groupName]
if !ok {
fn("Not found group")
@@ -191,7 +183,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
}
data, err := json.Marshal(traffic)
if err != nil {
log.Errorln("Error: %s", err)
logError("Error: %s", err)
return ""
}
return string(data)
@@ -205,7 +197,7 @@ func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
}
data, err := json.Marshal(traffic)
if err != nil {
log.Errorln("Error: %s", err)
logError("Error: %s", err)
return ""
}
return string(data)
@@ -233,7 +225,7 @@ func handleAsyncTestDelay(paramsString string, fn func(string)) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
defer cancel()
proxies := tunnel.ProxiesWithProviders()
proxies := tunnel.AllProxies()
proxy := proxies[params.ProxyName]
delayData := &Delay{
@@ -253,7 +245,6 @@ func handleAsyncTestDelay(paramsString string, fn func(string)) {
testUrl = params.TestUrl
}
delayData.Url = testUrl
delay, err := proxy.URLTest(ctx, testUrl, expectedStatus)
if err != nil || delay == 0 {
delayData.Value = -1
@@ -275,7 +266,7 @@ func handleGetConnections() string {
snapshot := statistic.DefaultManager.Snapshot()
data, err := json.Marshal(snapshot)
if err != nil {
log.Errorln("Error: %s", err)
logError("Error: %s", err)
return ""
}
return string(data)
@@ -538,7 +529,7 @@ func handleSetupConfig(bytes []byte) string {
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)
if err != nil {
log.Errorln("unmarshalRawConfig error %v", err)
logError("unmarshalRawConfig error %v", err)
_ = applyConfig(defaultSetupParams())
return err.Error()
}
+45 -20
View File
@@ -3,18 +3,21 @@
package main
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
"net"
"strconv"
"io"
"sync"
)
var conn net.Conn
var (
conn io.ReadWriteCloser
connMu sync.Mutex
)
func (result ActionResult) send() {
data, err := result.Json()
if err != nil {
logError("ActionResult marshal error: method=%s id=%s err=%v", result.Method, result.Id, err)
return
}
send(data)
@@ -28,43 +31,65 @@ func sendMessage(message Message) {
result.send()
}
func writeFrame(w io.Writer, data []byte) error {
frame := make([]byte, 4+len(data))
binary.LittleEndian.PutUint32(frame, uint32(len(data)))
copy(frame[4:], data)
_, err := w.Write(frame)
return err
}
func readFrame(r io.Reader) ([]byte, error) {
lenBuf := make([]byte, 4)
if _, err := io.ReadFull(r, lenBuf); err != nil {
return nil, err
}
length := binary.LittleEndian.Uint32(lenBuf)
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return nil, err
}
return data, nil
}
func send(data []byte) {
if conn == nil {
logError("send conn nil")
return
}
_, _ = conn.Write(append(data, []byte("\n")...))
connMu.Lock()
defer connMu.Unlock()
if err := writeFrame(conn, data); err != nil {
logError("server write error: %v", err)
}
}
func startServer(arg string) {
_, err := strconv.Atoi(arg)
if err != nil {
conn, err = net.Dial("unix", arg)
} else {
conn, err = net.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", arg))
}
var err error
conn, err = dial(arg)
if err != nil {
panic(err.Error())
}
defer func(conn net.Conn) {
defer func(conn io.Closer) {
_ = conn.Close()
}(conn)
reader := bufio.NewReader(conn)
for {
data, err := reader.ReadString('\n')
data, err := readFrame(conn)
if err != nil {
if err != io.EOF {
logError("server read error: %v", err)
}
return
}
var action = &Action{}
err = json.Unmarshal([]byte(data), action)
err = json.Unmarshal(data, action)
if err != nil {
return
logError("server unmarshal error: %v (data: %q)", err, data)
continue
}
result := ActionResult{
+31 -9
View File
@@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'controller.dart';
import 'pages/pages.dart';
class Application extends ConsumerStatefulWidget {
@@ -48,21 +47,44 @@ class ApplicationState extends ConsumerState<Application> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) {
await appController.attach(currentContext, ref);
if (globalState.navigatorKey.currentContext != null) {
await globalState.attach();
} else {
exit(0);
}
_autoUpdateProfilesTask();
appController.initLink();
_initLink();
app?.initShortcuts();
});
}
void _initLink() {
linkManager.initAppLinksListen((url) async {
final res = await globalState.showMessage(
title: currentAppLocalizations.addProfile,
message: TextSpan(
children: [
TextSpan(text: currentAppLocalizations.doYouWantToPass),
TextSpan(
text: ' $url ',
style: TextStyle(
color: context.colorScheme.primary,
decoration: TextDecoration.underline,
decorationColor: context.colorScheme.primary,
),
),
TextSpan(text: currentAppLocalizations.createProfile),
],
),
);
if (res != true) return;
ref.read(profilesActionProvider.notifier).addProfileFormURL(url);
});
}
void _autoUpdateProfilesTask() {
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
await appController.autoUpdateProfiles();
await ref.read(profilesActionProvider.notifier).autoUpdateProfiles();
_autoUpdateProfilesTask();
});
}
@@ -84,10 +106,10 @@ class ApplicationState extends ConsumerState<Application> {
child: ConnectivityManager(
onConnectivityChanged: (results) async {
commonPrint.log('connectivityChanged ${results.toString()}');
appController.updateLocalIp();
ref.read(systemActionProvider.notifier).updateLocalIp();
final hasVpn = results.contains(ConnectivityResult.vpn);
if (_preHasVpn == hasVpn) {
appController.addCheckIp();
ref.read(checkIpNumProvider.notifier).add();
}
_preHasVpn = hasVpn;
},
@@ -167,7 +189,7 @@ class ApplicationState extends ConsumerState<Application> {
linkManager.destroy();
_autoUpdateProfilesTaskTimer?.cancel();
await coreController.destroy();
await appController.handleExit();
await ref.read(systemActionProvider.notifier).handleExit();
super.dispose();
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
import 'package:fl_clash/l10n/l10n.dart';
final appLocalizations = AppLocalizations.current;
final currentAppLocalizations = AppLocalizations.current;
+43 -173
View File
@@ -1,185 +1,55 @@
import 'dart:io';
import 'dart:async';
import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class LocalImageCacheManager extends CacheManager {
static const key = 'ImageCaches';
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
factory LocalImageCacheManager() {
return _instance;
}
LocalImageCacheManager._()
: super(Config(key, fileService: _LocalImageCacheFileService()));
}
class _LocalImageCacheFileService extends FileService {
_LocalImageCacheFileService();
@override
Future<FileServiceResponse> get(
extension CacheManagerExt on CacheManager {
Stream<FileInfo> getFileStreamV2(
String url, {
String? key,
Map<String, String>? headers,
}) async {
final response = await request.dio.get<ResponseBody>(
url,
options: Options(headers: headers, responseType: ResponseType.stream),
);
return _LocalImageResponse(response);
}
}
class _LocalImageResponse implements FileServiceResponse {
_LocalImageResponse(this._response);
final DateTime _receivedTime = DateTime.now();
final Response<ResponseBody> _response;
String? _header(String name) {
return _response.headers.value(name);
VoidCallback? onRemoteNewLoaded,
}) {
key ??= url;
final streamController = StreamController<FileInfo>();
_pushFileToStream(streamController, url, key, headers, onRemoteNewLoaded);
return streamController.stream;
}
@override
int get statusCode => _response.statusCode ?? 0;
@override
Stream<List<int>> get content =>
_response.data!.stream.transform(uint8ListToListIntConverter);
@override
int? get contentLength => _response.data?.contentLength;
@override
DateTime get validTill {
var ageDuration = const Duration(days: 7);
final controlHeader = _header(HttpHeaders.cacheControlHeader);
if (controlHeader != null) {
final controlSettings = controlHeader.split(',');
for (final setting in controlSettings) {
final sanitizedSetting = setting.trim().toLowerCase();
if (sanitizedSetting == 'no-cache') {
ageDuration = Duration.zero;
}
if (sanitizedSetting.startsWith('max-age=')) {
final validSeconds =
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
if (validSeconds > 0) {
ageDuration = Duration(seconds: validSeconds);
}
Future<void> _pushFileToStream(
StreamController<dynamic> streamController,
String url,
String? key,
Map<String, String>? headers,
VoidCallback? onRemoteNewLoaded,
) async {
key ??= url;
FileInfo? cacheFile;
try {
cacheFile = await getFileFromCache(key);
if (cacheFile != null) {
streamController.add(cacheFile);
}
} on Object catch (e) {
cacheLogger.log(
'CacheManager: Failed to load cached file for $url with error:\n$e',
CacheManagerLogLevel.debug,
);
}
if (cacheFile == null || cacheFile.validTill.isBefore(DateTime.now())) {
try {
final res = (await downloadFile(url, key: key, authHeaders: headers));
streamController.add(res);
if (cacheFile == null) {
onRemoteNewLoaded?.call();
}
} on Object catch (e) {
cacheLogger.log(
'CacheManager: Failed to download file from $url with error:\n$e',
CacheManagerLogLevel.debug,
);
}
}
if (ageDuration > const Duration(days: 7)) {
return _receivedTime.add(ageDuration);
}
return _receivedTime.add(const Duration(days: 7));
}
@override
String? get eTag => _header(HttpHeaders.etagHeader);
@override
String get fileExtension {
var fileExtension = '';
final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
if (contentTypeHeader != null) {
final contentType = ContentType.parse(contentTypeHeader);
fileExtension = contentType.fileExtension;
}
return fileExtension;
streamController.close();
}
}
extension ContentTypeConverter on ContentType {
String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
}
const mimeTypes = {
'application/vnd.android.package-archive': '.apk',
'application/epub+zip': '.epub',
'application/gzip': '.gz',
'application/java-archive': '.jar',
'application/json': '.json',
'application/ld+json': '.jsonld',
'application/msword': '.doc',
'application/octet-stream': '.bin',
'application/ogg': '.ogx',
'application/pdf': '.pdf',
'application/php': '.php',
'application/rtf': '.rtf',
'application/vnd.amazon.ebook': '.azw',
'application/vnd.apple.installer+xml': '.mpkg',
'application/vnd.mozilla.xul+xml': '.xul',
'application/vnd.ms-excel': '.xls',
'application/vnd.ms-fontobject': '.eot',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.oasis.opendocument.presentation': '.odp',
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
'.pptx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
'.docx',
'application/vnd.rar': '.rar',
'application/vnd.visio': '.vsd',
'application/x-7z-compressed': '.7z',
'application/x-abiword': '.abw',
'application/x-bzip': '.bz',
'application/x-bzip2': '.bz2',
'application/x-csh': '.csh',
'application/x-freearc': '.arc',
'application/x-sh': '.sh',
'application/x-shockwave-flash': '.swf',
'application/x-tar': '.tar',
'application/xhtml+xml': '.xhtml',
'application/xml': '.xml',
'application/zip': '.zip',
'audio/3gpp': '.3gp',
'audio/3gpp2': '.3g2',
'audio/aac': '.aac',
'audio/x-aac': '.aac',
'audio/midi': '.midi',
'audio/x-midi': '.midi',
'audio/x-m4a': '.m4a',
'audio/m4a': '.m4a',
'audio/mpeg': '.mp3',
'audio/ogg': '.oga',
'audio/opus': '.opus',
'audio/wav': '.wav',
'audio/x-wav': '.wav',
'audio/webm': '.weba',
'font/otf': '.otf',
'font/ttf': '.ttf',
'font/woff': '.woff',
'font/woff2': '.woff2',
'image/bmp': '.bmp',
'image/gif': '.gif',
'image/jpeg': '.jpg',
'image/png': '.png',
'image/svg+xml': '.svg',
'image/tiff': '.tiff',
'image/vnd.microsoft.icon': '.ico',
'image/webp': '.webp',
'text/calendar': '.ics',
'text/css': '.css',
'text/csv': '.csv',
'text/html': '.html',
'text/javascript': '.js',
'text/plain': '.txt',
'text/xml': '.xml',
'video/3gpp': '.3gp',
'video/3gpp2': '.3g2',
'video/mp2t': '.ts',
'video/mpeg': '.mpeg',
'video/ogg': '.ogv',
'video/webm': '.webm',
'video/x-msvideo': '.avi',
'video/quicktime': '.mov',
};
+3 -11
View File
@@ -93,10 +93,7 @@ extension ColorExtension on Color {
.toColor();
}
Color blendDarken(
BuildContext context, {
double factor = 0.1,
}) {
Color blendDarken(BuildContext context, {double factor = 0.1}) {
final brightness = Theme.of(context).brightness;
return Color.lerp(
this,
@@ -105,10 +102,7 @@ extension ColorExtension on Color {
)!;
}
Color blendLighten(
BuildContext context, {
double factor = 0.1,
}) {
Color blendLighten(BuildContext context, {double factor = 0.1}) {
final brightness = Theme.of(context).brightness;
return Color.lerp(
this,
@@ -122,9 +116,7 @@ extension ColorSchemeExtension on ColorScheme {
ColorScheme toPureBlack(bool isPrueBlack) => isPrueBlack
? copyWith(
surface: Colors.black,
surfaceContainer: surfaceContainer.darken(
5,
),
surfaceContainer: surfaceContainer.darken(5),
)
: this;
}
+2 -2
View File
@@ -1,5 +1,4 @@
export 'app_localizations.dart';
export 'cache.dart';
export 'color.dart';
export 'compute.dart';
export 'constant.dart';
@@ -10,11 +9,11 @@ export 'file.dart';
export 'fixed.dart';
export 'function.dart';
export 'future.dart';
export 'hive.dart';
export 'http.dart';
export 'icons.dart';
export 'indexing.dart';
export 'iterable.dart';
export 'javascript.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
@@ -29,6 +28,7 @@ export 'num.dart';
export 'package.dart';
export 'path.dart';
export 'picker.dart';
export 'pipe.dart';
export 'preferences.dart';
export 'print.dart';
export 'protocol.dart';
+7 -4
View File
@@ -16,6 +16,7 @@ const browserUa =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const packageName = 'com.follow.clash';
final unixSocketPath = '/tmp/FlClashSocket_${Random().nextInt(10000)}.sock';
final windowsPipeName = '\\\\.\\pipe\\FlClashCore_${Random().nextInt(10000)}';
const helperPort = 47890;
const maxTextScale = 1.4;
const minTextScale = 0.8;
@@ -29,8 +30,9 @@ final listHeaderPadding = EdgeInsets.only(
top: 24.mAp,
bottom: 8.mAp,
);
const sheetAppBarHeight = 68.0;
const watchExecution = true;
const watchExecution = false;
final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
@@ -62,7 +64,7 @@ const defaultTestUrl = 'https://www.gstatic.com/generate_204';
final commonFilter = ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
tileMode: TileMode.mirror,
tileMode: TileMode.clamp,
);
const listEquality = ListEquality();
@@ -77,6 +79,7 @@ const scriptListEquality = ListEquality<Script>();
const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>();
const profileListEquality = ListEquality<Profile>();
const proxyGroupsEquality = ListEquality<ProxyGroup>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryListEquality =
@@ -108,9 +111,9 @@ double getWidgetHeight(num lines) {
const maxLength = 1000;
final mainIsolate = 'FlClashMainIsolate';
const mainIsolate = 'FlClashMainIsolate';
final serviceIsolate = 'FlClashServiceIsolate';
const serviceIsolate = 'FlClashServiceIsolate';
const defaultPrimaryColors = [
0xFF795548,
+22 -1
View File
@@ -1,7 +1,10 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/models/state.dart';
import 'package:fl_clash/widgets/inherited.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:fl_clash/widgets/sheet.dart';
import 'package:flutter/material.dart';
extension BuildContextExtension on BuildContext {
@@ -9,6 +12,24 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<CommonScaffoldState>();
}
void safeNestedPop<T extends Object?>([T? result]) {
final nestedPop = SheetProvider.of(this)?.nestedNavigatorPop;
if (nestedPop != null) {
return nestedPop(result);
} else {
return Navigator.of(this).pop(result);
}
}
double get sheetTopPadding {
final sheetType = SheetProvider.of(this)!.type;
if (sheetType == SheetType.bottomSheet) {
return sheetAppBarHeight;
} else {
return 10;
}
}
void showNotifier(String text, {MessageActionState? actionState}) {
return findAncestorStateOfType<StatusManagerState>()?.message(
text,
@@ -52,7 +73,7 @@ extension BuildContextExtension on BuildContext {
T? findLastStateOfType<T extends State>() {
T? state;
visitor(Element element) {
void visitor(Element element) {
if (!element.mounted) {
return;
}
+5 -2
View File
@@ -1,4 +1,6 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:flutter/cupertino.dart';
import 'context.dart';
extension DateTimeExtension on DateTime {
bool get isBeforeNow {
@@ -12,7 +14,8 @@ extension DateTimeExtension on DateTime {
return true;
}
String get lastUpdateTimeDesc {
String getLastUpdateTimeDesc(BuildContext context) {
final appLocalizations = context.appLocalizations;
final currentDateTime = DateTime.now();
final difference = currentDateTime.difference(this);
final days = difference.inDays;
+1 -3
View File
@@ -6,7 +6,6 @@ import 'package:webdav_client/webdav_client.dart';
class DAVClient {
late Client client;
Completer<bool> pingCompleter = Completer();
late String fileName;
DAVClient(DAVProps dav) {
@@ -16,10 +15,9 @@ class DAVClient {
client.setConnectTimeout(8000);
client.setSendTimeout(60000);
client.setReceiveTimeout(60000);
pingCompleter.complete(_ping());
}
Future<bool> _ping() async {
Future<bool> ping() async {
try {
await client.ping();
return true;
+2 -2
View File
@@ -17,14 +17,14 @@ extension FileExt on File {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsString(str);
return writeAsString(str);
}
Future<File> safeWriteAsBytes(List<int> bytes) async {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsBytes(bytes);
return writeAsBytes(bytes);
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ class FixedList<T> {
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
list: List.of(_list),
);
}
}
+4 -5
View File
@@ -1,13 +1,12 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
class Debouncer {
final Map<FunctionTag, Timer?> _operations = {};
final Map<dynamic, Timer?> _operations = {};
void call(
FunctionTag tag,
dynamic tag,
Function func, {
List<dynamic>? args,
Duration? duration,
@@ -30,10 +29,10 @@ class Debouncer {
}
class Throttler {
final Map<FunctionTag, Timer?> _operations = {};
final Map<dynamic, Timer?> _operations = {};
bool call(
FunctionTag tag,
dynamic tag,
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
View File
+10 -5
View File
@@ -1,18 +1,23 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FlClashHttpOverrides extends HttpOverrides {
static String handleFindProxy(Uri url) {
if ([localhost].contains(url.host)) {
return 'DIRECT';
}
final port = appController.config.patchClashConfig.mixedPort;
final isStart = appController.isStart;
commonPrint.log('find $url proxy:$isStart');
final ref = globalState.container;
final isStart = ref.read(isStartProvider);
commonPrint.log('find $url proxy: $isStart');
if (!isStart) return 'DIRECT';
return 'PROXY localhost:$port';
final mixedPort = ref.read(
patchClashConfigProvider.select((state) => state.mixedPort),
);
return 'PROXY localhost:$mixedPort';
}
@override
+32 -28
View File
@@ -35,13 +35,13 @@ class Indexing {
String? _incrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
final String head = x[0];
final List<String> digs = x.substring(1).split('');
bool carry = true;
for (int i = digs.length - 1; carry && i >= 0; i--) {
int d = digits.indexOf(digs[i]) + 1;
final int d = digits.indexOf(digs[i]) + 1;
if (d == digits.length) {
digs[i] = '0';
} else {
@@ -57,7 +57,7 @@ class Indexing {
if (head == 'z') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) + 1);
final String h = String.fromCharCode(head.codeUnitAt(0) + 1);
if (h.compareTo('a') > 0) {
digs.add('0');
} else {
@@ -71,13 +71,13 @@ class Indexing {
String? _decrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
final String head = x[0];
final List<String> digs = x.substring(1).split('');
bool borrow = true;
for (int i = digs.length - 1; borrow && i >= 0; i--) {
int d = digits.indexOf(digs[i]) - 1;
final int d = digits.indexOf(digs[i]) - 1;
if (d == -1) {
digs[i] = digits[digits.length - 1];
} else {
@@ -93,7 +93,7 @@ class Indexing {
if (head == 'A') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) - 1);
final String h = String.fromCharCode(head.codeUnitAt(0) - 1);
if (h.compareTo('Z') < 0) {
digs.add(digits[digits.length - 1]);
} else {
@@ -132,13 +132,13 @@ class Indexing {
}
}
int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
int digitB = (b != null && b.isNotEmpty)
final int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
final int digitB = (b != null && b.isNotEmpty)
? digits.indexOf(b[0])
: digits.length;
if (digitB - digitA > 1) {
int midDigit = (digitA + digitB + 1) ~/ 2;
final int midDigit = (digitA + digitB + 1) ~/ 2;
return digits[midDigit];
} else {
if (b != null && b.length > 1) {
@@ -151,7 +151,7 @@ class Indexing {
}
String _getIntegerPart(String key) {
int integerPartLength = _getIntegerLength(key[0]);
final int integerPartLength = _getIntegerLength(key[0]);
if (integerPartLength > key.length) {
throw Exception('Invalid order key: $key');
}
@@ -163,8 +163,8 @@ class Indexing {
throw Exception('Invalid order key: $key');
}
String i = _getIntegerPart(key);
String f = key.substring(i.length);
final String i = _getIntegerPart(key);
final String f = key.substring(i.length);
if (f.isNotEmpty && f[f.length - 1] == '0') {
throw Exception('Invalid order key: $key');
}
@@ -192,8 +192,8 @@ class Indexing {
if (a == null) {
b = b!;
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
final String ib = _getIntegerPart(b);
final String fb = b.substring(ib.length);
if (ib == smallestInteger) {
return ib + _midpoint('', fb);
}
@@ -201,22 +201,22 @@ class Indexing {
}
if (b == null) {
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String? i = _incrementInteger(ia);
final String ia = _getIntegerPart(a);
final String fa = a.substring(ia.length);
final String? i = _incrementInteger(ia);
return i ?? ia + _midpoint(fa, null);
}
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
final String ia = _getIntegerPart(a);
final String fa = a.substring(ia.length);
final String ib = _getIntegerPart(b);
final String fb = b.substring(ib.length);
if (ia == ib) {
return ia + _midpoint(fa, fb);
}
String? i = _incrementInteger(ia);
final String? i = _incrementInteger(ia);
return (i == null || i.compareTo(b) < 0) ? i : ia + _midpoint(fa, null);
}
@@ -230,7 +230,7 @@ class Indexing {
if (b == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
final List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(c, b);
result.add(c);
@@ -240,7 +240,7 @@ class Indexing {
if (a == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
final List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(a, c);
result.add(c);
@@ -248,13 +248,17 @@ class Indexing {
return result.reversed.toList();
}
int mid = n ~/ 2;
String? c = generateKeyBetween(a, b);
final int mid = n ~/ 2;
final String? c = generateKeyBetween(a, b);
return generateNKeysBetween(a, c, mid)
.followedBy([c])
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
.toList();
}
List<String?> generateNKeys(int n) {
return generateNKeysBetween(null, null, n);
}
}
final indexing = Indexing();
+15 -4
View File
@@ -13,9 +13,9 @@ extension IterableExt<E> on Iterable<E> {
Iterable<List<E>> chunks(int size) sync* {
if (length == 0) return;
var iterator = this.iterator;
final iterator = this.iterator;
while (iterator.moveNext()) {
var chunk = [iterator.current];
final chunk = [iterator.current];
for (var i = 1; i < size && iterator.moveNext(); i++) {
chunk.add(iterator.current);
}
@@ -25,7 +25,7 @@ extension IterableExt<E> on Iterable<E> {
Iterable<E> fill(int length, {required E Function(int count) filler}) sync* {
int count = 0;
for (var item in this) {
for (final item in this) {
yield item;
count++;
if (count >= length) return;
@@ -69,6 +69,17 @@ extension ListExt<T> on List<T> {
return res;
}
List<T> copyAndPut(T data, bool Function(T element) test) {
final newList = List<T>.from(this);
final index = newList.indexWhere(test);
if (index != -1) {
newList[index] = data;
} else {
newList.insert(0, data);
}
return newList;
}
List<T> safeSublist(int start, [int? end]) {
if (start <= 0) return this;
if (start > length) return [];
@@ -121,7 +132,7 @@ extension DoubleListExt on List<double> {
int right = length - 1;
while (left <= right) {
int mid = left + (right - left) ~/ 2;
final int mid = left + (right - left) ~/ 2;
if (mid == length - 1 ||
(this[mid] <= target && target < this[mid + 1])) {
+27
View File
@@ -0,0 +1,27 @@
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'package:flutter_js/flutter_js.dart';
Future<Map<String, dynamic>> handleEvaluate(
String scriptContent,
Map<String, dynamic> config,
) async {
if (config['proxy-providers'] == null) {
config['proxy-providers'] = {};
}
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;
}
+3 -3
View File
@@ -23,15 +23,15 @@ class AutoLaunch {
}
Future<bool> get isEnable async {
return await launchAtStartup.isEnabled();
return launchAtStartup.isEnabled();
}
Future<bool> enable() async {
return await launchAtStartup.enable();
return launchAtStartup.enable();
}
Future<bool> disable() async {
return await launchAtStartup.disable();
return launchAtStartup.disable();
}
Future<void> updateStatus(bool isAutoLaunch) async {
+15 -4
View File
@@ -11,16 +11,27 @@ class Measure {
: _measureMap = {},
_textScaler = TextScaler.linear(textScaleFactor);
Size computeTextSize(Text text, {double maxWidth = double.infinity}) {
final textPainter = TextPainter(
text: TextSpan(text: text.data, style: text.style),
TextPainter computeText(Text text, {TextStyle? style, double? maxWidth}) {
return TextPainter(
text: TextSpan(text: text.data, style: text.style ?? style),
maxLines: text.maxLines,
textScaler: _textScaler,
ellipsis: '...',
locale: Localizations.localeOf(context),
textDirection: text.textDirection ?? TextDirection.ltr,
)..layout(maxWidth: maxWidth);
)..layout(maxWidth: maxWidth ?? double.infinity);
}
Size computeTextSize(Text text, {TextStyle? style, double? maxWidth}) {
final textPainter = computeText(text, style: style, maxWidth: maxWidth);
return textPainter.size;
}
bool computeTextIsOverflow(Text text, {TextStyle? style, double? maxWidth}) {
final textPainter = computeText(text, style: style, maxWidth: maxWidth);
return textPainter.didExceedMaxLines;
}
double get bodyMediumHeight {
return _measureMap.updateCacheValue(
'bodyMediumHeight',
+1 -1
View File
@@ -46,7 +46,7 @@ class Migration {
}
Future<MigrationData> _oldToNow(Map<String, Object?> configMap) async {
return await oldToNowTask(configMap);
return oldToNowTask(configMap);
}
}
+9 -2
View File
@@ -1,6 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'utils.dart';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
T get value => state;
@@ -25,9 +28,9 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
void onUpdate(T value) {}
void update(T? Function(T) builder) {
void update(T Function(T) builder) {
final res = builder(value);
if (res == null) {
if (res == value) {
return;
}
value = res;
@@ -41,3 +44,7 @@ mixin AsyncNotifierMixin<T> on AnyNotifier<AsyncValue<T>, T> {
state = AsyncData(value);
}
}
mixin UniqueKeyStateMixin<T extends StatefulWidget> on State<T> {
final key = utils.id;
}
+6 -6
View File
@@ -13,7 +13,7 @@ class Navigation {
return [
NavigationItem(
keep: false,
icon: Icon(Icons.space_dashboard),
icon: const Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
builder: (_) =>
const DashboardView(key: GlobalObjectKey(PageLabel.dashboard)),
@@ -28,13 +28,13 @@ class Navigation {
: [],
),
NavigationItem(
icon: Icon(Icons.folder),
icon: const Icon(Icons.folder),
label: PageLabel.profiles,
builder: (_) =>
const ProfilesView(key: GlobalObjectKey(PageLabel.profiles)),
),
NavigationItem(
icon: Icon(Icons.view_timeline),
icon: const Icon(Icons.view_timeline),
label: PageLabel.requests,
builder: (_) =>
const RequestsView(key: GlobalObjectKey(PageLabel.requests)),
@@ -42,7 +42,7 @@ class Navigation {
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
NavigationItem(
icon: Icon(Icons.ballot),
icon: const Icon(Icons.ballot),
label: PageLabel.connections,
builder: (_) =>
const ConnectionsView(key: GlobalObjectKey(PageLabel.connections)),
@@ -50,7 +50,7 @@ class Navigation {
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
NavigationItem(
icon: Icon(Icons.storage),
icon: const Icon(Icons.storage),
label: PageLabel.resources,
description: 'resourcesDesc',
builder: (_) =>
@@ -67,7 +67,7 @@ class Navigation {
: [],
),
NavigationItem(
icon: Icon(Icons.construction),
icon: const Icon(Icons.construction),
label: PageLabel.tools,
builder: (_) => const ToolsView(key: GlobalObjectKey(PageLabel.tools)),
modes: [NavigationItemMode.desktop, NavigationItemMode.mobile],
+10 -9
View File
@@ -1,16 +1,17 @@
import 'package:animations/animations.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
class BaseNavigator {
static Future<T?> push<T>(BuildContext context, Widget child) async {
if (!appController.isMobile) {
return await Navigator.of(
if (!globalState.container.read(isMobileViewProvider)) {
return Navigator.of(
context,
).push<T>(CommonDesktopRoute(builder: (context) => child));
}
return await Navigator.of(
return Navigator.of(
context,
).push<T>(CommonRoute(builder: (context) => child));
}
@@ -65,10 +66,10 @@ class CommonDesktopRoute<T> extends PageRoute<T> {
bool get maintainState => true;
@override
Duration get transitionDuration => Duration(milliseconds: 200);
Duration get transitionDuration => const Duration(milliseconds: 200);
@override
Duration get reverseTransitionDuration => Duration(milliseconds: 200);
Duration get reverseTransitionDuration => const Duration(milliseconds: 200);
}
class CommonRoute<T> extends PageRoute<T> {
@@ -106,10 +107,10 @@ class CommonRoute<T> extends PageRoute<T> {
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Duration get reverseTransitionDuration => Duration(milliseconds: 300);
Duration get reverseTransitionDuration => const Duration(milliseconds: 300);
}
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
@@ -262,7 +263,7 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
(_primaryShadowCurve ?? widget.primaryRouteAnimation).drive(
DecorationTween(
begin: const _CommonEdgeShadowDecoration(),
end: _CommonEdgeShadowDecoration(<Color>[
end: const _CommonEdgeShadowDecoration(<Color>[
Color(0x04000000),
Colors.transparent,
]),
+2 -2
View File
@@ -27,7 +27,7 @@ extension NumExt on num {
}
TrafficShow get traffic {
final units = TrafficUnit.values;
const units = TrafficUnit.values;
var size = toDouble();
var unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
@@ -41,7 +41,7 @@ extension NumExt on num {
}
TrafficShow get shortTraffic {
final units = TrafficUnit.values;
const units = TrafficUnit.values;
var size = toDouble();
var unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
+4 -4
View File
@@ -8,7 +8,7 @@ import 'package:mobile_scanner/mobile_scanner.dart';
class Picker {
Future<PlatformFile?> pickerFile({bool withData = true}) async {
final filePickerResult = await FilePicker.platform.pickFiles(
final filePickerResult = await FilePicker.pickFiles(
withData: withData,
allowMultiple: false,
initialDirectory: await appPath.downloadDirPath,
@@ -17,7 +17,7 @@ class Picker {
}
Future<String?> saveFile(String fileName, Uint8List bytes) async {
final path = await FilePicker.platform.saveFile(
final path = await FilePicker.saveFile(
fileName: fileName,
initialDirectory: await appPath.downloadDirPath,
bytes: bytes,
@@ -35,7 +35,7 @@ class Picker {
await localFile.create(recursive: true);
}
final bytes = Platform.isAndroid ? await localFile.readAsBytes() : null;
final path = await FilePicker.platform.saveFile(
final path = await FilePicker.saveFile(
fileName: fileName,
initialDirectory: await appPath.downloadDirPath,
bytes: bytes,
@@ -59,7 +59,7 @@ class Picker {
);
final result = capture?.barcodes.first.rawValue;
if (result == null || !result.isUrl) {
throw appLocalizations.pleaseUploadValidQrcode;
throw currentAppLocalizations.pleaseUploadValidQrcode;
}
return result;
}
+234
View File
@@ -0,0 +1,234 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:win32/win32.dart';
const _pipeBufferSize = 256 * 1024;
class _PipeCmd {
final int type; // 0 = write, 1 = shutdown
final Uint8List? data;
const _PipeCmd(this.type, this.data);
}
void _pipeIsolate(List<Object> args) {
final pipeName = args[0] as String;
final dataPort = args[1] as SendPort;
final namePtr = pipeName.toNativeUtf16();
final handle = CreateNamedPipe(
namePtr,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
_pipeBufferSize,
_pipeBufferSize,
0,
nullptr,
);
calloc.free(namePtr);
if (handle == INVALID_HANDLE_VALUE) {
dataPort.send(
'error:CreateNamedPipe failed, Win32 error=${GetLastError()}',
);
return;
}
final writeRx = ReceivePort();
final readBuf = calloc<Uint8>(_pipeBufferSize);
final bytesRead = calloc<Uint32>();
final overlapped = calloc<OVERLAPPED>();
final readEvent = CreateEvent(nullptr, 1, 0, nullptr);
overlapped.ref.hEvent = readEvent;
var running = true;
var clientConnected = false;
writeRx.listen((msg) {
if (msg is _PipeCmd) {
if (msg.type == 0) {
if (clientConnected && msg.data != null) {
final buf = calloc<Uint8>(msg.data!.length);
final written = calloc<Uint32>();
buf.asTypedList(msg.data!.length).setAll(0, msg.data!);
WriteFile(handle, buf, msg.data!.length, written, nullptr);
calloc.free(written);
calloc.free(buf);
}
} else if (msg.type == 1) {
running = false;
if (clientConnected) {
DisconnectNamedPipe(handle);
clientConnected = false;
}
writeRx.close();
}
}
});
final connectEvent = CreateEvent(nullptr, 1, 0, nullptr);
final connectOverlapped = calloc<OVERLAPPED>();
connectOverlapped.ref.hEvent = connectEvent;
while (running) {
final cr = ConnectNamedPipe(handle, connectOverlapped);
if (cr == 0) {
final err = GetLastError();
if (err == ERROR_IO_PENDING) {
while (running) {
final wr = WaitForSingleObject(connectEvent, 100);
if (wr == WAIT_OBJECT_0) break;
}
if (!running) break;
} else if (err != ERROR_PIPE_CONNECTED) {
dataPort.send('error:ConnectNamedPipe failed, Win32 error=$err');
break;
}
}
if (!running) break;
clientConnected = true;
dataPort.send(writeRx.sendPort);
var readPending = false;
while (running && clientConnected) {
if (!readPending) {
bytesRead.value = 0;
final readResult = ReadFile(
handle,
readBuf,
_pipeBufferSize,
bytesRead,
overlapped,
);
if (readResult != 0) {
if (bytesRead.value > 0) {
dataPort.send(
Uint8List.fromList(readBuf.asTypedList(bytesRead.value)),
);
} else {
break;
}
} else {
final err = GetLastError();
if (err == ERROR_IO_PENDING) {
readPending = true;
} else if (bytesRead.value > 0) {
dataPort.send(
Uint8List.fromList(readBuf.asTypedList(bytesRead.value)),
);
} else {
break;
}
}
}
if (readPending) {
final wr = WaitForSingleObject(readEvent, 100);
if (wr == WAIT_OBJECT_0) {
final got = GetOverlappedResult(handle, overlapped, bytesRead, 0);
if (got != 0 && bytesRead.value > 0) {
dataPort.send(
Uint8List.fromList(readBuf.asTypedList(bytesRead.value)),
);
readPending = false;
} else {
break;
}
} else if (wr != WAIT_TIMEOUT) {
break;
}
}
}
if (running && clientConnected) {
DisconnectNamedPipe(handle);
clientConnected = false;
dataPort.send('closed');
}
}
CloseHandle(readEvent);
CloseHandle(connectEvent);
calloc.free(connectOverlapped);
calloc.free(overlapped);
calloc.free(bytesRead);
calloc.free(readBuf);
CloseHandle(handle);
}
class NamedPipeServer {
final String pipeName;
Isolate? _isolate;
SendPort? _writePort;
ReceivePort? _responsePort;
StreamSubscription? _responseSub;
final _dataController = StreamController<Uint8List>.broadcast();
Completer<void> _connectionCompleter = Completer<void>();
void Function()? onDisconnect;
NamedPipeServer._(this.pipeName);
Stream<Uint8List> get dataStream => _dataController.stream;
Completer<void> get connectionCompleter => _connectionCompleter;
static Future<NamedPipeServer> bind(String pipeName) async {
final server = NamedPipeServer._(pipeName);
server._responsePort = ReceivePort();
server._responseSub = server._responsePort!.listen((message) {
if (message is SendPort) {
server._writePort = message;
if (server._connectionCompleter.isCompleted) {
server._connectionCompleter = Completer<void>();
}
server._connectionCompleter.complete();
} else if (message is Uint8List) {
server._dataController.add(message);
} else if (message == 'closed') {
server._connectionCompleter = Completer<void>();
server.onDisconnect?.call();
} else if (message is String && message.startsWith('error:')) {
commonPrint.log('[NamedPipeServer] $message', logLevel: LogLevel.error);
}
});
server._isolate = await Isolate.spawn(_pipeIsolate, [
pipeName,
server._responsePort!.sendPort,
]);
return server;
}
void writeln(String message) {
final wp = _writePort;
if (wp == null) return;
final str = '$message\n';
wp.send(_PipeCmd(0, Uint8List.fromList(str.codeUnits)));
}
Future<void> close() async {
final wp = _writePort;
if (wp == null) return;
_writePort = null;
wp.send(const _PipeCmd(1, null));
_responseSub?.cancel();
_responsePort?.close();
await Future.delayed(const Duration(milliseconds: 50));
_isolate?.kill();
_isolate = null;
}
}
+6 -3
View File
@@ -1,6 +1,7 @@
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
class CommonPrint {
@@ -16,10 +17,12 @@ class CommonPrint {
void log(String? text, {LogLevel logLevel = LogLevel.info}) {
final payload = '[APP] $text';
debugPrint(payload);
if (!appController.isAttach) {
if (!globalState.isAttach) {
return;
}
appController.addLog(Log.app(payload).copyWith(logLevel: logLevel));
globalState.container
.read(logsProvider.notifier)
.add(Log.app(payload).copyWith(logLevel: logLevel));
}
}
+4 -4
View File
@@ -13,13 +13,13 @@ class Protocol {
}
void register(String scheme) {
String protocolRegKey = 'Software\\Classes\\$scheme';
RegistryValue protocolRegValue = RegistryValue.string(
final String protocolRegKey = 'Software\\Classes\\$scheme';
const RegistryValue protocolRegValue = RegistryValue.string(
'URL Protocol',
'',
);
String protocolCmdRegKey = 'shell\\open\\command';
RegistryValue protocolCmdRegValue = RegistryValue.string(
const String protocolCmdRegKey = 'shell\\open\\command';
final RegistryValue protocolCmdRegValue = RegistryValue.string(
'',
'"${Platform.resolvedExecutable}" "%1"',
);
+1 -1
View File
@@ -25,7 +25,7 @@ class Render {
throttler.call(
FunctionTag.renderPause,
_pause,
duration: Duration(seconds: 5),
duration: const Duration(seconds: 5),
);
}
+8 -6
View File
@@ -6,7 +6,6 @@ import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
@@ -24,7 +23,7 @@ class Request {
createHttpClient: () {
final client = HttpClient();
client.findProxy = (Uri uri) {
client.userAgent = appController.ua;
client.userAgent = globalState.ua;
return FlClashHttpOverrides.handleFindProxy(uri);
};
return client;
@@ -42,13 +41,13 @@ class Request {
commonPrint.log('getFileResponseForUrl error ${e.toString()}');
if (e is DioException) {
if (e.type == DioExceptionType.unknown) {
throw appLocalizations.unknownNetworkError;
throw currentAppLocalizations.unknownNetworkError;
} else if (e.type == DioExceptionType.badResponse) {
throw appLocalizations.networkException;
throw currentAppLocalizations.networkException;
}
rethrow;
}
throw appLocalizations.unknownNetworkError;
throw currentAppLocalizations.unknownNetworkError;
}
}
@@ -106,7 +105,7 @@ class Request {
final token = cancelToken ?? CancelToken();
final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer();
handleFailRes() {
void handleFailRes() {
if (!completer.isCompleted && failureCount == _ipInfoSources.length) {
completer.complete(Result.success(null));
}
@@ -125,6 +124,7 @@ class Request {
completer.complete(Result.success(source.value(res.data!)));
return;
}
commonPrint.log('checkIp data empty', logLevel: LogLevel.info);
failureCount++;
handleFailRes();
})
@@ -132,7 +132,9 @@ class Request {
failureCount++;
if (e is DioException && e.type == DioExceptionType.cancel) {
completer.complete(Result.error('cancelled'));
return;
}
commonPrint.log('checkIp error $e', logLevel: LogLevel.warning);
handleFailRes();
});
return completer.future;
+17 -11
View File
@@ -8,18 +8,24 @@ class Snowflake {
return _instance!;
}
static const int twepoch = 1704067200000;
static const int _twepoch = 1704067200000;
static const int workerIdBits = 10;
static const int sequenceBits = 12;
static const int _workerIdBits = 10;
static const int _sequenceBits = 12;
static const int maxWorkerId = -1 ^ (-1 << workerIdBits);
static const int sequenceMask = -1 ^ (-1 << sequenceBits);
static const int _sequenceMask = -1 ^ (-1 << _sequenceBits);
static const int workerIdShift = sequenceBits;
static const int timestampLeftShift = sequenceBits + workerIdBits;
static const int _workerIdShift = _sequenceBits;
static const int _timestampLeftShift = _sequenceBits + _workerIdBits;
final int workerId = 1;
static int buildId(int? id) {
if (id != null) {
return id;
}
return snowflake.id;
}
final int _workerId = 1;
int _lastTimestamp = -1;
int _sequence = 0;
@@ -31,7 +37,7 @@ class Snowflake {
);
}
if (timestamp == _lastTimestamp) {
_sequence = (_sequence + 1) & sequenceMask;
_sequence = (_sequence + 1) & _sequenceMask;
if (_sequence == 0) {
timestamp = _getNextMillis(_lastTimestamp);
}
@@ -41,8 +47,8 @@ class Snowflake {
_lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(workerId << workerIdShift) |
return ((timestamp - _twepoch) << _timestampLeftShift) |
(_workerId << _workerIdShift) |
_sequence;
}
+16 -4
View File
@@ -6,7 +6,12 @@ import 'package:fl_clash/common/common.dart';
extension StringExtension on String {
bool get isUrl {
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
final uri = Uri.tryParse(this);
return uri != null &&
(uri.scheme == 'http' ||
uri.scheme == 'https' ||
uri.scheme == 'ftp') &&
uri.host.isNotEmpty;
}
dynamic get splitByMultipleSeparators {
@@ -35,7 +40,7 @@ extension StringExtension on String {
final byteData = ByteData(length * 2);
final bom = [0xFF, 0xFE];
for (int i = 0; i < length; i++) {
int charCode = codeUnitAt(i);
final int charCode = codeUnitAt(i);
byteData.setUint16(i * 2, charCode, Endian.little);
}
return bom + byteData.buffer.asUint8List();
@@ -79,13 +84,20 @@ extension StringExtension on String {
// }
Future<T> commonToJSON<T>() async {
final thresholdLimit = 51200;
const thresholdLimit = 51200;
if (length < thresholdLimit) {
return json.decode(this);
} else {
return await decodeJSONTask<T>(this);
return decodeJSONTask<T>(this);
}
}
String? get value {
if (isEmpty) {
return null;
}
return this;
}
}
extension StringNullExt on String? {
+17 -7
View File
@@ -64,11 +64,14 @@ class System {
return true;
}
static String _shellEscape(String value) {
return "'${value.replaceAll("'", "'\\''")}'";
}
Future<AuthorizeCode> authorizeCore() async {
if (system.isAndroid) {
return AuthorizeCode.error;
}
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
@@ -83,7 +86,8 @@ class System {
}
if (system.isMacOS) {
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
final escapedPath = _shellEscape(appPath.corePath);
final shell = 'chown root:admin $escapedPath && chmod +sx $escapedPath';
final arguments = [
'-e',
'do shell script "$shell" with administrator privileges',
@@ -98,13 +102,18 @@ class System {
final password = await globalState.showCommonDialog<String>(
child: InputDialog(
obscureText: true,
title: appLocalizations.pleaseInputAdminPassword,
title: currentAppLocalizations.pleaseInputAdminPassword,
value: '',
),
);
if (password == null || password.isEmpty) {
return AuthorizeCode.error;
}
final escapedPassword = _shellEscape(password);
final escapedCorePath = _shellEscape(appPath.corePath);
final arguments = [
'-c',
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"',
'echo $escapedPassword | sudo -S chown root:root $escapedCorePath && echo $escapedPassword | sudo -S chmod +sx $escapedCorePath',
];
final result = await Process.run(shell, arguments);
if (result.exitCode != 0) {
@@ -125,6 +134,7 @@ class System {
await SystemNavigator.pop();
}
await window?.close();
window?.forceExit();
}
}
@@ -257,12 +267,12 @@ class Windows {
final res = runas('cmd.exe', command);
await Future.delayed(Duration(milliseconds: 300));
await Future.delayed(const Duration(milliseconds: 300));
final retryStatus = await retry(
task: checkService,
maxAttempts: 5,
retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: Duration(seconds: 1),
delay: const Duration(seconds: 1),
);
return res && retryStatus == WindowsHelperServiceStatus.running;
}
@@ -404,7 +414,7 @@ class MacOS {
if (originDns == null) {
return;
}
final needAddDns = '223.5.5.5';
const needAddDns = '223.5.5.5';
if (originDns.contains(needAddDns)) {
return;
}
+88 -58
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -13,7 +14,7 @@ import 'package:flutter/services.dart';
import 'package:path/path.dart';
Future<T> decodeJSONTask<T>(String data) async {
return await compute<String, T>(_decodeJSON, data);
return compute<String, T>(_decodeJSON, data);
}
Future<T> _decodeJSON<T>(String content) async {
@@ -21,7 +22,7 @@ Future<T> _decodeJSON<T>(String content) async {
}
Future<String> encodeJSONTask<T>(T data) async {
return await compute<T, String>(_encodeJSON, data);
return compute<T, String>(_encodeJSON, data);
}
Future<String> _encodeJSON<T>(T content) async {
@@ -29,15 +30,23 @@ Future<String> _encodeJSON<T>(T content) async {
}
Future<String> encodeYamlTask<T>(T data) async {
return await compute<T, String>(_encodeYaml, data);
return compute<T, String>(_encodeYaml, data);
}
Future<String> _encodeYaml<T>(T content) async {
return yaml.encode(content);
}
Future<String> encodeMD5Task(String data) async {
return compute<String, String>(_encodeMD5, data);
}
Future<String> _encodeMD5<T>(String content) async {
return content.toMd5();
}
Future<List<Group>> toGroupsTask(ComputeGroupsState data) async {
return await compute<ComputeGroupsState, List<Group>>(_toGroupsTask, data);
return compute<ComputeGroupsState, List<Group>>(_toGroupsTask, data);
}
Future<List<Group>> _toGroupsTask(ComputeGroupsState state) async {
@@ -73,16 +82,16 @@ Future<List<Group>> _toGroupsTask(ComputeGroupsState state) async {
);
}
Future<Map<String, dynamic>> makeRealProfileTask(
Future<VM2<String, String>> makeRealProfileTask(
MakeRealProfileState data,
) async {
return await compute<MakeRealProfileState, Map<String, dynamic>>(
return compute<MakeRealProfileState, VM2<String, String>>(
_makeRealProfileTask,
data,
);
}
Future<Map<String, dynamic>> _makeRealProfileTask(
Future<VM2<String, String>> _makeRealProfileTask(
MakeRealProfileState data,
) async {
final rawConfig = Map.from(data.rawConfig);
@@ -186,7 +195,7 @@ Future<Map<String, dynamic>> _makeRealProfileTask(
rawConfig['dns'] = {};
}
final isEnableDns = rawConfig['dns']['enable'] == true;
final systemDns = 'system://';
const systemDns = 'system://';
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
@@ -210,58 +219,62 @@ Future<Map<String, dynamic>> _makeRealProfileTask(
}
}
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 (data.rules.isEmpty) {
if (rawConfig['rules'] != null) {
rules = List<String>.from(rawConfig['rules']);
}
if (addedRules.isNotEmpty) {
final hasMatchPlaceholder = addedRules.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;
if (hasMatchPlaceholder) {
for (int i = rules.length - 1; i >= 0; i--) {
final parsed = Rule.parse(rules[i]);
if (parsed.ruleAction == RuleAction.MATCH) {
final target = parsed.ruleTarget;
if (target != null && target.isNotEmpty) {
replacementTarget = target;
break;
}
}
}
}
}
final List<String> finalAddedRules;
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);
if (replacementTarget?.isNotEmpty == true) {
finalAddedRules = [];
for (int i = 0; i < addedRules.length; i++) {
final parsed = addedRules[i];
if (parsed.ruleTarget?.toUpperCase() == 'MATCH') {
finalAddedRules.add(
parsed.copyWith(ruleTarget: replacementTarget).rawValue,
);
} else {
finalAddedRules.add(addedRules[i].rawValue);
}
}
} else {
finalAddedRules = addedRules.map((e) => e.rawValue).toList();
}
} else {
finalAddedRules = addedRules.map((e) => e.value).toList();
rules = [...finalAddedRules, ...rules];
}
rules = [...finalAddedRules, ...rules];
} else {
rules = data.rules.map((item) => item.rawValue).toList();
}
if (data.proxyGroups.isNotEmpty) {
rawConfig['proxy-groups'] = data.proxyGroups;
}
rawConfig['rules'] = rules;
return Map<String, dynamic>.from(rawConfig);
final yaml = await _encodeYaml(Map<String, dynamic>.from(rawConfig));
return VM2(yaml, yaml.toMd5());
}
Future<List<String>> shakingProfileTask(
VM2<Iterable<int>, Iterable<int>> data,
) async {
return await compute<
return compute<
VM3<Iterable<int>, Iterable<int>, RootIsolateToken>,
List<String>
>(_shakingProfileTask, VM3(data.a, data.b, RootIsolateToken.instance!));
@@ -307,7 +320,7 @@ Future<List<String>> _shakingProfileTask(
}
Future<String> encodeLogsTask(List<Log> data) async {
return await compute<List<Log>, String>(_encodeLogsTask, data);
return compute<List<Log>, String>(_encodeLogsTask, data);
}
Future<String> _encodeLogsTask(List<Log> data) async {
@@ -318,7 +331,7 @@ Future<String> _encodeLogsTask(List<Log> data) async {
Future<MigrationData> oldToNowTask(Map<String, Object?> data) async {
final homeDir = await appPath.homeDirPath;
return await compute<
return compute<
VM3<Map<String, Object?>, String, String>,
MigrationData
>(_oldToNowTask, VM3(data, homeDir, homeDir));
@@ -345,7 +358,8 @@ Future<MigrationData> _oldToNowTask(
vpnPropsRaw['accessControlProps'] = vpnPropsRaw['accessControl'];
}
configMap['davProps'] = configMap['dav'];
final appSettingProps = configMap['appSetting'] as Map? ?? {};
final appSettingProps =
configMap['appSetting'] as Map<String, dynamic>? ?? {};
appSettingProps['restoreStrategy'] = appSettingProps['recoveryStrategy'];
configMap['appSettingProps'] = appSettingProps;
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
@@ -377,16 +391,17 @@ Future<MigrationData> _oldToNowTask(
Script(id: newId, label: label, lastUpdateTime: DateTime.now()),
);
}
List rawRules = configMap['rules'] as List<dynamic>? ?? [];
final List rawRules = configMap['rules'] as List<dynamic>? ?? [];
final List<Rule> rules = [];
final List<ProfileRuleLink> links = [];
for (final rawRule in rawRules) {
final id = idMap.updateCacheValue(rawRule['id'], () => snowflake.id);
rawRule['id'] = id;
rules.add(Rule.fromJson(rawRule));
final value = rawRule['value'] ?? '';
rules.add(Rule.parse(value, id: id));
links.add(ProfileRuleLink(ruleId: id));
}
List rawProfiles = configMap['profiles'] as List<dynamic>? ?? [];
final List rawProfiles = configMap['profiles'] as List<dynamic>? ?? [];
final List<Profile> profiles = [];
for (final rawProfile in rawProfiles) {
final rawId = rawProfile['id'] as String?;
@@ -402,8 +417,8 @@ Future<MigrationData> _oldToNowTask(
final addedRules = standardOverwrite['addedRules'] as List? ?? [];
for (final addRule in addedRules) {
final id = idMap.updateCacheValue(addRule['id'], () => snowflake.id);
addRule['id'] = id;
rules.add(Rule.fromJson(addRule));
final value = addRule['value'] ?? '';
rules.add(Rule.parse(value, id: id));
links.add(
ProfileRuleLink(
profileId: profileId,
@@ -458,7 +473,7 @@ Future<String> backupTask(
Map<String, dynamic> configMap,
Iterable<String> fileNames,
) async {
return await compute<
return compute<
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken>,
String
>(_backupTask, VM3(configMap, fileNames, RootIsolateToken.instance!));
@@ -516,7 +531,7 @@ Future<String> _backupTask<T>(
}
Future<MigrationData> restoreTask() async {
return await compute<RootIsolateToken, MigrationData>(
return compute<RootIsolateToken, MigrationData>(
_restoreTask,
RootIsolateToken.instance!,
);
@@ -541,7 +556,7 @@ Future<MigrationData> _restoreTask(RootIsolateToken token) async {
await input.close();
final restoreConfigFile = File(join(restoreDirPath, configJsonName));
if (!await restoreConfigFile.exists()) {
throw appLocalizations.invalidBackupFile;
throw currentAppLocalizations.invalidBackupFile;
}
final restoreConfigMap =
json.decode(await restoreConfigFile.readAsString())
@@ -567,10 +582,11 @@ Future<MigrationData> _restoreTask(RootIsolateToken token) async {
),
);
final results = await Future.wait([
database.profilesDao.all().get(),
database.scriptsDao.all().get(),
database.profilesDao.query().get(),
database.scriptsDao.query().get(),
database.rules.all().map((item) => item.toRule()).get(),
database.profileRuleLinks.all().map((item) => item.toLink()).get(),
database.proxyGroups.all().map((item) => item.toProxyGroup()).get(),
]);
final profiles = results[0].cast<Profile>();
final scripts = results[1].cast<Script>();
@@ -592,6 +608,7 @@ Future<MigrationData> _restoreTask(RootIsolateToken token) async {
scripts: scripts,
rules: results[2].cast<Rule>(),
links: results[3].cast<ProfileRuleLink>(),
proxyGroups: results[4].cast<ProxyGroup>(),
);
await database.close();
return migrationData;
@@ -610,3 +627,16 @@ String _getScriptPath(String root, String fileName) {
String _getProfilePath(String root, String fileName) {
return join(root, 'profiles', '$fileName.yaml');
}
Future<List<T>> mapListTask<T, S>(List<S> results, T Function(S) mapper) async {
return compute<VM2<List<S>, T Function(S)>, List<T>>(
_mapListTask,
VM2(results, mapper),
);
}
Future<List<T>> _mapListTask<T, S>(VM2<List<S>, T Function(S)> vm2) async {
final results = vm2.a;
final mapper = vm2.b;
return results.map((item) => mapper(item)).toList();
}
+13 -10
View File
@@ -6,16 +6,15 @@ class CommonTheme {
final Map<String, Color> _colorMap;
final double textScaleFactor;
CommonTheme.of(
this.context,
this.textScaleFactor,
) : _colorMap = {};
CommonTheme.of(this.context, this.textScaleFactor) : _colorMap = {};
Color get darkenSecondaryContainer {
return _colorMap.updateCacheValue(
'darkenSecondaryContainer',
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1),
() => context.colorScheme.secondaryContainer.blendDarken(
context,
factor: 0.1,
),
);
}
@@ -31,16 +30,20 @@ class CommonTheme {
Color get darken2SecondaryContainer {
return _colorMap.updateCacheValue(
'darken2SecondaryContainer',
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.2),
() => context.colorScheme.secondaryContainer.blendDarken(
context,
factor: 0.2,
),
);
}
Color get darken3PrimaryContainer {
return _colorMap.updateCacheValue(
'darken3PrimaryContainer',
() => context.colorScheme.primaryContainer
.blendDarken(context, factor: 0.3),
() => context.colorScheme.primaryContainer.blendDarken(
context,
factor: 0.3,
),
);
}
}
+23 -16
View File
@@ -1,8 +1,9 @@
import 'dart:io';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:tray_manager/tray_manager.dart';
@@ -69,7 +70,12 @@ class Tray {
tunEnable: trayState.tunEnable,
);
}
List<MenuItem> menuItems = [];
final List<MenuItem> menuItems = [];
final ref = globalState.container;
final commonAction = ref.read(commonActionProvider.notifier);
final systemAction = ref.read(systemActionProvider.notifier);
final setupAction = ref.read(setupActionProvider.notifier);
final appLocalizations = currentAppLocalizations;
final showMenuItem = MenuItem(
label: appLocalizations.show,
onClick: (_) {
@@ -80,7 +86,7 @@ class Tray {
final startMenuItem = MenuItem.checkbox(
label: trayState.isStart ? appLocalizations.stop : appLocalizations.start,
onClick: (_) async {
appController.updateStart();
commonAction.updateStart();
},
checked: false,
);
@@ -89,7 +95,7 @@ class Tray {
final speedStatistics = MenuItem.checkbox(
label: appLocalizations.speedStatistics,
onClick: (_) async {
appController.updateSpeedStatistics();
commonAction.updateSpeedStatistics();
},
checked: trayState.showTrayTitle,
);
@@ -101,7 +107,7 @@ class Tray {
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
appController.changeMode(mode);
setupAction.changeMode(mode);
},
checked: mode == trayState.mode,
),
@@ -110,19 +116,20 @@ class Tray {
menuItems.add(MenuItem.separator());
if (system.isMacOS) {
for (final group in trayState.groups) {
List<MenuItem> subMenuItems = [];
final List<MenuItem> subMenuItems = [];
for (final proxy in group.all) {
subMenuItems.add(
MenuItem.checkbox(
label: proxy.name,
checked:
appController.getSelectedProxyName(group.name) == proxy.name,
ref.read(selectedProxyNameProvider(group.name)) == proxy.name,
onClick: (_) {
appController.updateCurrentSelectedMap(group.name, proxy.name);
appController.changeProxy(
groupName: group.name,
proxyName: proxy.name,
);
ref
.read(profilesActionProvider.notifier)
.updateCurrentSelectedMap(group.name, proxy.name);
ref
.read(proxiesActionProvider.notifier)
.changeProxy(groupName: group.name, proxyName: proxy.name);
},
),
);
@@ -143,7 +150,7 @@ class Tray {
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
appController.updateTun();
systemAction.updateTun();
},
checked: trayState.tunEnable,
),
@@ -152,7 +159,7 @@ class Tray {
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
appController.updateSystemProxy();
systemAction.updateSystemProxy();
},
checked: trayState.systemProxy,
),
@@ -162,7 +169,7 @@ class Tray {
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
appController.updateAutoLaunch();
systemAction.updateAutoLaunch();
},
checked: trayState.autoLaunch,
);
@@ -178,7 +185,7 @@ class Tray {
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await appController.handleExit();
await systemAction.handleExit();
},
);
menuItems.add(exitMenuItem);
+27 -21
View File
@@ -36,7 +36,7 @@ class Utils {
}
String getDateStringLast2(int value) {
var valueRaw = '0$value';
final valueRaw = '0$value';
return valueRaw.substring(valueRaw.length - 2);
}
@@ -45,7 +45,7 @@ class Utils {
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final random = Random();
int length = minLength + random.nextInt(maxLength - minLength + 1);
final int length = minLength + random.nextInt(maxLength - minLength + 1);
String result = '';
for (int i = 0; i < length; i++) {
@@ -76,11 +76,11 @@ class Utils {
}
String getTimeDifference(DateTime dateTime) {
var currentDateTime = DateTime.now();
var difference = currentDateTime.difference(dateTime);
var inHours = difference.inHours;
var inMinutes = difference.inMinutes;
var inSeconds = difference.inSeconds;
final currentDateTime = DateTime.now();
final difference = currentDateTime.difference(dateTime);
final inHours = difference.inHours;
final inMinutes = difference.inMinutes;
final inSeconds = difference.inSeconds;
return '${getDateStringLast2(inHours)}:${getDateStringLast2(inMinutes)}:${getDateStringLast2(inSeconds)}';
}
@@ -102,7 +102,7 @@ class Utils {
Locale? getLocaleForString(String? localString) {
if (localString == null) return null;
var localSplit = localString.split('_');
final localSplit = localString.split('_');
if (localSplit.length == 1) {
return Locale(localSplit[0]);
}
@@ -157,25 +157,29 @@ class Utils {
}
int compareVersions(String version1, String version2) {
List<String> v1 = version1.split('+')[0].split('.');
List<String> v2 = version2.split('+')[0].split('.');
int major1 = int.parse(v1[0]);
int major2 = int.parse(v2[0]);
final List<String> v1 = version1.split('+')[0].split('.');
final List<String> v2 = version2.split('+')[0].split('.');
final int major1 = int.parse(v1[0]);
final int major2 = int.parse(v2[0]);
if (major1 != major2) {
return major1.compareTo(major2);
}
int minor1 = v1.length > 1 ? int.parse(v1[1]) : 0;
int minor2 = v2.length > 1 ? int.parse(v2[1]) : 0;
final int minor1 = v1.length > 1 ? int.parse(v1[1]) : 0;
final int minor2 = v2.length > 1 ? int.parse(v2[1]) : 0;
if (minor1 != minor2) {
return minor1.compareTo(minor2);
}
int patch1 = v1.length > 2 ? int.parse(v1[2]) : 0;
int patch2 = v2.length > 2 ? int.parse(v2[2]) : 0;
final int patch1 = v1.length > 2 ? int.parse(v1[2]) : 0;
final int patch2 = v2.length > 2 ? int.parse(v2[2]) : 0;
if (patch1 != patch2) {
return patch1.compareTo(patch2);
}
int build1 = version1.contains('+') ? int.parse(version1.split('+')[1]) : 0;
int build2 = version2.contains('+') ? int.parse(version2.split('+')[1]) : 0;
final int build1 = version1.contains('+')
? int.parse(version1.split('+')[1])
: 0;
final int build2 = version2.contains('+')
? int.parse(version2.split('+')[1])
: 0;
return build1.compareTo(build2);
}
@@ -295,7 +299,7 @@ class Utils {
}
Future<String?> getLocalIpAddress() async {
List<NetworkInterface> interfaces =
final List<NetworkInterface> interfaces =
await NetworkInterface.list(includeLoopback: false)
..sort((a, b) {
if (a.isWifi && !b.isWifi) return -1;
@@ -326,13 +330,15 @@ class Utils {
FutureOr<T> handleWatch<T>({
required Function function,
required void Function(T data, int elapsedMilliseconds) onWatch,
required void Function() onStart,
required void Function(T data, int elapsedMilliseconds) onEnd,
}) async {
if (kDebugMode && watchExecution) {
onStart();
final stopwatch = Stopwatch()..start();
final res = await function();
stopwatch.stop();
onWatch(res, stopwatch.elapsedMilliseconds);
onEnd(res, stopwatch.elapsedMilliseconds);
return res;
}
return await function();
+5 -2
View File
@@ -28,14 +28,14 @@ class Window {
}
await windowManager.ensureInitialized();
// kDebugMode ? Size(680, 580) :
WindowOptions windowOptions = WindowOptions(
final WindowOptions windowOptions = WindowOptions(
size: props.size,
minimumSize: const Size(380, 400),
);
if (!system.isMacOS || version > 10) {
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
}
await windowManager.setMaximizable(false);
await windowManager.setMaximizable(true);
await _windowPosition(props);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true);
@@ -84,6 +84,9 @@ class Window {
Future<void> close() async {
await windowManager.close();
}
void forceExit() {
exit(0);
}
-1202
View File
File diff suppressed because it is too large Load Diff
+28 -17
View File
@@ -7,6 +7,7 @@ import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/core/interface.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';
import 'package:path/path.dart';
@@ -22,6 +23,14 @@ class CoreController {
}
}
@visibleForTesting
CoreController.test(this._interface);
@visibleForTesting
static void resetInstance() {
_instance = null;
}
factory CoreController() {
_instance ??= CoreController._internal();
return _instance!;
@@ -49,20 +58,22 @@ class CoreController {
continue;
}
final data = await rootBundle.load('assets/data/$geoFileName');
List<int> bytes = data.buffer.asUint8List();
final List<int> bytes = data.buffer.asUint8List();
await geoFile.writeAsBytes(bytes, flush: true);
}
} catch (e) {
exit(0);
commonPrint.log(
'Failed to initialize geo data: $e',
logLevel: LogLevel.error,
);
rethrow;
}
}
Future<bool> init(int version) async {
await initGeo();
final homeDirPath = await appPath.homeDirPath;
return await _interface.init(
InitParams(homeDir: homeDirPath, version: version),
);
return _interface.init(InitParams(homeDir: homeDirPath, version: version));
}
Future<void> shutdown(bool isUser) async {
@@ -86,7 +97,7 @@ class CoreController {
}
Future<String> updateConfig(UpdateParams updateParams) async {
return await _interface.updateConfig(updateParams);
return _interface.updateConfig(updateParams);
}
Future<String> setupConfig({
@@ -130,16 +141,16 @@ class CoreController {
return connectionsRaw.map((e) => TrackerInfo.fromJson(e)).toList();
}
void closeConnection(String id) {
_interface.closeConnection(id);
Future<void> closeConnection(String id) async {
await _interface.closeConnection(id);
}
void closeConnections() {
_interface.closeConnections();
Future<void> closeConnections() async {
await _interface.closeConnections();
}
void resetConnections() {
_interface.resetConnections();
Future<void> resetConnections() async {
await _interface.resetConnections();
}
Future<List<ExternalProvider>> getExternalProviders() async {
@@ -185,11 +196,11 @@ class CoreController {
}
Future<bool> startListener() async {
return await _interface.startListener();
return _interface.startListener();
}
Future<bool> stopListener() async {
return await _interface.stopListener();
return _interface.stopListener();
}
Future<Delay> getDelay(String url, String proxyName) async {
@@ -213,7 +224,7 @@ class CoreController {
Future<Traffic> getTraffic(bool onlyStatisticsProxy) async {
final trafficString = await _interface.getTraffic(onlyStatisticsProxy);
if (trafficString.isEmpty) {
return Traffic();
return const Traffic();
}
return Traffic.fromJson(json.decode(trafficString));
}
@@ -231,7 +242,7 @@ class CoreController {
onlyStatisticsProxy,
);
if (totalTrafficString.isEmpty) {
return Traffic();
return const Traffic();
}
return Traffic.fromJson(json.decode(totalTrafficString));
}
@@ -269,7 +280,7 @@ class CoreController {
}
Future<String> deleteFile(String path) async {
return await _interface.deleteFile(path);
return _interface.deleteFile(path);
}
}
+11 -13
View File
@@ -4,7 +4,6 @@ import 'dart:convert';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
mixin CoreInterface {
Future<bool> init(InitParams params);
@@ -94,15 +93,14 @@ abstract class CoreHandlerInterface with CoreInterface {
);
return null;
}
if (kDebugMode && watchExecution) {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
}
return await utils.handleWatch(
function: () async {
return await invoke<T>(method: method, data: data, timeout: timeout);
onStart: () {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
},
onWatch: (data, elapsedMilliseconds) {
function: () async {
return invoke<T>(method: method, data: data, timeout: timeout);
},
onEnd: (data, elapsedMilliseconds) {
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
},
);
@@ -188,7 +186,7 @@ abstract class CoreHandlerInterface with CoreInterface {
);
return data != null
? ProxiesData.fromJson(data)
: ProxiesData(proxies: {}, all: []);
: const ProxiesData(proxies: {}, all: []);
}
@override
@@ -294,17 +292,17 @@ abstract class CoreHandlerInterface with CoreInterface {
}
@override
resetTraffic() {
FutureOr<void> resetTraffic() {
_invoke(method: ActionMethod.resetTraffic);
}
@override
startLog() {
FutureOr<void> startLog() {
_invoke(method: ActionMethod.startLog);
}
@override
stopLog() {
FutureOr<void> stopLog() {
_invoke<bool>(method: ActionMethod.stopLog);
}
@@ -328,7 +326,7 @@ abstract class CoreHandlerInterface with CoreInterface {
return await _invoke<String>(
method: ActionMethod.asyncTestDelay,
data: json.encode(delayParams),
timeout: Duration(seconds: 6),
timeout: const Duration(seconds: 6),
) ??
json.encode(Delay(name: proxyName, value: -1, url: url));
}
+9 -3
View File
@@ -1,10 +1,11 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'interface.dart';
@@ -17,12 +18,17 @@ class CoreLib extends CoreHandlerInterface {
@override
Future<String> preload() async {
if (_connectedCompleter.isCompleted) {
return 'core is connected';
}
final res = await service?.init();
if (res?.isEmpty != true) {
return res ?? '';
}
_connectedCompleter.complete(true);
final syncRes = await service?.syncState(appController.sharedState);
final syncRes = await service?.syncState(
globalState.container.read(sharedStateProvider),
);
return syncRes ?? '';
}
@@ -32,7 +38,7 @@ class CoreLib extends CoreHandlerInterface {
}
@override
destroy() async {
FutureOr<bool> destroy() async {
return true;
}
+55 -78
View File
@@ -8,13 +8,12 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart';
import 'interface.dart';
import 'transport.dart';
class CoreService extends CoreHandlerInterface {
static CoreService? _instance;
final Completer<ServerSocket> _serverCompleter = Completer();
Completer<Socket> _socketCompleter = Completer();
late final IPCCoreTransport _transport;
Completer<bool> _shutdownCompleter = Completer();
@@ -28,6 +27,9 @@ class CoreService extends CoreHandlerInterface {
}
CoreService._internal() {
_transport = IPCCoreTransport(
address: system.isWindows ? windowsPipeName : unixSocketPath,
);
_initServer();
}
@@ -44,52 +46,42 @@ class CoreService extends CoreHandlerInterface {
}
Future<void> _initServer() async {
final server = await retry(
task: () async {
try {
final address = !system.isWindows
? InternetAddress(unixSocketPath, type: InternetAddressType.unix)
: InternetAddress(localhost, type: InternetAddressType.IPv4);
await _deleteSocketFile();
final server = await ServerSocket.bind(address, 0, shared: true);
server.listen((socket) async {
await _attachSocket(socket);
});
return server;
} catch (_) {
return null;
}
},
retryIf: (server) => server == null,
);
if (server == null) {
exit(0);
}
_serverCompleter.complete(server);
}
await _transport.init();
Future<void> _attachSocket(Socket socket) async {
await _destroySocket();
_socketCompleter.complete(socket);
socket
_transport.onDisconnect = () {
_handleInvokeCrashEvent();
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete(true);
}
};
_transport.dataStream
.transform(uint8ListToListIntConverter)
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((data) async {
final dataJson = await data.trim().commonToJSON<dynamic>();
handleResult(ActionResult.fromJson(dataJson));
})
.onDone(() {
_handleInvokeCrashEvent();
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete(true);
}
});
.listen(
(data) async {
try {
final dataJson = await data.trim().commonToJSON<dynamic>();
handleResult(ActionResult.fromJson(dataJson));
} catch (e) {
commonPrint.log(
'Failed to parse transport data: $e',
logLevel: LogLevel.error,
);
}
},
onError: (error) {
commonPrint.log(
'Transport data stream error: $error',
logLevel: LogLevel.error,
);
},
);
}
void _handleInvokeCrashEvent() {
coreEventManager.sendEvent(
CoreEvent(type: CoreEventType.crash, data: 'socket done'),
const CoreEvent(type: CoreEventType.crash, data: 'core done'),
);
}
@@ -97,17 +89,23 @@ class CoreService extends CoreHandlerInterface {
if (_process != null) {
await shutdown(false);
}
final serverSocket = await _serverCompleter.future;
final arg = system.isWindows
? '${serverSocket.port}'
: serverSocket.address.address;
if (system.isWindows && await system.checkIsAdmin()) {
final isSuccess = await request.startCoreByHelper(arg);
final isSuccess = await request.startCoreByHelper(_transport.address);
if (isSuccess) {
await _transport.connectionCompleter.future;
return;
}
}
_process = await Process.start(appPath.corePath, [arg]);
try {
_process = await Process.start(appPath.corePath, [_transport.address]);
} catch (e) {
commonPrint.log(
'Failed to start core process: $e',
logLevel: LogLevel.error,
);
_handleInvokeCrashEvent();
return;
}
_process?.stdout.listen((_) {});
_process?.stderr.listen((e) {
final error = utf8.decode(e);
@@ -115,51 +113,31 @@ class CoreService extends CoreHandlerInterface {
commonPrint.log(error, logLevel: LogLevel.warning);
}
});
await _socketCompleter.future;
await _transport.connectionCompleter.future;
}
@override
destroy() async {
final server = await _serverCompleter.future;
FutureOr<bool> destroy() async {
await shutdown(false);
await server.close();
await _deleteSocketFile();
await _transport.close();
return true;
}
Future<void> sendMessage(String message) async {
final socket = await _socketCompleter.future;
socket.writeln(message);
}
Future<void> _deleteSocketFile() async {
if (!system.isWindows) {
final file = File(unixSocketPath);
await file.safeDelete();
}
}
Future<void> _destroySocket() async {
if (_socketCompleter.isCompleted) {
final socket = await _socketCompleter.future;
_socketCompleter = Completer();
await socket.close();
}
await _transport.connectionCompleter.future;
_transport.send(message);
}
@override
shutdown(bool isUser) async {
if (!_socketCompleter.isCompleted && _process == null) {
return false;
}
Future<bool> shutdown(bool isUser) async {
_shutdownCompleter = Completer();
await _destroySocket();
_clearCompleter();
if (system.isWindows) {
await request.stopCoreByHelper();
}
_transport.disconnected();
_process?.kill();
_process = null;
_clearCompleter();
if (isUser) {
return _shutdownCompleter.future;
} else {
@@ -175,7 +153,6 @@ class CoreService extends CoreHandlerInterface {
@override
Future<String> preload() async {
await _serverCompleter.future;
await start();
return '';
}
@@ -202,7 +179,7 @@ class CoreService extends CoreHandlerInterface {
}
@override
Completer get completer => _socketCompleter;
Completer get completer => _transport.connectionCompleter;
}
final coreService = system.isDesktop ? CoreService() : null;
+106
View File
@@ -0,0 +1,106 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:rust_api/rust_api.dart';
// ── Binary frame types (mirrors Rust ipc.rs) ────────────────────────────────
const _typeReady = 0x00;
const _typeConnected = 0x01;
const _typeDisconnected = 0x02;
const _typeData = 0x03;
const _typeError = 0x04;
class IPCCoreTransport {
final String address;
final StreamController<Uint8List> _dataController =
StreamController<Uint8List>();
StreamSubscription<Uint8List>? _subscription;
Completer<void> _completer = Completer<void>();
Completer<void> _readyCompleter = Completer<void>();
void Function()? onDisconnect;
IPCCoreTransport({required this.address});
Completer<void> get connectionCompleter => _completer;
Stream<Uint8List> get dataStream => _dataController.stream;
Future<void> init() async {
try {
final stream = restartIpcServer(name: address);
_subscription = stream.listen(
(data) {
if (data.isEmpty) return;
final type = data[0];
final payload = data.length > 1 ? data.sublist(1) : Uint8List(0);
switch (type) {
case _typeReady:
commonPrint.log('IPC Ready');
if (_readyCompleter.isCompleted) {
break;
}
_readyCompleter.complete();
break;
case _typeConnected:
commonPrint.log('IPC Connected');
if (_completer.isCompleted) {
break;
}
_completer.complete();
break;
case _typeDisconnected:
commonPrint.log('IPC Disconnected');
_completer = Completer<void>();
onDisconnect?.call();
break;
case _typeData:
_dataController.add(payload);
break;
case _typeError:
final msg = utf8.decode(payload);
commonPrint.log('IPC error: $msg', logLevel: LogLevel.error);
break;
default:
commonPrint.log(
'IPC unknown frame type: $type',
logLevel: LogLevel.warning,
);
}
},
onError: (error) {
commonPrint.log('IPC error: $error', logLevel: LogLevel.error);
},
cancelOnError: false,
);
await _readyCompleter.future;
} catch (e) {
commonPrint.log(
'Failed to start IPC server: $e',
logLevel: LogLevel.error,
);
rethrow;
}
}
void send(String message) {
sendIpcMessage(data: utf8.encode(message));
}
void disconnected() {
_completer = Completer<void>();
}
Future<void> close() async {
await _subscription?.cancel();
_subscription = null;
await stopIpcServer();
_readyCompleter = Completer<void>();
_completer = Completer<void>();
await _dataController.close();
}
}
+43
View File
@@ -0,0 +1,43 @@
part of 'database.dart';
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringListConverter extends TypeConverter<List<String>, String> {
const StringListConverter();
@override
List<String> fromSql(String fromDb) {
return List<String>.from(json.decode(fromDb));
}
@override
String toSql(List<String> value) {
return json.encode(value.toList());
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}
+133 -7
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -8,21 +9,31 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
part 'converter.dart';
part 'generated/database.g.dart';
part 'groups.dart';
part 'icons.dart';
part 'links.dart';
part 'profiles.dart';
part 'rules.dart';
part 'scripts.dart';
@DriftDatabase(
tables: [Profiles, Scripts, Rules, ProfileRuleLinks],
daos: [ProfilesDao, ScriptsDao, RulesDao],
tables: [
Profiles,
Scripts,
Rules,
ProfileRuleLinks,
ProxyGroups,
IconRecords,
],
daos: [ProfilesDao, ScriptsDao, RulesDao, ProxyGroupsDao, IconRecordsDao],
)
class Database extends _$Database {
Database([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;
int get schemaVersion => 2;
static LazyDatabase _openConnection() {
return LazyDatabase(() async {
@@ -31,11 +42,84 @@ class Database extends _$Database {
});
}
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: (m, from, to) async {
if (from < 2) {
await m.createTable(proxyGroups);
await m.createTable(iconRecords);
await _resetOrders();
await _migrateRules(m);
}
},
beforeOpen: (details) async {
// final m = Migrator(this);
// await m.createTable(iconRecords);
// await _migrateRules(m);
// await m.deleteTable('proxy_groups');
// await m.createTable(proxyGroups);
},
);
}
Future<void> _migrateRules(Migrator m) async {
final tableInfo = await customSelect('PRAGMA table_info(rules)').get();
final columnNames = tableInfo
.map((row) => row.read<String>('name'))
.toList();
if (columnNames.isEmpty) {
await m.createTable(rules);
return;
} else if (columnNames.contains('rule_action')) {
return;
}
await customStatement(
'ALTER TABLE rules ADD COLUMN rule_action TEXT NOT NULL DEFAULT ""',
);
await customStatement('ALTER TABLE rules ADD COLUMN content TEXT');
await customStatement('ALTER TABLE rules ADD COLUMN rule_target TEXT');
await customStatement('ALTER TABLE rules ADD COLUMN rule_provider TEXT');
await customStatement('ALTER TABLE rules ADD COLUMN sub_rule TEXT');
await customStatement(
'ALTER TABLE rules ADD COLUMN no_resolve INTEGER NOT NULL DEFAULT 0',
);
await customStatement(
'ALTER TABLE rules ADD COLUMN src INTEGER NOT NULL DEFAULT 0',
);
final oldRows = await customSelect('SELECT id, value FROM rules').get();
for (final row in oldRows) {
final id = row.read<int>('id');
final value = row.read<String>('value');
final parsed = Rule.parse(value, id: id);
await customStatement(
'UPDATE rules SET rule_action = ?, content = ?, rule_target = ?, rule_provider = ?, sub_rule = ?, no_resolve = ?, src = ? WHERE id = ?',
[
parsed.ruleAction.name,
parsed.content,
parsed.ruleTarget,
parsed.ruleProvider,
parsed.subRule,
parsed.noResolve ? 1 : 0,
parsed.src ? 1 : 0,
id,
],
);
}
await customStatement('ALTER TABLE rules DROP COLUMN value');
await m.createIndex(idxRuleTarget);
}
Future<void> _resetOrders() async {
await rulesDao.resetOrders();
}
Future<void> restore(
List<Profile> profiles,
List<Script> scripts,
List<Rule> rules,
List<ProfileRuleLink> links, {
List<ProfileRuleLink> links,
List<ProxyGroup> proxyGroups, {
bool isOverride = false,
}) async {
if (profiles.isNotEmpty ||
@@ -51,9 +135,21 @@ class Database extends _$Database {
);
scriptsDao.setAllWithBatch(b, scripts);
rulesDao.restoreWithBatch(b, rules, links);
proxyGroupsDao.setAllWithBatch(null, b, proxyGroups);
});
}
}
Future<void> setProfileCustomData(
int profileId,
List<ProxyGroup> groups,
List<Rule> rules,
) async {
await batch((b) {
proxyGroupsDao.setAllWithBatch(profileId, b, groups);
rulesDao.setCustomRulesWithBatch(profileId, b, rules);
});
}
}
extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
@@ -61,17 +157,47 @@ extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
Batch batch,
Iterable<Insertable<Row>> items, {
required Expression<bool> Function(Tbl tbl) deleteFilter,
bool preDelete = false,
}) async {
if (preDelete) {
batch.deleteWhere(this, deleteFilter);
}
batch.insertAllOnConflictUpdate(this, items);
batch.deleteWhere(this, deleteFilter);
if (!preDelete) {
batch.deleteWhere(this, deleteFilter);
}
}
Selectable<int?> get count {
final countExp = countAll();
final query = select().addColumns([countExp]);
return query.map((row) => row.read(countExp));
}
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
return await (delete()..where(filter)).go();
return (delete()..where(filter)).go();
}
Future<int> put(Insertable<Row> item) async {
return await insertOnConflictUpdate(item);
return insertOnConflictUpdate(item);
}
}
extension SimpleSelectStatementExt<T extends HasResultSet, D>
on SimpleSelectStatement<T, D> {
Selectable<int> get count {
final countExp = countAll();
final query = addColumns([countExp]);
return query.map((row) => row.read(countExp)!);
}
}
extension JoinedSelectStatementExt<T extends HasResultSet, D>
on JoinedSelectStatement<T, D> {
Selectable<int> get count {
final countExp = countAll();
addColumns([countExp]);
return map((row) => row.read(countExp)!);
}
}
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
part of 'database.dart';
@DataClassName('RawProxyGroup')
@TableIndex(
name: 'idx_profile_name_order',
columns: {#profileId, #name, #order},
)
class ProxyGroups extends Table {
@override
String get tableName => 'proxy_groups';
IntColumn get id => integer()();
IntColumn get profileId => integer().nullable().references(
Profiles,
#id,
onDelete: KeyAction.cascade,
)();
TextColumn get name => text()();
TextColumn get type => text()();
TextColumn get proxies =>
text().map(const StringListConverter()).nullable()();
TextColumn get use => text().map(const StringListConverter()).nullable()();
TextColumn get url => text().nullable()();
IntColumn get interval => integer().nullable()();
IntColumn get timeout => integer().nullable()();
IntColumn get maxFailedTimes => integer().nullable()();
BoolColumn get lazy => boolean().nullable()();
BoolColumn get disableUDP => boolean().nullable()();
TextColumn get filter => text().nullable()();
TextColumn get excludeFilter => text().nullable()();
TextColumn get excludeType => text().nullable()();
TextColumn get expectedStatus => text().nullable()();
BoolColumn get includeAll => boolean().nullable()();
BoolColumn get includeAllProxies => boolean().nullable()();
BoolColumn get includeAllProviders => boolean().nullable()();
BoolColumn get hidden => boolean().nullable()();
TextColumn get icon => text().nullable()();
TextColumn get order => text().nullable()();
@override
Set<Column> get primaryKey => {id};
}
@DriftAccessor(tables: [ProxyGroups])
class ProxyGroupsDao extends DatabaseAccessor<Database>
with _$ProxyGroupsDaoMixin {
ProxyGroupsDao(super.attachedDatabase);
Selectable<ProxyGroup> query(int profileId) {
final stmt = proxyGroups.select();
stmt.where((row) => row.profileId.equals(profileId));
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
]);
return stmt.map((item) => item.toProxyGroup());
}
Selectable<int> count(int profileId) {
final stmt = proxyGroups.select();
stmt.where((row) => row.profileId.equals(profileId));
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
]);
return stmt.count;
}
Future<int> order(
int profileId, {
required ProxyGroup proxyGroup,
required String order,
}) async {
return proxyGroups.insertOnConflictUpdate(
proxyGroup.toCompanion(profileId, order),
);
}
Future<void> renameProxies(
int profileId, {
required String oldName,
required String newName,
}) {
return customUpdate(
'UPDATE ${proxyGroups.entityName} '
'SET ${proxyGroups.proxies.name} = REPLACE(${proxyGroups.proxies.name}, ?, ?) '
'WHERE ${proxyGroups.profileId.name} = ?',
variables: [
Variable.withString('"$oldName"'),
Variable.withString('"$newName"'),
Variable.withInt(profileId),
],
);
}
void setAllWithBatch(
int? profileId,
Batch batch,
Iterable<ProxyGroup> proxyGroups,
) async {
final keys = indexing.generateNKeys(proxyGroups.length);
this.proxyGroups.setAll(
batch,
proxyGroups.mapIndexed(
(index, item) => item.toCompanion(profileId, keys[index]),
),
deleteFilter: (row) => profileId == null
? const Constant(true)
: row.profileId.equals(profileId),
preDelete: true,
);
}
}
extension RawProxyGroupExt on RawProxyGroup {
ProxyGroup toProxyGroup() {
return ProxyGroup(
profileId: profileId,
id: id,
name: name,
type: GroupType.parse(type),
proxies: proxies,
use: use,
url: url,
interval: interval,
timeout: timeout,
maxFailedTimes: maxFailedTimes,
lazy: lazy,
disableUDP: disableUDP,
filter: filter,
excludeFilter: excludeFilter,
excludeType: excludeType,
expectedStatus: expectedStatus,
includeAll: includeAll,
includeAllProxies: includeAllProxies,
includeAllProviders: includeAllProviders,
hidden: hidden,
icon: icon,
order: order,
);
}
}
extension ProxyGroupsCompanionExt on ProxyGroup {
ProxyGroupsCompanion toCompanion([int? profileId, String? order]) {
return ProxyGroupsCompanion.insert(
id: Value(id),
profileId: Value(this.profileId ?? profileId),
name: name,
type: type.value,
proxies: Value(proxies),
use: Value(use),
url: Value(url),
interval: Value(interval),
timeout: Value(timeout),
maxFailedTimes: Value(maxFailedTimes),
lazy: Value(lazy),
disableUDP: Value(disableUDP),
filter: Value(filter),
excludeFilter: Value(excludeFilter),
excludeType: Value(excludeType),
expectedStatus: Value(expectedStatus),
includeAll: Value(includeAll),
includeAllProxies: Value(includeAllProxies),
includeAllProviders: Value(includeAllProviders),
hidden: Value(hidden),
icon: Value(icon),
order: Value(order ?? this.order),
);
}
}
+87
View File
@@ -0,0 +1,87 @@
part of 'database.dart';
@DataClassName('IconRecord')
@TableIndex(name: 'last_accessed_url', columns: {#lastAccessed, #url})
class IconRecords extends Table {
@override
String get tableName => 'icon_records';
TextColumn get url => text()();
IntColumn get lastAccessed => integer()();
@override
Set<Column> get primaryKey => {url};
}
@DriftAccessor(tables: [IconRecords])
class IconRecordsDao extends DatabaseAccessor<Database>
with _$IconRecordsDaoMixin {
IconRecordsDao(super.attachedDatabase);
final int maxCapacity = 1000;
Future<IconRecord?> get(String url) async {
final now = DateTime.now().millisecondsSinceEpoch;
return transaction(() async {
final query = select(iconRecords)..where((t) => t.url.equals(url));
final record = await query.getSingleOrNull();
if (record != null) {
await (update(iconRecords)..where((t) => t.url.equals(url))).write(
IconRecordsCompanion(lastAccessed: Value(now)),
);
}
return record;
});
}
Future<void> putIfAbsent(String url) async {
final existing = await (select(
iconRecords,
)..where((t) => t.url.equals(url))).getSingleOrNull();
if (existing != null) return;
await put(url);
}
Future<void> put(String url) async {
final now = DateTime.now().millisecondsSinceEpoch;
await transaction(() async {
await into(iconRecords).insertOnConflictUpdate(
IconRecordsCompanion.insert(url: url, lastAccessed: now),
);
final count = await iconRecords.count.getSingle() ?? 0;
if (count > maxCapacity) {
final oldestRecords =
await (select(iconRecords)
..orderBy([
(t) => OrderingTerm(
expression: t.lastAccessed,
mode: OrderingMode.asc,
),
])
..limit(count - maxCapacity))
.get();
final oldestUrls = oldestRecords.map((e) => e.url).toList();
await (delete(iconRecords)..where((t) => t.url.isIn(oldestUrls))).go();
}
});
}
Future<List<IconRecord>> query(String query) {
return (select(iconRecords)
..where((t) => t.url.contains(query))
..orderBy([
(t) => OrderingTerm(
expression: t.lastAccessed,
mode: OrderingMode.desc,
),
]))
.get();
}
}
+2 -2
View File
@@ -40,13 +40,13 @@ extension RawProfileRuleLinkExt on RawProfileRuleLink {
}
extension ProfileRuleLinksCompanionExt on ProfileRuleLink {
ProfileRuleLinksCompanion toCompanion() {
ProfileRuleLinksCompanion toCompanion([String? order]) {
return ProfileRuleLinksCompanion.insert(
id: key,
ruleId: ruleId,
scene: Value(scene),
profileId: Value(profileId),
order: Value(order),
order: Value(order ?? this.order),
);
}
}
+6 -29
View File
@@ -57,7 +57,7 @@ class SubscriptionInfoConverter
class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
ProfilesDao(super.attachedDatabase);
Selectable<Profile> all() {
Selectable<Profile> query() {
final stmt = profiles.select();
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
@@ -87,6 +87,11 @@ class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
batch.insertAllOnConflictUpdate(profiles, items);
}
Selectable<String> fileNames() {
final query = profiles.selectOnly()..addColumns([profiles.id]);
return query.map((row) => '${row.read(profiles.id)}.yaml');
}
void setAllWithBatch(Batch batch, Iterable<Profile> profiles) {
final List<ProfilesCompanion> items = [];
final List<int> ids = [];
@@ -99,34 +104,6 @@ class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
}
}
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}
extension RawProfilExt on RawProfile {
Profile toProfile() {
return Profile(
+162 -79
View File
@@ -1,13 +1,26 @@
part of 'database.dart';
@DataClassName('RawRule')
@TableIndex(name: 'idx_rule_target', columns: {#ruleTarget})
class Rules extends Table {
@override
String get tableName => 'rules';
IntColumn get id => integer()();
TextColumn get value => text()();
TextColumn get ruleAction => textEnum<RuleAction>()();
TextColumn get content => text().nullable()();
TextColumn get ruleTarget => text().nullable()();
TextColumn get ruleProvider => text().nullable()();
TextColumn get subRule => text().nullable()();
BoolColumn get noResolve => boolean().withDefault(const Constant(false))();
BoolColumn get src => boolean().withDefault(const Constant(false))();
@override
Set<Column> get primaryKey => {id};
@@ -17,19 +30,31 @@ class Rules extends Table {
class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
RulesDao(super.attachedDatabase);
Selectable<Rule> allGlobalAddedRules() {
return _get();
Selectable<Rule> queryGlobalAddedRules() {
return _query();
}
Selectable<Rule> allProfileAddedRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.added);
Selectable<Rule> queryProfileAddedRules(int profileId) {
return _query(profileId: profileId, scene: RuleScene.added);
}
Selectable<Rule> allProfileDisabledRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.disabled);
Selectable<Rule> queryProfileDisabledRules(int profileId) {
return _query(profileId: profileId, scene: RuleScene.disabled);
}
Selectable<Rule> allAddedRules(int profileId) {
Selectable<Rule> queryProfileCustomRules(int profileId) {
return _query(profileId: profileId, scene: RuleScene.custom);
}
Selectable<int> profileCustomRulesCount(int profileId) {
final query = _getSelectStatement(
profileId: profileId,
scene: RuleScene.custom,
);
return query.count;
}
Selectable<Rule> queryAddedRules(int profileId) {
final disabledIdsQuery = selectOnly(profileRuleLinks)
..addColumns([profileRuleLinks.ruleId])
..where(
@@ -65,6 +90,26 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
});
}
Future<void> resetOrders() async {
final stmt = profileRuleLinks.select();
stmt.orderBy([
(t) => OrderingTerm.asc(t.scene),
//v0.8.92 ordering desc
(t) => OrderingTerm.desc(t.order),
(t) => OrderingTerm.desc(t.id),
]);
final links = await stmt.map((item) => item.toLink()).get();
final keys = indexing.generateNKeys(links.length);
await batch((b) {
b.insertAllOnConflictUpdate(
profileRuleLinks,
links.mapIndexed((index, item) => item.toCompanion(keys[index])),
);
});
}
void restoreWithBatch(
Batch batch,
Iterable<Rule> rules,
@@ -76,9 +121,10 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
);
final ruleIds = rules.map((item) => item.id);
batch.deleteWhere(this.rules, (t) => t.id.isNotIn(ruleIds));
final keys = indexing.generateNKeys(links.length);
batch.insertAllOnConflictUpdate(
profileRuleLinks,
links.map((item) => item.toCompanion()),
links.mapIndexed((index, item) => item.toCompanion(keys[index])),
);
final linkKeys = links.map((item) => item.key);
batch.deleteWhere(profileRuleLinks, (t) => t.id.isNotIn(linkKeys));
@@ -96,20 +142,20 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putProfileCustomRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.custom);
}
Future<void> putProfileDisabledRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putGlobalRules(Iterable<Rule> rules) {
return _putAll(rules);
}
Future<void> setGlobalRules(Iterable<Rule> rules) {
return _set(rules);
void setCustomRulesWithBatch(int profileId, Batch b, Iterable<Rule> rules) {
_setWithBatch(b, rules, profileId: profileId, scene: RuleScene.custom);
}
Future<int> putDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.insertOnConflictUpdate(
return profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: ruleId,
profileId: profileId,
@@ -119,7 +165,7 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
}
Future<bool> delDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.deleteOne(
return profileRuleLinks.deleteOne(
ProfileRuleLink(
profileId: profileId,
ruleId: ruleId,
@@ -132,7 +178,7 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
required int ruleId,
required String order,
}) async {
return await _order(ruleId: ruleId, order: order);
return _order(ruleId: ruleId, order: order);
}
Future<int> orderProfileAddedRule(
@@ -140,7 +186,7 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
required int ruleId,
required String order,
}) async {
return await _order(
return _order(
ruleId: ruleId,
order: order,
profileId: profileId,
@@ -148,7 +194,43 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
);
}
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
Future<int> orderProfileCustomRule(
int profileId, {
required int ruleId,
required String order,
}) async {
return _order(
ruleId: ruleId,
order: order,
profileId: profileId,
scene: RuleScene.custom,
);
}
Future<int> renameCustomRuleTarget(
int profileId, {
required String oldName,
required String newName,
}) {
final stmt = rules.update()
..where((t) => t.ruleTarget.equals(oldName))
..where(
(t) => t.id.isInQuery(
selectOnly(profileRuleLinks)
..addColumns([profileRuleLinks.ruleId])
..where(
profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(RuleScene.custom),
),
),
);
return stmt.write(RulesCompanion(ruleTarget: Value(newName)));
}
JoinedSelectStatement<HasResultSet, dynamic> _getSelectStatement({
int? profileId,
RuleScene? scene,
}) {
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
@@ -160,10 +242,13 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
profileRuleLinks.scene.equalsValue(scene),
);
query.orderBy([
OrderingTerm.desc(profileRuleLinks.order),
OrderingTerm.desc(profileRuleLinks.id),
]);
query.orderBy([OrderingTerm.asc(profileRuleLinks.order)]);
return query;
}
Selectable<Rule> _query({int? profileId, RuleScene? scene}) {
final query = _getSelectStatement(profileId: profileId, scene: scene);
return query.map((row) {
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
@@ -184,7 +269,7 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
t.ruleId.equals(ruleId) &
t.scene.equalsValue(scene);
});
return await stmt.write(ProfileRuleLinksCompanion(order: Value(order)));
return stmt.write(ProfileRuleLinksCompanion(order: Value(order)));
}
Future<int> _put(Rule rule, {int? profileId, RuleScene? scene}) async {
@@ -193,11 +278,12 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
if (row == 0) {
return 0;
}
return await profileRuleLinks.insertOnConflictUpdate(
return profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: rule.id,
profileId: profileId,
scene: scene,
order: rule.order,
).toCompanion(),
);
});
@@ -207,77 +293,74 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
await rules.deleteWhere((t) => t.id.isIn(ruleIds));
}
Future<void> _putAll(
void _setWithBatch(
Batch b,
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
});
}
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
Future<void> _set(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
final keys = indexing.generateNKeys(rules.length);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.mapIndexed(
(index, item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(keys[index]),
),
);
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
}
}
extension RawRuleExt on RawRule {
Rule toRule([String? order]) {
return Rule(id: id, value: value, order: order);
return Rule(
id: id,
ruleAction: ruleAction,
content: content,
ruleTarget: ruleTarget,
ruleProvider: ruleProvider,
subRule: subRule,
noResolve: noResolve,
src: src,
order: order,
);
}
}
extension RulesCompanionExt on Rule {
RulesCompanion toCompanion() {
return RulesCompanion.insert(id: Value(id), value: value);
return RulesCompanion.insert(
id: Value(id),
ruleAction: ruleAction,
content: Value(content),
ruleTarget: Value(ruleTarget),
ruleProvider: Value(ruleProvider),
subRule: Value(subRule),
noResolve: Value(noResolve),
src: Value(src),
);
}
}
+6 -1
View File
@@ -19,7 +19,7 @@ class Scripts extends Table {
class ScriptsDao extends DatabaseAccessor<Database> with _$ScriptsDaoMixin {
ScriptsDao(super.attachedDatabase);
Selectable<Script> all() {
Selectable<Script> query() {
return scripts.select().map((item) => item.toScript());
}
@@ -29,6 +29,11 @@ class ScriptsDao extends DatabaseAccessor<Database> with _$ScriptsDaoMixin {
return stmt.map((it) => it.toScript());
}
Selectable<String> fileNames() {
final query = scripts.selectOnly()..addColumns([scripts.id]);
return query.map((row) => '${row.read(scripts.id)}.js');
}
Future<void> setAll(Iterable<Script> scripts) async {
await batch((b) async {
await setAllWithBatch(b, scripts);
+127 -24
View File
@@ -2,7 +2,7 @@
import 'dart:io';
import 'package:fl_clash/common/color.dart';
import 'package:fl_clash/common/context.dart';
import 'package:fl_clash/common/system.dart';
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -37,27 +37,36 @@ const desktopPlatforms = [
SupportPlatform.Windows,
];
enum GroupType {
Selector,
URLTest,
Fallback,
LoadBalance,
Relay;
enum GroupName { GLOBAL }
static GroupType parseProfileType(String type) {
return switch (type) {
'url-test' => URLTest,
'select' => Selector,
enum GroupType {
@JsonValue('select')
Selector('select'),
@JsonValue('url-test')
URLTest('url-test'),
@JsonValue('fallback')
Fallback('fallback'),
@JsonValue('load-balance')
LoadBalance('load-balance'),
@JsonValue('relay')
Relay('relay');
final String value;
const GroupType(this.value);
static GroupType parse(String type) {
return switch (type.toLowerCase()) {
'url-test' || 'urltest' => URLTest,
'select' || 'selector' => Selector,
'fallback' => Fallback,
'load-balance' => LoadBalance,
'load-balance' || 'loadbalance' => LoadBalance,
'relay' => Relay,
String() => throw UnimplementedError(),
};
}
}
enum GroupName { GLOBAL, Proxy, Auto, Fallback }
extension GroupTypeExtension on GroupType {
static List<String> get valueList =>
GroupType.values.map((e) => e.toString().split('.').last).toList();
@@ -71,8 +80,6 @@ extension GroupTypeExtension on GroupType {
if (index == -1) return null;
return GroupType.values[index];
}
String get value => GroupTypeExtension.valueList[index];
}
enum UsedProxy { GLOBAL, DIRECT, REJECT }
@@ -91,13 +98,14 @@ enum ViewMode { mobile, laptop, desktop }
enum LogLevel { debug, info, warning, error, silent }
extension LogLevelExt on LogLevel {
Color? get color {
Color? color(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
LogLevel.silent => Colors.grey.shade700,
LogLevel.debug => Colors.grey.shade400,
LogLevel.silent => colorScheme.outline,
LogLevel.debug => colorScheme.onSurfaceVariant,
LogLevel.info => null,
LogLevel.warning => Colors.orangeAccent.darken(),
LogLevel.error => Colors.redAccent,
LogLevel.warning => colorScheme.tertiary,
LogLevel.error => colorScheme.error,
};
}
}
@@ -282,6 +290,7 @@ enum FunctionTag {
autoScrollToEnd,
loadedProvider,
saveSharedFile,
removeProxy,
}
enum DashboardWidget {
@@ -311,7 +320,7 @@ enum DashboardWidget {
const DashboardWidget(this.widget, {this.platforms = SupportPlatform.values});
static DashboardWidget getDashboardWidget(GridItem gridItem) {
final dashboardWidgets = DashboardWidget.values;
const dashboardWidgets = DashboardWidget.values;
final index = dashboardWidgets.indexWhere(
(item) => item.widget == gridItem,
);
@@ -394,6 +403,47 @@ extension RuleActionExt on RuleAction {
RuleAction.IP_SUFFIX,
RuleAction.RULE_SET,
].contains(this);
String getDesc(BuildContext context) {
final appLocalizations = context.appLocalizations;
return switch (this) {
RuleAction.DOMAIN => appLocalizations.ruleActionDomainDesc,
RuleAction.DOMAIN_SUFFIX => appLocalizations.ruleActionDomainSuffixDesc,
RuleAction.DOMAIN_KEYWORD => appLocalizations.ruleActionDomainKeywordDesc,
RuleAction.DOMAIN_REGEX => appLocalizations.ruleActionDomainRegexDesc,
RuleAction.GEOSITE => appLocalizations.ruleActionGeositeDesc,
RuleAction.IP_CIDR => appLocalizations.ruleActionIpCidrDesc,
RuleAction.IP_CIDR6 => appLocalizations.ruleActionIpCidr6Desc,
RuleAction.IP_SUFFIX => appLocalizations.ruleActionIpSuffixDesc,
RuleAction.IP_ASN => appLocalizations.ruleActionIpAsnDesc,
RuleAction.GEOIP => appLocalizations.ruleActionGeoipDesc,
RuleAction.SRC_GEOIP => appLocalizations.ruleActionSrcGeoipDesc,
RuleAction.SRC_IP_ASN => appLocalizations.ruleActionSrcIpAsnDesc,
RuleAction.SRC_IP_CIDR => appLocalizations.ruleActionSrcIpCidrDesc,
RuleAction.SRC_IP_SUFFIX => appLocalizations.ruleActionSrcIpSuffixDesc,
RuleAction.DST_PORT => appLocalizations.ruleActionDstPortDesc,
RuleAction.SRC_PORT => appLocalizations.ruleActionSrcPortDesc,
RuleAction.IN_PORT => appLocalizations.ruleActionInPortDesc,
RuleAction.IN_TYPE => appLocalizations.ruleActionInTypeDesc,
RuleAction.IN_USER => appLocalizations.ruleActionInUserDesc,
RuleAction.IN_NAME => appLocalizations.ruleActionInNameDesc,
RuleAction.PROCESS_PATH => appLocalizations.ruleActionProcessPathDesc,
RuleAction.PROCESS_PATH_REGEX =>
appLocalizations.ruleActionProcessPathRegexDesc,
RuleAction.PROCESS_NAME => appLocalizations.ruleActionProcessNameDesc,
RuleAction.PROCESS_NAME_REGEX =>
appLocalizations.ruleActionProcessNameRegexDesc,
RuleAction.UID => appLocalizations.ruleActionUidDesc,
RuleAction.NETWORK => appLocalizations.ruleActionNetworkDesc,
RuleAction.DSCP => appLocalizations.ruleActionDscpDesc,
RuleAction.RULE_SET => appLocalizations.ruleActionRuleSetDesc,
RuleAction.AND => appLocalizations.ruleActionAndDesc,
RuleAction.OR => appLocalizations.ruleActionOrDesc,
RuleAction.NOT => appLocalizations.ruleActionNotDesc,
RuleAction.SUB_RULE => appLocalizations.ruleActionSubRuleDesc,
RuleAction.MATCH => appLocalizations.ruleActionMatchDesc,
};
}
}
enum OverrideRuleType { override, added }
@@ -402,10 +452,23 @@ enum OverwriteType {
// none,
standard,
script,
// custom,
custom,
}
enum RuleTarget { DIRECT, REJECT, MATCH }
enum RuleTarget {
DIRECT,
REJECT;
static Set<String> get baseTargets =>
RuleTarget.values.map((item) => item.name).toSet();
// static bool isBaseRuleTarget(String? target) {
// return RuleTarget.values.indexWhere(
// (item) => item.name == target?.toUpperCase(),
// ) !=
// -1;
// }
}
enum RestoreStrategy { compatible, override }
@@ -424,3 +487,43 @@ enum LoadingTag { profiles, backup_restore, access, proxies }
enum CoreStatus { connecting, connected, disconnected }
enum RuleScene { added, disabled, custom }
enum ItemPosition {
start,
middle,
end,
startAndEnd;
static ItemPosition get(int index, int length) {
ItemPosition position = ItemPosition.middle;
if (length == 1) {
position = ItemPosition.startAndEnd;
} else if (index == length - 1) {
position = ItemPosition.end;
} else if (index == 0) {
position = ItemPosition.start;
}
return position;
}
static ItemPosition calculateVisualPosition<T>(
int currentIndex,
List<T> items,
Set<T> deletedItems,
) {
final currentItem = items[currentIndex];
if (deletedItems.contains(currentItem)) {
return ItemPosition.middle;
}
final int visualLength = items.length - deletedItems.length;
if (visualLength <= 0) return ItemPosition.middle;
int deletedCountBeforeMe = 0;
for (int i = 0; i < currentIndex; i++) {
if (deletedItems.contains(items[i])) {
deletedCountBeforeMe++;
}
}
final int visualIndex = currentIndex - deletedCountBeforeMe;
return ItemPosition.get(visualIndex, visualLength);
}
}
+153 -48
View File
@@ -1,21 +1,27 @@
library;
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/state.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:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
final ruleItemHeight =
globalState.measure.bodyLargeHeight +
globalState.measure.bodyMediumHeight +
12;
class RuleItem extends StatelessWidget {
final bool isSelected;
final bool isEditing;
final Rule rule;
final bool hasMatch;
final void Function() onSelected;
final void Function(Rule rule) onEdit;
final bool Function(Rule rule)? checkInvalidHandler;
const RuleItem({
super.key,
@@ -23,19 +29,129 @@ class RuleItem extends StatelessWidget {
required this.rule,
required this.onSelected,
required this.onEdit,
this.checkInvalidHandler,
this.isEditing = false,
this.hasMatch = false,
});
VM2<bool, Color?> _checkInvalid(BuildContext context) {
if (rule.ruleAction != RuleAction.SUB_RULE) {
final ruleTarget = rule.ruleTarget ?? '';
if (ruleTarget.toUpperCase() == 'DIRECT') {
return VM2(
false,
Colors.green.harmonizeWith(context.colorScheme.primary),
);
} else if (ruleTarget.toUpperCase() == 'REJECT') {
return VM2(
false,
Colors.orange.harmonizeWith(context.colorScheme.primary),
);
} else if (hasMatch && ruleTarget.toUpperCase() == 'MATCH') {
return VM2(false, context.colorScheme.tertiary);
}
}
bool invalid = true;
if (checkInvalidHandler != null) {
invalid = checkInvalidHandler!(rule);
}
return VM2(
invalid,
invalid ? context.colorScheme.error : context.colorScheme.tertiary,
);
}
Widget _buildInfoWidget(BuildContext context) {
return CommonMinIconButtonTheme(
child: IconButton(
onPressed: () {
globalState.showMessage(
message: TextSpan(
text: rule.targetErrorTip(
context.appLocalizations.invalidSubRule(rule.subRule ?? ''),
context.appLocalizations.invalidPolicy(rule.ruleTarget ?? ''),
),
),
);
},
icon: Icon(Icons.info, size: 16.ap, color: context.colorScheme.error),
),
);
}
@override
Widget build(BuildContext context) {
return CommonSelectedListItem(
final vm2 = _checkInvalid(context);
final invalid = vm2.a;
return SelectedDecorationListItem(
minVerticalPadding: 0,
isSelected: isSelected,
isEditing: isEditing,
horizontalTitleGap: 0,
invalid: invalid,
onSelected: () {
onSelected();
},
title: Text(
rule.value,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
title: Center(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Builder(
builder: (context) {
final style = DefaultTextStyle.of(
context,
).style.toJetBrainsMono;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rule.ruleAction.name,
style: style.copyWith(
fontSize: context.textTheme.bodyLarge?.fontSize,
),
),
Flexible(
child: Builder(
builder: (context) {
return TooltipText(
text: Text(
rule.realContent ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: style.copyWith(
fontSize:
context.textTheme.bodyMedium?.fontSize,
color: style.color?.opacity60,
),
),
);
},
),
),
],
);
},
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (invalid) _buildInfoWidget(context),
if (rule.realTarget != null)
Text(
rule.realTarget!,
style: context.textTheme.bodyMedium?.toJetBrainsMono
.copyWith(color: vm2.b),
),
],
),
],
),
),
onPressed: () {
onEdit(rule);
@@ -58,30 +174,19 @@ class RuleStatusItem extends StatelessWidget {
@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),
),
return DecorationListItem(
title: TooltipText(
text: Text(
rule.rawValue,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
),
),
trailing: Switch(value: status, onChanged: onChange),
onPressed: () {
onChange(!status);
},
);
}
}
@@ -115,14 +220,15 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
...RuleTarget.values.map(
(item) => DropdownMenuEntry(value: item.name, label: item.name),
),
const DropdownMenuEntry(value: 'MATCH', label: 'MATCH'),
];
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;
final rule = widget.rule;
if (rule != null) {
_ruleAction = rule.ruleAction;
_contentController.text = rule.content ?? '';
_ruleTargetController.text = rule.ruleTarget ?? '';
_noResolve = rule.noResolve;
_src = rule.src;
return;
}
_ruleAction = RuleAction.addedRuleActions.first;
@@ -144,21 +250,20 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
if (res == false) {
return;
}
final parsedRule = ParsedRule(
final rule = Rule(
id: widget.rule?.id ?? -1,
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) {
final appLocalizations = context.appLocalizations;
return CommonDialog(
title: widget.rule != null
? appLocalizations.editRule
@@ -172,7 +277,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
child: DropdownMenuTheme(
data: DropdownMenuThemeData(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
labelStyle: context.textTheme.bodyLarge?.copyWith(
overflow: TextOverflow.ellipsis,
),
@@ -202,7 +307,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
},
child: Text(_ruleAction.value),
),
SizedBox(height: 24),
const SizedBox(height: 24),
TextFormField(
keyboardType: TextInputType.text,
onFieldSubmitted: (_) {
@@ -222,7 +327,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
return null;
},
),
SizedBox(height: 24),
const SizedBox(height: 24),
FormField<String>(
validator: (_) {
if (_ruleTargetController.text.isEmpty) {
@@ -246,7 +351,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
},
),
if (_ruleAction.hasParams) ...[
SizedBox(height: 20),
const SizedBox(height: 20),
Wrap(
spacing: 8,
children: [
@@ -254,7 +359,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
radius: 8,
isSelected: _src,
child: Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
@@ -273,7 +378,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
radius: 8,
isSelected: _noResolve,
child: Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
@@ -291,7 +396,7 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
],
),
],
SizedBox(height: 20),
const SizedBox(height: 20),
],
);
},
+275 -16
View File
@@ -38,23 +38,35 @@ class MessageLookup extends MessageLookupByLibrary {
static String m6(count) =>
"${Intl.plural(count, one: '1 hour ago', other: '${count} hours ago')}";
static String m7(count) =>
static String m7(target) => "${target} is an invalid policy";
static String m8(proxyName) => "${proxyName} is an invalid proxy";
static String m9(providerName) =>
"${providerName} is an invalid proxy provider";
static String m10(subRule) => "${subRule} is an invalid SUB_RULE";
static String m11(appName) =>
"1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check ${appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.";
static String m12(count) =>
"${Intl.plural(count, one: '1 minute ago', other: '${count} minutes ago')}";
static String m8(count) =>
static String m13(count) =>
"${Intl.plural(count, one: '1 month ago', other: '${count} months ago')}";
static String m9(label) => "No ${label} yet";
static String m14(label) => "No ${label} yet";
static String m10(label) => "${label} must be a number";
static String m15(label) => "${label} must be a number";
static String m11(label) => "${label} must be between 1024 and 49151";
static String m16(label) => "${label} must be between 1024 and 49151";
static String m12(count) => "${count} items have been selected";
static String m17(count) => "${count} items have been selected";
static String m13(label) => "${label} must be a url";
static String m18(label) => "${label} must be a url";
static String m14(count) =>
static String m19(count) =>
"${Intl.plural(count, one: '1 year ago', other: '${count} years ago')}";
final messages = _notInlinedMessages(_notInlinedMessages);
@@ -82,11 +94,19 @@ class MessageLookup extends MessageLookupByLibrary {
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
"add": MessageLookupByLibrary.simpleMessage("Add"),
"addProfile": MessageLookupByLibrary.simpleMessage("Add Profile"),
"addProxies": MessageLookupByLibrary.simpleMessage("Add proxies"),
"addProxyGroup": MessageLookupByLibrary.simpleMessage("Add proxy group"),
"addProxyProviders": MessageLookupByLibrary.simpleMessage(
"Add proxy providers",
),
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Attach on the original rules",
),
"addedRules": MessageLookupByLibrary.simpleMessage("Added rules"),
"additionalParameters": MessageLookupByLibrary.simpleMessage(
"Additional parameters",
),
"address": MessageLookupByLibrary.simpleMessage("Address"),
"addressHelp": MessageLookupByLibrary.simpleMessage(
"WebDAV server address",
@@ -176,6 +196,11 @@ class MessageLookup extends MessageLookupByLibrary {
"basicConfigDesc": MessageLookupByLibrary.simpleMessage(
"Modify the basic configuration globally",
),
"basicInfo": MessageLookupByLibrary.simpleMessage("Basic info"),
"basicStrategy": MessageLookupByLibrary.simpleMessage("Basic strategy"),
"batteryOptimizationDesc": MessageLookupByLibrary.simpleMessage(
"To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
),
"bind": MessageLookupByLibrary.simpleMessage("Bind"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
"bypassDomain": MessageLookupByLibrary.simpleMessage("Bypass domain"),
@@ -208,13 +233,25 @@ class MessageLookup extends MessageLookupByLibrary {
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
"Opening it will lose part of its application ability and gain the support of full amount of Clash.",
),
"configDataDetected": MessageLookupByLibrary.simpleMessage(
"Data detected in configuration",
),
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to clear all data?",
),
"confirmDeleteProxyGroup": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to delete the current proxy group?",
),
"confirmExitWindow": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to exit the current window?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to force crash the core?",
),
"confirmOverwriteTip": MessageLookupByLibrary.simpleMessage(
"Existing data will be overwritten after confirmation",
),
"connected": MessageLookupByLibrary.simpleMessage("Connected"),
"connecting": MessageLookupByLibrary.simpleMessage("Connecting..."),
"connection": MessageLookupByLibrary.simpleMessage("Connection"),
@@ -225,6 +262,9 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
"content": MessageLookupByLibrary.simpleMessage("Content"),
"contentNotEmpty": MessageLookupByLibrary.simpleMessage(
"Content cannot be empty",
),
"contentScheme": MessageLookupByLibrary.simpleMessage("Content"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"Control global added rules",
@@ -248,10 +288,15 @@ class MessageLookup extends MessageLookupByLibrary {
"When enabled, automatically uploads crash logs without sensitive information when the app crashes",
),
"create": MessageLookupByLibrary.simpleMessage("Create"),
"createProfile": MessageLookupByLibrary.simpleMessage("Create Profile"),
"creationTime": MessageLookupByLibrary.simpleMessage("Creation time"),
"custom": MessageLookupByLibrary.simpleMessage("Custom"),
"cut": MessageLookupByLibrary.simpleMessage("Cut"),
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
"dashboard": MessageLookupByLibrary.simpleMessage("Dashboard"),
"dataChangedSave": MessageLookupByLibrary.simpleMessage(
"Data changes detected, do you want to save?",
),
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
"This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.",
),
@@ -293,6 +338,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Developer mode is enabled.",
),
"direct": MessageLookupByLibrary.simpleMessage("Direct"),
"disableUDP": MessageLookupByLibrary.simpleMessage("Disable UDP"),
"disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
"This software is only used for non-commercial purposes such as learning exchanges and scientific research. It is strictly prohibited to use this software for commercial purposes. Any commercial activity, if any, has nothing to do with this software.",
@@ -318,6 +364,8 @@ class MessageLookup extends MessageLookupByLibrary {
"editGlobalRules": MessageLookupByLibrary.simpleMessage(
"Edit global rules",
),
"editProxy": MessageLookupByLibrary.simpleMessage("Edit proxy"),
"editProxyGroup": MessageLookupByLibrary.simpleMessage("Edit proxy group"),
"editRule": MessageLookupByLibrary.simpleMessage("Edit rule"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("English"),
@@ -327,9 +375,18 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"When the app is in the background, the app is hidden from the recent task",
),
"excludeProxyFilter": MessageLookupByLibrary.simpleMessage(
"Exclude proxy filter",
),
"excludeSsids": MessageLookupByLibrary.simpleMessage("Exclude SSIDs"),
"excludeSsidsDesc": MessageLookupByLibrary.simpleMessage(
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("Exclude type"),
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("Expected status"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Expiration time"),
"exportFile": MessageLookupByLibrary.simpleMessage("Export file"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
@@ -396,6 +453,7 @@ class MessageLookup extends MessageLookupByLibrary {
"hasCacheChange": MessageLookupByLibrary.simpleMessage(
"Do you want to cache the changes?",
),
"hideFromList": MessageLookupByLibrary.simpleMessage("Hide from list"),
"host": MessageLookupByLibrary.simpleMessage("Host"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("Add Hosts"),
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("Hotkey conflict"),
@@ -411,16 +469,39 @@ class MessageLookup extends MessageLookupByLibrary {
"iconConfiguration": MessageLookupByLibrary.simpleMessage(
"Icon configuration",
),
"iconRecords": MessageLookupByLibrary.simpleMessage("Icon records"),
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
"iconUrl": MessageLookupByLibrary.simpleMessage("Icon URL"),
"ignoreBatteryOptimization": MessageLookupByLibrary.simpleMessage(
"Ignore Battery Optimization",
),
"import": MessageLookupByLibrary.simpleMessage("Import"),
"importFile": MessageLookupByLibrary.simpleMessage("Import from file"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Import from URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Import from URL"),
"includeAllProxies": MessageLookupByLibrary.simpleMessage(
"Include all proxies",
),
"includeAllProxiesTip": MessageLookupByLibrary.simpleMessage(
"Import all proxies not containing proxy groups, additional proxy groups can be added below",
),
"includeAllProxyProviders": MessageLookupByLibrary.simpleMessage(
"Include all proxy providers",
),
"includeAllProxyProvidersTip": MessageLookupByLibrary.simpleMessage(
"When enabled, it will override the imported proxy providers",
),
"infiniteTime": MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(
"Please enter the correct hotkey",
),
"inputProxyGroupName": MessageLookupByLibrary.simpleMessage(
"Input proxy group name",
),
"inputRuleContent": MessageLookupByLibrary.simpleMessage(
"Input rule content",
),
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
"Intelligent selection",
),
@@ -430,6 +511,10 @@ class MessageLookup extends MessageLookupByLibrary {
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Invalid backup file",
),
"invalidPolicy": m7,
"invalidProxy": m8,
"invalidProxyProvider": m9,
"invalidSubRule": m10,
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic",
@@ -455,6 +540,16 @@ class MessageLookup extends MessageLookupByLibrary {
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local",
),
"locationPermission": MessageLookupByLibrary.simpleMessage(
"Location Permission",
),
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
"locationPermissionGuide": m11,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
"log": MessageLookupByLibrary.simpleMessage("Log"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -469,6 +564,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Used for UWP loopback unlocking",
),
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
"matchSourceIp": MessageLookupByLibrary.simpleMessage("Match source IP"),
"maxFailedTimes": MessageLookupByLibrary.simpleMessage("Max failed times"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"),
"messageTest": MessageLookupByLibrary.simpleMessage("Message test"),
"messageTestTip": MessageLookupByLibrary.simpleMessage(
@@ -480,12 +577,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify the default system exit event",
),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"minutesAgo": m7,
"minutesAgo": m12,
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
"months": MessageLookupByLibrary.simpleMessage("Months"),
"monthsAgo": m8,
"monthsAgo": m13,
"more": MessageLookupByLibrary.simpleMessage("More"),
"name": MessageLookupByLibrary.simpleMessage("Name"),
"nameSort": MessageLookupByLibrary.simpleMessage("Sort by name"),
@@ -529,7 +626,11 @@ class MessageLookup extends MessageLookupByLibrary {
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile",
),
"noRecords": MessageLookupByLibrary.simpleMessage("No records"),
"noResolve": MessageLookupByLibrary.simpleMessage("No resolve IP"),
"noResolveHostname": MessageLookupByLibrary.simpleMessage(
"No resolve hostname",
),
"none": MessageLookupByLibrary.simpleMessage("none"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"The current proxy group cannot be selected.",
@@ -537,8 +638,12 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile",
),
"nullTip": m9,
"numberTip": m10,
"nullTip": m14,
"numberTip": m15,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
),
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -550,6 +655,7 @@ class MessageLookup extends MessageLookupByLibrary {
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
"When turned on, only statistics proxy traffic",
),
"optional": MessageLookupByLibrary.simpleMessage("Optional"),
"options": MessageLookupByLibrary.simpleMessage("Options"),
"other": MessageLookupByLibrary.simpleMessage("Other"),
"otherContributors": MessageLookupByLibrary.simpleMessage(
@@ -598,10 +704,11 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Please enter a different port",
),
"portTip": m11,
"portTip": m16,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Prioritize the use of DOH\'s http/3",
),
"prerequisites": MessageLookupByLibrary.simpleMessage("Prerequisites"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage(
"Please press the keyboard.",
),
@@ -636,9 +743,26 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("Project"),
"providers": MessageLookupByLibrary.simpleMessage("Providers"),
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
"proxiesEmpty": MessageLookupByLibrary.simpleMessage("Proxies is empty"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("Proxies setting"),
"proxyChains": MessageLookupByLibrary.simpleMessage("Proxy chains"),
"proxyDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Detected selected proxies are abnormal",
),
"proxyFilter": MessageLookupByLibrary.simpleMessage("Proxy filter"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("Proxy group"),
"proxyGroupDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Detected current proxy group is abnormal",
),
"proxyGroupEmpty": MessageLookupByLibrary.simpleMessage(
"Proxy group is empty",
),
"proxyGroupNameDuplicate": MessageLookupByLibrary.simpleMessage(
"Proxy group name is duplicate",
),
"proxyGroupNameEmpty": MessageLookupByLibrary.simpleMessage(
"Proxy group name cannot be empty",
),
"proxyNameserver": MessageLookupByLibrary.simpleMessage("Proxy nameserver"),
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage(
"Domain for resolving proxy nodes",
@@ -647,13 +771,24 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the Clash listening port",
),
"proxyProviderDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Detected selected proxy providers are abnormal",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
"proxyProvidersEmpty": MessageLookupByLibrary.simpleMessage(
"Proxy providers is empty",
),
"proxyProvidersNotEmpty": MessageLookupByLibrary.simpleMessage(
"Proxy providers cannot be empty",
),
"proxyType": MessageLookupByLibrary.simpleMessage("Proxy type"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Prune cache"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile",
),
"quickFill": MessageLookupByLibrary.simpleMessage("Quick fill"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
@@ -723,8 +858,107 @@ class MessageLookup extends MessageLookupByLibrary {
"routeMode_config": MessageLookupByLibrary.simpleMessage("Use config"),
"ru": MessageLookupByLibrary.simpleMessage("Russian"),
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
"ruleActionAndDesc": MessageLookupByLibrary.simpleMessage(
"Logical rule AND",
),
"ruleActionDomainDesc": MessageLookupByLibrary.simpleMessage(
"Match full domain",
),
"ruleActionDomainKeywordDesc": MessageLookupByLibrary.simpleMessage(
"Match domain keyword",
),
"ruleActionDomainRegexDesc": MessageLookupByLibrary.simpleMessage(
"Wildcard match, only supports * and ? wildcards",
),
"ruleActionDomainSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Match domain suffix",
),
"ruleActionDscpDesc": MessageLookupByLibrary.simpleMessage(
"Match DSCP mark (tproxy udp inbound only)",
),
"ruleActionDstPortDesc": MessageLookupByLibrary.simpleMessage(
"Match request target port range",
),
"ruleActionGeoipDesc": MessageLookupByLibrary.simpleMessage(
"Match IP\'s country code",
),
"ruleActionGeositeDesc": MessageLookupByLibrary.simpleMessage(
"Match domains within Geosite",
),
"ruleActionInNameDesc": MessageLookupByLibrary.simpleMessage(
"Match inbound name",
),
"ruleActionInPortDesc": MessageLookupByLibrary.simpleMessage(
"Match inbound port",
),
"ruleActionInTypeDesc": MessageLookupByLibrary.simpleMessage(
"Match inbound type",
),
"ruleActionInUserDesc": MessageLookupByLibrary.simpleMessage(
"Match inbound username, supports multiple usernames separated by /",
),
"ruleActionIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"Match IP\'s ASN",
),
"ruleActionIpCidr6Desc": MessageLookupByLibrary.simpleMessage(
"Match IP address range, IP-CIDR6 is just an alias",
),
"ruleActionIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"Match IP address range",
),
"ruleActionIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Match IP suffix range",
),
"ruleActionMatchDesc": MessageLookupByLibrary.simpleMessage(
"Match all requests, no conditions needed",
),
"ruleActionNetworkDesc": MessageLookupByLibrary.simpleMessage(
"Match TCP or UDP",
),
"ruleActionNotDesc": MessageLookupByLibrary.simpleMessage(
"Logical rule NOT",
),
"ruleActionOrDesc": MessageLookupByLibrary.simpleMessage("Logical rule OR"),
"ruleActionProcessNameDesc": MessageLookupByLibrary.simpleMessage(
"Match using process name, matches package name on Android",
),
"ruleActionProcessNameRegexDesc": MessageLookupByLibrary.simpleMessage(
"Match using process name regex, matches package name on Android",
),
"ruleActionProcessPathDesc": MessageLookupByLibrary.simpleMessage(
"Match using full process path",
),
"ruleActionProcessPathRegexDesc": MessageLookupByLibrary.simpleMessage(
"Match using process path regex",
),
"ruleActionRuleSetDesc": MessageLookupByLibrary.simpleMessage(
"Reference rule set, requires rule-providers configuration",
),
"ruleActionSrcGeoipDesc": MessageLookupByLibrary.simpleMessage(
"Match source IP\'s country code",
),
"ruleActionSrcIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"Match source IP\'s ASN",
),
"ruleActionSrcIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"Match source IP address range",
),
"ruleActionSrcIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Match source IP suffix range",
),
"ruleActionSrcPortDesc": MessageLookupByLibrary.simpleMessage(
"Match request source port range",
),
"ruleActionSubRuleDesc": MessageLookupByLibrary.simpleMessage(
"Match to sub-rule, pay attention to the use of parentheses",
),
"ruleActionUidDesc": MessageLookupByLibrary.simpleMessage(
"Match Linux USER ID",
),
"ruleEmpty": MessageLookupByLibrary.simpleMessage("Rule is empty"),
"ruleName": MessageLookupByLibrary.simpleMessage("Rule name"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
"ruleSet": MessageLookupByLibrary.simpleMessage("Rule set"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("Rule target"),
"save": MessageLookupByLibrary.simpleMessage("Save"),
"saveChanges": MessageLookupByLibrary.simpleMessage(
@@ -740,8 +974,21 @@ class MessageLookup extends MessageLookupByLibrary {
"search": MessageLookupByLibrary.simpleMessage("Search"),
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selectProxies": MessageLookupByLibrary.simpleMessage("Select proxies"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
"Select proxy providers",
),
"selectRuleSet": MessageLookupByLibrary.simpleMessage(
"Please select rule set",
),
"selectSplitStrategy": MessageLookupByLibrary.simpleMessage(
"Please select split strategy",
),
"selectSubRule": MessageLookupByLibrary.simpleMessage(
"Please select sub rule",
),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
"selectedCountTitle": m12,
"selectedCountTitle": m17,
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
@@ -757,6 +1004,11 @@ class MessageLookup extends MessageLookupByLibrary {
"specialProxy": MessageLookupByLibrary.simpleMessage("Special proxy"),
"specialRules": MessageLookupByLibrary.simpleMessage("special rules"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("Speed statistics"),
"splitStrategy": MessageLookupByLibrary.simpleMessage("Split strategy"),
"splitStrategyNotEmpty": MessageLookupByLibrary.simpleMessage(
"Split strategy cannot be empty",
),
"ssidsEmpty": MessageLookupByLibrary.simpleMessage("SSIDs is empty"),
"stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -772,6 +1024,10 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Style"),
"subRule": MessageLookupByLibrary.simpleMessage("Sub rule"),
"subRuleEmpty": MessageLookupByLibrary.simpleMessage("Sub rule is empty"),
"subRuleNotEmpty": MessageLookupByLibrary.simpleMessage(
"Sub rule cannot be empty",
),
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"system": MessageLookupByLibrary.simpleMessage("System"),
@@ -790,7 +1046,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
"Enabling it will allow TCP concurrency",
),
"testInterval": MessageLookupByLibrary.simpleMessage("Test interval"),
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
"testWhenUsed": MessageLookupByLibrary.simpleMessage("Test when used"),
"textScale": MessageLookupByLibrary.simpleMessage("Text Scaling"),
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
@@ -801,6 +1059,7 @@ class MessageLookup extends MessageLookupByLibrary {
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
"tight": MessageLookupByLibrary.simpleMessage("Tight"),
"time": MessageLookupByLibrary.simpleMessage("Time"),
"timeout": MessageLookupByLibrary.simpleMessage("Timeout"),
"tip": MessageLookupByLibrary.simpleMessage("tip"),
"toggle": MessageLookupByLibrary.simpleMessage("Toggle"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("TonalSpot"),
@@ -833,7 +1092,7 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Obtain profile through URL",
),
"urlTip": m13,
"urlTip": m18,
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
"value": MessageLookupByLibrary.simpleMessage("Value"),
@@ -859,7 +1118,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"whitelistMode": MessageLookupByLibrary.simpleMessage("Whitelist mode"),
"years": MessageLookupByLibrary.simpleMessage("Years"),
"yearsAgo": m14,
"yearsAgo": m19,
"zh_CN": MessageLookupByLibrary.simpleMessage("Simplified Chinese"),
};
}
+244 -16
View File
@@ -34,21 +34,32 @@ class MessageLookup extends MessageLookupByLibrary {
static String m6(count) => "${count}時間前";
static String m7(count) => "${count}分前";
static String m7(target) => "${target} は無効なポリシーです";
static String m8(count) => "${count}ヶ月前";
static String m8(proxyName) => "${proxyName} は無効なプロキシです";
static String m9(label) => "まだ${label}はありません";
static String m9(providerName) => "${providerName} は無効なプロキシプロバイダーです";
static String m10(label) => "${label}は数字でなければなりません";
static String m10(subRule) => "${subRule} は無効なSUB_RULEです";
static String m11(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m11(appName) =>
"1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check ${appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.";
static String m12(count) => "${count} 項目が選択されています";
static String m12(count) => "${count}分前";
static String m13(label) => "${label}はURLである必要があります";
static String m13(count) => "${count}ヶ月前";
static String m14(count) => "${count}年前";
static String m14(label) => "まだ${label}はありません";
static String m15(label) => "${label}は数字でなければなりません";
static String m16(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m17(count) => "${count} 項目が選択されています";
static String m18(label) => "${label}はURLである必要があります";
static String m19(count) => "${count}年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -73,9 +84,13 @@ class MessageLookup extends MessageLookupByLibrary {
"action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"),
"add": MessageLookupByLibrary.simpleMessage("追加"),
"addProfile": MessageLookupByLibrary.simpleMessage("プロファイルを追加"),
"addProxies": MessageLookupByLibrary.simpleMessage("プロキシを追加"),
"addProxyGroup": MessageLookupByLibrary.simpleMessage("プロキシグループを追加"),
"addProxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダーを追加"),
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
"addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"),
"additionalParameters": MessageLookupByLibrary.simpleMessage("追加パラメータ"),
"address": MessageLookupByLibrary.simpleMessage("アドレス"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAVサーバーアドレス"),
"addressTip": MessageLookupByLibrary.simpleMessage("有効なWebDAVアドレスを入力"),
@@ -125,6 +140,11 @@ class MessageLookup extends MessageLookupByLibrary {
"backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"),
"basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"),
"basicConfigDesc": MessageLookupByLibrary.simpleMessage("基本設定をグローバルに変更"),
"basicInfo": MessageLookupByLibrary.simpleMessage("基本情報"),
"basicStrategy": MessageLookupByLibrary.simpleMessage("基本戦略"),
"batteryOptimizationDesc": MessageLookupByLibrary.simpleMessage(
"To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
),
"bind": MessageLookupByLibrary.simpleMessage("バインド"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("ブラックリストモード"),
"bypassDomain": MessageLookupByLibrary.simpleMessage("バイパスドメイン"),
@@ -151,13 +171,25 @@ class MessageLookup extends MessageLookupByLibrary {
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
"有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
),
"configDataDetected": MessageLookupByLibrary.simpleMessage(
"設定内にデータが検出されました",
),
"confirm": MessageLookupByLibrary.simpleMessage("確認"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"すべてのデータをクリアしてもよろしいですか?",
),
"confirmDeleteProxyGroup": MessageLookupByLibrary.simpleMessage(
"現在のプロキシグループを削除してもよろしいですか?",
),
"confirmExitWindow": MessageLookupByLibrary.simpleMessage(
"現在のウィンドウを閉じてもよろしいですか?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"コアを強制的にクラッシュさせてもよろしいですか?",
),
"confirmOverwriteTip": MessageLookupByLibrary.simpleMessage(
"確認後、既存のデータは上書きされます",
),
"connected": MessageLookupByLibrary.simpleMessage("接続済み"),
"connecting": MessageLookupByLibrary.simpleMessage("接続中..."),
"connection": MessageLookupByLibrary.simpleMessage("接続"),
@@ -166,6 +198,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentNotEmpty": MessageLookupByLibrary.simpleMessage("内容は空にできません"),
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"グローバル追加ルールを制御",
@@ -187,10 +220,15 @@ class MessageLookup extends MessageLookupByLibrary {
"有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします",
),
"create": MessageLookupByLibrary.simpleMessage("作成"),
"createProfile": MessageLookupByLibrary.simpleMessage("Create Profile"),
"creationTime": MessageLookupByLibrary.simpleMessage("作成時間"),
"custom": MessageLookupByLibrary.simpleMessage("カスタム"),
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
"dashboard": MessageLookupByLibrary.simpleMessage("ダッシュボード"),
"dataChangedSave": MessageLookupByLibrary.simpleMessage(
"データの変更が検出されました。保存しますか?",
),
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
"本アプリはFirebase Crashlyticsを使用してクラッシュ情報を収集し、アプリの安定性を向上させます。\n収集されるデータにはデバイス情報とクラッシュ詳細が含まれますが、個人の機密データは含まれません。\n設定でこの機能を無効にすることができます。",
),
@@ -222,6 +260,7 @@ class MessageLookup extends MessageLookupByLibrary {
"デベロッパーモードが有効になりました。",
),
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
"disableUDP": MessageLookupByLibrary.simpleMessage("UDPを無効化"),
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
"本ソフトウェアは学習交流や科学研究などの非営利目的でのみ使用されます。商用利用は厳禁です。いかなる商用活動も本ソフトウェアとは無関係です。",
@@ -237,6 +276,8 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("ダウンロード"),
"edit": MessageLookupByLibrary.simpleMessage("編集"),
"editGlobalRules": MessageLookupByLibrary.simpleMessage("グローバルルールを編集"),
"editProxy": MessageLookupByLibrary.simpleMessage("プロキシを編集"),
"editProxyGroup": MessageLookupByLibrary.simpleMessage("プロキシグループを編集"),
"editRule": MessageLookupByLibrary.simpleMessage("ルールを編集"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英語"),
@@ -246,9 +287,16 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"アプリがバックグラウンド時に最近のタスクから非表示",
),
"excludeProxyFilter": MessageLookupByLibrary.simpleMessage("除外プロキシフィルター"),
"excludeSsids": MessageLookupByLibrary.simpleMessage("Exclude SSIDs"),
"excludeSsidsDesc": MessageLookupByLibrary.simpleMessage(
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("除外タイプ"),
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("終了"),
"expand": MessageLookupByLibrary.simpleMessage("標準"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("期待されるステータス"),
"expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"),
"exportFile": MessageLookupByLibrary.simpleMessage("ファイルをエクスポート"),
"exportLogs": MessageLookupByLibrary.simpleMessage("ログをエクスポート"),
@@ -297,6 +345,7 @@ class MessageLookup extends MessageLookupByLibrary {
"goDownload": MessageLookupByLibrary.simpleMessage("ダウンロードへ"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage("スクリプト設定に移動"),
"hasCacheChange": MessageLookupByLibrary.simpleMessage("変更をキャッシュしますか?"),
"hideFromList": MessageLookupByLibrary.simpleMessage("リストから隠す"),
"host": MessageLookupByLibrary.simpleMessage("ホスト"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("ホストを追加"),
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("ホットキー競合"),
@@ -308,19 +357,40 @@ class MessageLookup extends MessageLookupByLibrary {
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
"iconRecords": MessageLookupByLibrary.simpleMessage("アイコン履歴"),
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
"iconUrl": MessageLookupByLibrary.simpleMessage("アイコンURL"),
"ignoreBatteryOptimization": MessageLookupByLibrary.simpleMessage(
"Ignore Battery Optimization",
),
"import": MessageLookupByLibrary.simpleMessage("インポート"),
"importFile": MessageLookupByLibrary.simpleMessage("ファイルからインポート"),
"importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"importUrl": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"includeAllProxies": MessageLookupByLibrary.simpleMessage("すべてのプロキシを含める"),
"includeAllProxiesTip": MessageLookupByLibrary.simpleMessage(
"プロキシグループに含まれないすべてのプロキシをインポートします。下でさらにプロキシグループを追加できます",
),
"includeAllProxyProviders": MessageLookupByLibrary.simpleMessage(
"すべてのプロキシプロバイダーを含める",
),
"includeAllProxyProvidersTip": MessageLookupByLibrary.simpleMessage(
"有効にすると、インポートされたプロキシプロバイダーを上書きします",
),
"infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"),
"init": MessageLookupByLibrary.simpleMessage("初期化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
"inputProxyGroupName": MessageLookupByLibrary.simpleMessage("プロキシグループ名を入力"),
"inputRuleContent": MessageLookupByLibrary.simpleMessage("ルール内容を入力"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
"interval": MessageLookupByLibrary.simpleMessage("インターバル"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("無効なバックアップファイル"),
"invalidPolicy": m7,
"invalidProxy": m8,
"invalidProxyProvider": m9,
"invalidSubRule": m10,
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
@@ -340,6 +410,16 @@ class MessageLookup extends MessageLookupByLibrary {
"loading": MessageLookupByLibrary.simpleMessage("読み込み中..."),
"local": MessageLookupByLibrary.simpleMessage("ローカル"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
"locationPermission": MessageLookupByLibrary.simpleMessage(
"Location Permission",
),
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
"locationPermissionGuide": m11,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
"log": MessageLookupByLibrary.simpleMessage("ログ"),
"logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"),
"logcat": MessageLookupByLibrary.simpleMessage("ログキャット"),
@@ -350,6 +430,8 @@ class MessageLookup extends MessageLookupByLibrary {
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
"loose": MessageLookupByLibrary.simpleMessage("疎"),
"matchSourceIp": MessageLookupByLibrary.simpleMessage("送信元IPをマッチング"),
"maxFailedTimes": MessageLookupByLibrary.simpleMessage("最大失敗回数"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
"messageTest": MessageLookupByLibrary.simpleMessage("メッセージテスト"),
"messageTestTip": MessageLookupByLibrary.simpleMessage("これはメッセージです。"),
@@ -359,12 +441,12 @@ class MessageLookup extends MessageLookupByLibrary {
"システムの終了イベントを変更",
),
"minutes": MessageLookupByLibrary.simpleMessage("分"),
"minutesAgo": m7,
"minutesAgo": m12,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
"mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
"months": MessageLookupByLibrary.simpleMessage("月"),
"monthsAgo": m8,
"monthsAgo": m13,
"more": MessageLookupByLibrary.simpleMessage("詳細"),
"name": MessageLookupByLibrary.simpleMessage("名前"),
"nameSort": MessageLookupByLibrary.simpleMessage("名前順"),
@@ -398,7 +480,9 @@ class MessageLookup extends MessageLookupByLibrary {
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルを作成するか、有効なプロファイルを追加してください",
),
"noRecords": MessageLookupByLibrary.simpleMessage("履歴なし"),
"noResolve": MessageLookupByLibrary.simpleMessage("IPを解決しない"),
"noResolveHostname": MessageLookupByLibrary.simpleMessage("ホスト名を解決しない"),
"none": MessageLookupByLibrary.simpleMessage("なし"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"現在のプロキシグループは選択できません",
@@ -406,8 +490,12 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルがありません。追加してください",
),
"nullTip": m9,
"numberTip": m10,
"nullTip": m14,
"numberTip": m15,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
),
"oneColumn": MessageLookupByLibrary.simpleMessage("1列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"),
@@ -415,6 +503,7 @@ class MessageLookup extends MessageLookupByLibrary {
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
"有効化するとプロキシトラフィックのみ統計",
),
"optional": MessageLookupByLibrary.simpleMessage("オプション"),
"options": MessageLookupByLibrary.simpleMessage("オプション"),
"other": MessageLookupByLibrary.simpleMessage("その他"),
"otherContributors": MessageLookupByLibrary.simpleMessage("その他の貢献者"),
@@ -455,8 +544,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("ポート"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
"portTip": m11,
"portTip": m16,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
"prerequisites": MessageLookupByLibrary.simpleMessage("Prerequisites"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
"process": MessageLookupByLibrary.simpleMessage("プロセス"),
@@ -485,20 +575,46 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("プロジェクト"),
"providers": MessageLookupByLibrary.simpleMessage("プロバイダー"),
"proxies": MessageLookupByLibrary.simpleMessage("プロキシ"),
"proxiesEmpty": MessageLookupByLibrary.simpleMessage("プロキシが空です"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("プロキシ設定"),
"proxyChains": MessageLookupByLibrary.simpleMessage("プロキシチェーン"),
"proxyDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"選択されたプロキシに異常があることを検出しました",
),
"proxyFilter": MessageLookupByLibrary.simpleMessage("プロキシフィルター"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("プロキシグループ"),
"proxyGroupDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"現在のプロキシグループが異常であることを検出しました",
),
"proxyGroupEmpty": MessageLookupByLibrary.simpleMessage("プロキシグループが空です"),
"proxyGroupNameDuplicate": MessageLookupByLibrary.simpleMessage(
"プロキシグループ名が重複しています",
),
"proxyGroupNameEmpty": MessageLookupByLibrary.simpleMessage(
"プロキシグループ名は空にできません",
),
"proxyNameserver": MessageLookupByLibrary.simpleMessage("プロキシネームサーバー"),
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage(
"プロキシノード解決用ドメイン",
),
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
"proxyProviderDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"選択されたプロキシプロバイダーに異常があることを検出しました",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
"proxyProvidersEmpty": MessageLookupByLibrary.simpleMessage(
"プロキシプロバイダーが空です",
),
"proxyProvidersNotEmpty": MessageLookupByLibrary.simpleMessage(
"プロキシプロバイダーは空にできません",
),
"proxyType": MessageLookupByLibrary.simpleMessage("プロキシタイプ"),
"pruneCache": MessageLookupByLibrary.simpleMessage("キャッシュの削除"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
"quickFill": MessageLookupByLibrary.simpleMessage("クイック入力"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
@@ -550,8 +666,101 @@ class MessageLookup extends MessageLookupByLibrary {
"routeMode_config": MessageLookupByLibrary.simpleMessage("設定を使用"),
"ru": MessageLookupByLibrary.simpleMessage("ロシア語"),
"rule": MessageLookupByLibrary.simpleMessage("ルール"),
"ruleActionAndDesc": MessageLookupByLibrary.simpleMessage("論理ルール AND"),
"ruleActionDomainDesc": MessageLookupByLibrary.simpleMessage(
"完全なドメインをマッチング",
),
"ruleActionDomainKeywordDesc": MessageLookupByLibrary.simpleMessage(
"ドメインキーワードをマッチング",
),
"ruleActionDomainRegexDesc": MessageLookupByLibrary.simpleMessage(
"ワイルドカードマッチング(*と?のみサポート)",
),
"ruleActionDomainSuffixDesc": MessageLookupByLibrary.simpleMessage(
"ドメイン接尾辞をマッチング",
),
"ruleActionDscpDesc": MessageLookupByLibrary.simpleMessage(
"DSCPマークをマッチング (tproxy udp inboundのみ)",
),
"ruleActionDstPortDesc": MessageLookupByLibrary.simpleMessage(
"宛先ポート範囲をマッチング",
),
"ruleActionGeoipDesc": MessageLookupByLibrary.simpleMessage(
"IPの国コードをマッチング",
),
"ruleActionGeositeDesc": MessageLookupByLibrary.simpleMessage(
"Match domains within Geosite",
),
"ruleActionInNameDesc": MessageLookupByLibrary.simpleMessage(
"インバウンド名をマッチング",
),
"ruleActionInPortDesc": MessageLookupByLibrary.simpleMessage(
"インバウンドポートをマッチング",
),
"ruleActionInTypeDesc": MessageLookupByLibrary.simpleMessage(
"インバウンドタイプをマッチング",
),
"ruleActionInUserDesc": MessageLookupByLibrary.simpleMessage(
"インバウンドユーザー名をマッチング(/で複数指定可)",
),
"ruleActionIpAsnDesc": MessageLookupByLibrary.simpleMessage("IPのASNをマッチング"),
"ruleActionIpCidr6Desc": MessageLookupByLibrary.simpleMessage(
"IPアドレス範囲をマッチング(IP-CIDR6はエイリアスです)",
),
"ruleActionIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"IPアドレス範囲をマッチング",
),
"ruleActionIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"IP接尾辞範囲をマッチング",
),
"ruleActionMatchDesc": MessageLookupByLibrary.simpleMessage(
"すべてのリクエストにマッチ(条件なし)",
),
"ruleActionNetworkDesc": MessageLookupByLibrary.simpleMessage(
"TCPまたはUDPをマッチング",
),
"ruleActionNotDesc": MessageLookupByLibrary.simpleMessage("論理ルール NOT"),
"ruleActionOrDesc": MessageLookupByLibrary.simpleMessage("論理ルール OR"),
"ruleActionProcessNameDesc": MessageLookupByLibrary.simpleMessage(
"プロセス名でマッチング(Androidではパッケージ名)",
),
"ruleActionProcessNameRegexDesc": MessageLookupByLibrary.simpleMessage(
"プロセス名正規表現でマッチング(Androidではパッケージ名)",
),
"ruleActionProcessPathDesc": MessageLookupByLibrary.simpleMessage(
"フルプロセスパスでマッチング",
),
"ruleActionProcessPathRegexDesc": MessageLookupByLibrary.simpleMessage(
"プロセスパス正規表現でマッチング",
),
"ruleActionRuleSetDesc": MessageLookupByLibrary.simpleMessage(
"ルールセットを参照。rule-providersの設定が必要",
),
"ruleActionSrcGeoipDesc": MessageLookupByLibrary.simpleMessage(
"送信元IPの国コードをマッチング",
),
"ruleActionSrcIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"送信元IPのASNをマッチング",
),
"ruleActionSrcIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"送信元IPアドレス範囲をマッチング",
),
"ruleActionSrcIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"送信元IP接尾辞範囲をマッチング",
),
"ruleActionSrcPortDesc": MessageLookupByLibrary.simpleMessage(
"送信元ポート範囲をマッチング",
),
"ruleActionSubRuleDesc": MessageLookupByLibrary.simpleMessage(
"サブルールにマッチング。括弧の使用に注意",
),
"ruleActionUidDesc": MessageLookupByLibrary.simpleMessage(
"Linux USER IDをマッチング",
),
"ruleEmpty": MessageLookupByLibrary.simpleMessage("ルールが空です"),
"ruleName": MessageLookupByLibrary.simpleMessage("ルール名"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("ルールプロバイダー"),
"ruleSet": MessageLookupByLibrary.simpleMessage("ルールセット"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("ルール対象"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
"saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"),
@@ -563,8 +772,17 @@ class MessageLookup extends MessageLookupByLibrary {
"search": MessageLookupByLibrary.simpleMessage("検索"),
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
"selectProxies": MessageLookupByLibrary.simpleMessage("プロキシを選択"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
"プロキシプロバイダーを選択",
),
"selectRuleSet": MessageLookupByLibrary.simpleMessage("ルールセットを選択してください"),
"selectSplitStrategy": MessageLookupByLibrary.simpleMessage(
"分流戦略を選択してください",
),
"selectSubRule": MessageLookupByLibrary.simpleMessage("サブルールを選択してください"),
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
"selectedCountTitle": m12,
"selectedCountTitle": m17,
"settings": MessageLookupByLibrary.simpleMessage("設定"),
"show": MessageLookupByLibrary.simpleMessage("表示"),
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
@@ -578,6 +796,11 @@ class MessageLookup extends MessageLookupByLibrary {
"specialProxy": MessageLookupByLibrary.simpleMessage("特殊プロキシ"),
"specialRules": MessageLookupByLibrary.simpleMessage("特殊ルール"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("速度統計"),
"splitStrategy": MessageLookupByLibrary.simpleMessage("分流戦略"),
"splitStrategyNotEmpty": MessageLookupByLibrary.simpleMessage(
"分流戦略は空にできません",
),
"ssidsEmpty": MessageLookupByLibrary.simpleMessage("SSIDs is empty"),
"stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"),
"standard": MessageLookupByLibrary.simpleMessage("標準"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -591,6 +814,8 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("VPNを停止中..."),
"style": MessageLookupByLibrary.simpleMessage("スタイル"),
"subRule": MessageLookupByLibrary.simpleMessage("サブルール"),
"subRuleEmpty": MessageLookupByLibrary.simpleMessage("サブルールが空です"),
"subRuleNotEmpty": MessageLookupByLibrary.simpleMessage("サブルールは空にできません"),
"submit": MessageLookupByLibrary.simpleMessage("送信"),
"sync": MessageLookupByLibrary.simpleMessage("同期"),
"system": MessageLookupByLibrary.simpleMessage("システム"),
@@ -605,7 +830,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage("モバイル表示でのみ有効"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
"testInterval": MessageLookupByLibrary.simpleMessage("テスト間隔"),
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
"testWhenUsed": MessageLookupByLibrary.simpleMessage("使用時にテスト"),
"textScale": MessageLookupByLibrary.simpleMessage("テキストスケーリング"),
"theme": MessageLookupByLibrary.simpleMessage("テーマ"),
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
@@ -614,6 +841,7 @@ class MessageLookup extends MessageLookupByLibrary {
"threeColumns": MessageLookupByLibrary.simpleMessage("3列"),
"tight": MessageLookupByLibrary.simpleMessage("密"),
"time": MessageLookupByLibrary.simpleMessage("時間"),
"timeout": MessageLookupByLibrary.simpleMessage("タイムアウト"),
"tip": MessageLookupByLibrary.simpleMessage("ヒント"),
"toggle": MessageLookupByLibrary.simpleMessage("トグル"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("トーンスポット"),
@@ -640,7 +868,7 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
"urlTip": m13,
"urlTip": m18,
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
"value": MessageLookupByLibrary.simpleMessage("値"),
@@ -660,7 +888,7 @@ class MessageLookup extends MessageLookupByLibrary {
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV設定"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("ホワイトリストモード"),
"years": MessageLookupByLibrary.simpleMessage("年"),
"yearsAgo": m14,
"yearsAgo": m19,
"zh_CN": MessageLookupByLibrary.simpleMessage("簡体字中国語"),
};
}
+291 -16
View File
@@ -37,23 +37,35 @@ class MessageLookup extends MessageLookupByLibrary {
static String m6(count) =>
"${Intl.plural(count, one: '${count} час назад', few: '${count} часа назад', many: '${count} часов назад', other: '${count} часа назад')}";
static String m7(count) =>
static String m7(target) => "${target} является недопустимой политикой";
static String m8(proxyName) => "${proxyName} является недопустимым прокси";
static String m9(providerName) =>
"${providerName} является недопустимым провайдером прокси";
static String m10(subRule) => "${subRule} является недопустимым подправилом";
static String m11(appName) =>
"1. Open System Settings > Privacy & Security\n2. Choose Location Services\n3. Find and check ${appName} in the right list\n\nAfter completing the setup, return to the app and use it normally. Thank you for your cooperation.";
static String m12(count) =>
"${Intl.plural(count, one: '${count} минута назад', few: '${count} минуты назад', many: '${count} минут назад', other: '${count} минуты назад')}";
static String m8(count) =>
static String m13(count) =>
"${Intl.plural(count, one: '${count} месяц назад', few: '${count} месяца назад', many: '${count} месяцев назад', other: '${count} месяца назад')}";
static String m9(label) => "${label} пока отсутствуют";
static String m14(label) => "${label} пока отсутствуют";
static String m10(label) => "${label} должно быть числом";
static String m15(label) => "${label} должно быть числом";
static String m11(label) => "${label} должен быть числом от 1024 до 49151";
static String m16(label) => "${label} должен быть числом от 1024 до 49151";
static String m12(count) => "Выбрано ${count} элементов";
static String m17(count) => "Выбрано ${count} элементов";
static String m13(label) => "${label} должен быть URL";
static String m18(label) => "${label} должен быть URL";
static String m14(count) =>
static String m19(count) =>
"${Intl.plural(count, one: '${count} год назад', few: '${count} года назад', many: '${count} лет назад', other: '${count} года назад')}";
final messages = _notInlinedMessages(_notInlinedMessages);
@@ -81,11 +93,21 @@ class MessageLookup extends MessageLookupByLibrary {
"action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"addProfile": MessageLookupByLibrary.simpleMessage("Добавить профиль"),
"addProxies": MessageLookupByLibrary.simpleMessage("Добавить прокси"),
"addProxyGroup": MessageLookupByLibrary.simpleMessage(
"Добавить группу прокси",
),
"addProxyProviders": MessageLookupByLibrary.simpleMessage(
"Добавить провайдеров прокси",
),
"addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Добавить к оригинальным правилам",
),
"addedRules": MessageLookupByLibrary.simpleMessage("Добавленные правила"),
"additionalParameters": MessageLookupByLibrary.simpleMessage(
"Дополнительные параметры",
),
"address": MessageLookupByLibrary.simpleMessage("Адрес"),
"addressHelp": MessageLookupByLibrary.simpleMessage("Адрес сервера WebDAV"),
"addressTip": MessageLookupByLibrary.simpleMessage(
@@ -175,6 +197,11 @@ class MessageLookup extends MessageLookupByLibrary {
"basicConfigDesc": MessageLookupByLibrary.simpleMessage(
"Глобальное изменение базовых настроек",
),
"basicInfo": MessageLookupByLibrary.simpleMessage("Основная информация"),
"basicStrategy": MessageLookupByLibrary.simpleMessage("Базовая стратегия"),
"batteryOptimizationDesc": MessageLookupByLibrary.simpleMessage(
"To ensure background operation, please disable battery optimization for this app. Tap to go to settings.",
),
"bind": MessageLookupByLibrary.simpleMessage("Привязать"),
"blacklistMode": MessageLookupByLibrary.simpleMessage(
"Режим черного списка",
@@ -213,13 +240,25 @@ class MessageLookup extends MessageLookupByLibrary {
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
"Включение приведет к потере части функциональности приложения, но обеспечит полную поддержку Clash.",
),
"configDataDetected": MessageLookupByLibrary.simpleMessage(
"Данные обнаружены в конфигурации",
),
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите очистить все данные?",
),
"confirmDeleteProxyGroup": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите удалить текущую группу прокси?",
),
"confirmExitWindow": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите выйти из текущего окна?",
),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
),
"confirmOverwriteTip": MessageLookupByLibrary.simpleMessage(
"Существующие данные будут перезаписаны после подтверждения",
),
"connected": MessageLookupByLibrary.simpleMessage("Подключено"),
"connecting": MessageLookupByLibrary.simpleMessage("Подключение..."),
"connection": MessageLookupByLibrary.simpleMessage("Соединение"),
@@ -230,6 +269,9 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
"contentNotEmpty": MessageLookupByLibrary.simpleMessage(
"Содержимое не может быть пустым",
),
"contentScheme": MessageLookupByLibrary.simpleMessage("Контентная тема"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage(
"Управление глобальными добавленными правилами",
@@ -253,10 +295,15 @@ class MessageLookup extends MessageLookupByLibrary {
"При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя",
),
"create": MessageLookupByLibrary.simpleMessage("Создать"),
"createProfile": MessageLookupByLibrary.simpleMessage("Create Profile"),
"creationTime": MessageLookupByLibrary.simpleMessage("Время создания"),
"custom": MessageLookupByLibrary.simpleMessage("Пользовательский"),
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
"dashboard": MessageLookupByLibrary.simpleMessage("Панель управления"),
"dataChangedSave": MessageLookupByLibrary.simpleMessage(
"Обнаружены изменения данных, хотите сохранить?",
),
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
"Это приложение использует Firebase Crashlytics для сбора информации о сбоях nhằm улучшения стабильности приложения.\nСобираемые данные включают информацию об устройстве и подробности о сбоях, но не содержат персональных конфиденциальных данных.\nВы можете отключить эту функцию в настройках.",
),
@@ -298,6 +345,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Режим разработчика активирован.",
),
"direct": MessageLookupByLibrary.simpleMessage("Прямой"),
"disableUDP": MessageLookupByLibrary.simpleMessage("Отключить UDP"),
"disclaimer": MessageLookupByLibrary.simpleMessage(
"Отказ от ответственности",
),
@@ -325,6 +373,10 @@ class MessageLookup extends MessageLookupByLibrary {
"editGlobalRules": MessageLookupByLibrary.simpleMessage(
"Редактировать глобальные правила",
),
"editProxy": MessageLookupByLibrary.simpleMessage("Редактировать прокси"),
"editProxyGroup": MessageLookupByLibrary.simpleMessage(
"Редактировать группу прокси",
),
"editRule": MessageLookupByLibrary.simpleMessage("Редактировать правило"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("Английский"),
@@ -338,9 +390,18 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
),
"excludeProxyFilter": MessageLookupByLibrary.simpleMessage(
"Исключить фильтр прокси",
),
"excludeSsids": MessageLookupByLibrary.simpleMessage("Exclude SSIDs"),
"excludeSsidsDesc": MessageLookupByLibrary.simpleMessage(
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("Тип исключения"),
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("Ожидаемый статус"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"),
"exportFile": MessageLookupByLibrary.simpleMessage("Экспорт файла"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Экспорт логов"),
@@ -411,6 +472,7 @@ class MessageLookup extends MessageLookupByLibrary {
"hasCacheChange": MessageLookupByLibrary.simpleMessage(
"Хотите сохранить изменения в кэше?",
),
"hideFromList": MessageLookupByLibrary.simpleMessage("Скрыть из списка"),
"host": MessageLookupByLibrary.simpleMessage("Хост"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("Добавить Hosts"),
"hotkeyConflict": MessageLookupByLibrary.simpleMessage(
@@ -428,11 +490,28 @@ class MessageLookup extends MessageLookupByLibrary {
"iconConfiguration": MessageLookupByLibrary.simpleMessage(
"Конфигурация иконки",
),
"iconRecords": MessageLookupByLibrary.simpleMessage("История иконок"),
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
"iconUrl": MessageLookupByLibrary.simpleMessage("URL иконки"),
"ignoreBatteryOptimization": MessageLookupByLibrary.simpleMessage(
"Ignore Battery Optimization",
),
"import": MessageLookupByLibrary.simpleMessage("Импорт"),
"importFile": MessageLookupByLibrary.simpleMessage("Импорт из файла"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Импорт по URL"),
"includeAllProxies": MessageLookupByLibrary.simpleMessage(
"Включить все прокси",
),
"includeAllProxiesTip": MessageLookupByLibrary.simpleMessage(
"Импорт всех прокси, не содержащих группы прокси, дополнительные группы прокси можно добавить ниже",
),
"includeAllProxyProviders": MessageLookupByLibrary.simpleMessage(
"Включить всех провайдеров прокси",
),
"includeAllProxyProvidersTip": MessageLookupByLibrary.simpleMessage(
"При включении это переопределит импортированных провайдеров прокси",
),
"infiniteTime": MessageLookupByLibrary.simpleMessage(
"Долгосрочное действие",
),
@@ -440,6 +519,12 @@ class MessageLookup extends MessageLookupByLibrary {
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, введите правильную горячую клавишу",
),
"inputProxyGroupName": MessageLookupByLibrary.simpleMessage(
"Введите имя группы прокси",
),
"inputRuleContent": MessageLookupByLibrary.simpleMessage(
"Введите содержимое правила",
),
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
"Интеллектуальный выбор",
),
@@ -449,6 +534,10 @@ class MessageLookup extends MessageLookupByLibrary {
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Неверный файл резервной копии",
),
"invalidPolicy": m7,
"invalidProxy": m8,
"invalidProxyProvider": m9,
"invalidSubRule": m10,
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"При включении будет возможно получать IPv6 трафик",
@@ -474,6 +563,16 @@ class MessageLookup extends MessageLookupByLibrary {
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на локальный диск",
),
"locationPermission": MessageLookupByLibrary.simpleMessage(
"Location Permission",
),
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
"locationPermissionGuide": m11,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
"log": MessageLookupByLibrary.simpleMessage("Журнал"),
"logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -490,6 +589,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Используется для разблокировки Loopback UWP",
),
"loose": MessageLookupByLibrary.simpleMessage("Свободный"),
"matchSourceIp": MessageLookupByLibrary.simpleMessage(
"Сопоставить исходный IP",
),
"maxFailedTimes": MessageLookupByLibrary.simpleMessage(
"Макс. количество неудач",
),
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
"messageTest": MessageLookupByLibrary.simpleMessage(
"Тестирование сообщения",
@@ -503,12 +608,12 @@ class MessageLookup extends MessageLookupByLibrary {
"Изменить стандартное событие выхода из системы",
),
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
"minutesAgo": m7,
"minutesAgo": m12,
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
"monthsAgo": m8,
"monthsAgo": m13,
"more": MessageLookupByLibrary.simpleMessage("Еще"),
"name": MessageLookupByLibrary.simpleMessage("Имя"),
"nameSort": MessageLookupByLibrary.simpleMessage("Сортировка по имени"),
@@ -554,7 +659,11 @@ class MessageLookup extends MessageLookupByLibrary {
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, создайте профиль или добавьте действительный профиль",
),
"noRecords": MessageLookupByLibrary.simpleMessage("Нет записей"),
"noResolve": MessageLookupByLibrary.simpleMessage("Не разрешать IP"),
"noResolveHostname": MessageLookupByLibrary.simpleMessage(
"Не разрешать имя хоста",
),
"none": MessageLookupByLibrary.simpleMessage("Нет"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"Текущая группа прокси не может быть выбрана.",
@@ -562,8 +671,12 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"Нет профиля, пожалуйста, добавьте профиль",
),
"nullTip": m9,
"numberTip": m10,
"nullTip": m14,
"numberTip": m15,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
),
"oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -575,6 +688,7 @@ class MessageLookup extends MessageLookupByLibrary {
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
"При включении будет учитываться только трафик прокси",
),
"optional": MessageLookupByLibrary.simpleMessage("Необязательно"),
"options": MessageLookupByLibrary.simpleMessage("Опции"),
"other": MessageLookupByLibrary.simpleMessage("Другое"),
"otherContributors": MessageLookupByLibrary.simpleMessage(
@@ -631,10 +745,11 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Введите другой порт",
),
"portTip": m11,
"portTip": m16,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Приоритетное использование HTTP/3 для DOH",
),
"prerequisites": MessageLookupByLibrary.simpleMessage("Prerequisites"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, нажмите клавишу.",
),
@@ -669,9 +784,26 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("Проект"),
"providers": MessageLookupByLibrary.simpleMessage("Провайдеры"),
"proxies": MessageLookupByLibrary.simpleMessage("Прокси"),
"proxiesEmpty": MessageLookupByLibrary.simpleMessage("Список прокси пуст"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("Настройка прокси"),
"proxyChains": MessageLookupByLibrary.simpleMessage("Цепочки прокси"),
"proxyDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Обнаружена аномалия выбранных прокси",
),
"proxyFilter": MessageLookupByLibrary.simpleMessage("Фильтр прокси"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("Группа прокси"),
"proxyGroupDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Обнаружена аномалия текущей группы прокси",
),
"proxyGroupEmpty": MessageLookupByLibrary.simpleMessage(
"Группа прокси пуста",
),
"proxyGroupNameDuplicate": MessageLookupByLibrary.simpleMessage(
"Имя группы прокси дублируется",
),
"proxyGroupNameEmpty": MessageLookupByLibrary.simpleMessage(
"Имя группы прокси не может быть пустым",
),
"proxyNameserver": MessageLookupByLibrary.simpleMessage(
"Прокси-сервер имен",
),
@@ -682,13 +814,24 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Установить порт прослушивания Clash",
),
"proxyProviderDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"Обнаружена аномалия выбранных провайдеров прокси",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
"proxyProvidersEmpty": MessageLookupByLibrary.simpleMessage(
"Провайдеры прокси пусты",
),
"proxyProvidersNotEmpty": MessageLookupByLibrary.simpleMessage(
"Провайдеры прокси не могут быть пустыми",
),
"proxyType": MessageLookupByLibrary.simpleMessage("Тип прокси"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Очистить кэш"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Сканируйте QR-код для получения профиля",
),
"quickFill": MessageLookupByLibrary.simpleMessage("Быстрое заполнение"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
@@ -768,8 +911,109 @@ class MessageLookup extends MessageLookupByLibrary {
),
"ru": MessageLookupByLibrary.simpleMessage("Русский"),
"rule": MessageLookupByLibrary.simpleMessage("Правило"),
"ruleActionAndDesc": MessageLookupByLibrary.simpleMessage(
"Логическое правило AND",
),
"ruleActionDomainDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить полный домен",
),
"ruleActionDomainKeywordDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить ключевое слово домена",
),
"ruleActionDomainRegexDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставление по маске, поддерживает только * и ?",
),
"ruleActionDomainSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить суффикс домена",
),
"ruleActionDscpDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить метку DSCP (только для tproxy udp inbound)",
),
"ruleActionDstPortDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон портов назначения запроса",
),
"ruleActionGeoipDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить код страны IP-адреса",
),
"ruleActionGeositeDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить домены внутри Geosite",
),
"ruleActionInNameDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить входящее имя",
),
"ruleActionInPortDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить входящий порт",
),
"ruleActionInTypeDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить входящий тип",
),
"ruleActionInUserDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить входящее имя пользователя, поддерживает несколько имен через /",
),
"ruleActionIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить ASN IP-адреса",
),
"ruleActionIpCidr6Desc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон IP-адресов (IP-CIDR6 — это просто псевдоним)",
),
"ruleActionIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон IP-адресов",
),
"ruleActionIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон суффиксов IP",
),
"ruleActionMatchDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить все запросы, условия не требуются",
),
"ruleActionNetworkDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить TCP или UDP",
),
"ruleActionNotDesc": MessageLookupByLibrary.simpleMessage(
"Логическое правило NOT",
),
"ruleActionOrDesc": MessageLookupByLibrary.simpleMessage(
"Логическое правило OR",
),
"ruleActionProcessNameDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить по имени процесса, на Android соответствует имени пакета",
),
"ruleActionProcessNameRegexDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить по регулярному выражению имени процесса, на Android соответствует имени пакета",
),
"ruleActionProcessPathDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить по полному пути процесса",
),
"ruleActionProcessPathRegexDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить по регулярному выражению пути процесса",
),
"ruleActionRuleSetDesc": MessageLookupByLibrary.simpleMessage(
"Ссылка на набор правил, требуется настройка rule-providers",
),
"ruleActionSrcGeoipDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить код страны исходного IP",
),
"ruleActionSrcIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить ASN исходного IP",
),
"ruleActionSrcIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон исходных IP-адресов",
),
"ruleActionSrcIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон исходных суффиксов IP",
),
"ruleActionSrcPortDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить диапазон портов источника запроса",
),
"ruleActionSubRuleDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить с подправилом, обратите внимание на использование скобок",
),
"ruleActionUidDesc": MessageLookupByLibrary.simpleMessage(
"Сопоставить Linux USER ID",
),
"ruleEmpty": MessageLookupByLibrary.simpleMessage("Правило пусто"),
"ruleName": MessageLookupByLibrary.simpleMessage("Название правила"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("Провайдеры правил"),
"ruleSet": MessageLookupByLibrary.simpleMessage("Набор правил"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("Цель правила"),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"saveChanges": MessageLookupByLibrary.simpleMessage("Сохранить изменения?"),
@@ -783,8 +1027,21 @@ class MessageLookup extends MessageLookupByLibrary {
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
"selectProxies": MessageLookupByLibrary.simpleMessage("Выбрать прокси"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
"Выбрать провайдеров прокси",
),
"selectRuleSet": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, выберите набор правил",
),
"selectSplitStrategy": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, выберите стратегию разделения",
),
"selectSubRule": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, выберите подправило",
),
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
"selectedCountTitle": m12,
"selectedCountTitle": m17,
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"show": MessageLookupByLibrary.simpleMessage("Показать"),
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
@@ -802,6 +1059,13 @@ class MessageLookup extends MessageLookupByLibrary {
"speedStatistics": MessageLookupByLibrary.simpleMessage(
"Статистика скорости",
),
"splitStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия разделения",
),
"splitStrategyNotEmpty": MessageLookupByLibrary.simpleMessage(
"Стратегия разделения не может быть пустой",
),
"ssidsEmpty": MessageLookupByLibrary.simpleMessage("SSIDs is empty"),
"stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"),
"standard": MessageLookupByLibrary.simpleMessage("Стандартный"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -817,6 +1081,10 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("Остановка VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Стиль"),
"subRule": MessageLookupByLibrary.simpleMessage("Подправило"),
"subRuleEmpty": MessageLookupByLibrary.simpleMessage("Подправило пусто"),
"subRuleNotEmpty": MessageLookupByLibrary.simpleMessage(
"Подправило не может быть пустым",
),
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
"system": MessageLookupByLibrary.simpleMessage("Система"),
@@ -835,7 +1103,13 @@ class MessageLookup extends MessageLookupByLibrary {
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
"Включение позволит использовать параллелизм TCP",
),
"testInterval": MessageLookupByLibrary.simpleMessage(
"Интервал тестирования",
),
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
"testWhenUsed": MessageLookupByLibrary.simpleMessage(
"Тестировать при использовании",
),
"textScale": MessageLookupByLibrary.simpleMessage("Масштабирование текста"),
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
@@ -846,6 +1120,7 @@ class MessageLookup extends MessageLookupByLibrary {
"threeColumns": MessageLookupByLibrary.simpleMessage("Три столбца"),
"tight": MessageLookupByLibrary.simpleMessage("Плотный"),
"time": MessageLookupByLibrary.simpleMessage("Время"),
"timeout": MessageLookupByLibrary.simpleMessage("Таймаут"),
"tip": MessageLookupByLibrary.simpleMessage("подсказка"),
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"),
@@ -882,7 +1157,7 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Получить профиль через URL",
),
"urlTip": m13,
"urlTip": m18,
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
"Использовать системные hosts",
@@ -912,7 +1187,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Режим белого списка",
),
"years": MessageLookupByLibrary.simpleMessage("Лет"),
"yearsAgo": m14,
"yearsAgo": m19,
"zh_CN": MessageLookupByLibrary.simpleMessage("Упрощенный китайский"),
};
}
+197 -19
View File
@@ -34,21 +34,32 @@ class MessageLookup extends MessageLookupByLibrary {
static String m6(count) => "${count} 小时前";
static String m7(count) => "${count} 分钟前";
static String m7(target) => "${target} 是一个无效的策略";
static String m8(count) => "${count} 个月前";
static String m8(proxyName) => "${proxyName} 是一个无效的代理";
static String m9(label) => "暂无${label}";
static String m9(providerName) => "${providerName} 是一个无效的代理集";
static String m10(label) => "${label}必须为数字";
static String m10(subRule) => "${subRule} 是一个无效的SUB_RULE";
static String m11(label) => "${label} 必须在 1024 到 49151 之间";
static String m11(appName) =>
"1. 打开 系统设置 > 隐私与安全性\n2. 选择 定位服务\n3. 在右侧列表中找到并勾选 ${appName}\n\n完成设置后,返回应用即可正常使用。感谢您的配合。";
static String m12(count) => "已选择 ${count} 项";
static String m12(count) => "${count} 分钟前";
static String m13(label) => "${label}必须为URL";
static String m13(count) => "${count} 个月前";
static String m14(count) => "${count} 年前";
static String m14(label) => "暂无${label}";
static String m15(label) => "${label}必须为数字";
static String m16(label) => "${label} 必须在 1024 到 49151 之间";
static String m17(count) => "已选择 ${count} 项";
static String m18(label) => "${label}必须为URL";
static String m19(count) => "${count} 年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -71,9 +82,13 @@ class MessageLookup extends MessageLookupByLibrary {
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
"add": MessageLookupByLibrary.simpleMessage("添加"),
"addProfile": MessageLookupByLibrary.simpleMessage("添加配置"),
"addProxies": MessageLookupByLibrary.simpleMessage("添加代理"),
"addProxyGroup": MessageLookupByLibrary.simpleMessage("添加策略组"),
"addProxyProviders": MessageLookupByLibrary.simpleMessage("添加代理集"),
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
"addedRules": MessageLookupByLibrary.simpleMessage("附加规则"),
"additionalParameters": MessageLookupByLibrary.simpleMessage("附加参数"),
"address": MessageLookupByLibrary.simpleMessage("地址"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
@@ -117,6 +132,11 @@ class MessageLookup extends MessageLookupByLibrary {
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
"basicConfig": MessageLookupByLibrary.simpleMessage("基本配置"),
"basicConfigDesc": MessageLookupByLibrary.simpleMessage("全局修改基本配置"),
"basicInfo": MessageLookupByLibrary.simpleMessage("基础信息"),
"basicStrategy": MessageLookupByLibrary.simpleMessage("基础策略"),
"batteryOptimizationDesc": MessageLookupByLibrary.simpleMessage(
"为保证后台运行,请关闭本应用的电池优化。点击前往设置。",
),
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
"bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"),
@@ -139,9 +159,15 @@ class MessageLookup extends MessageLookupByLibrary {
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
"开启将失去部分应用能力,获得全量的Clash的支持",
),
"configDataDetected": MessageLookupByLibrary.simpleMessage("检测到配置中存在数据"),
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
"confirmClearAllData": MessageLookupByLibrary.simpleMessage("确定要清除所有数据?"),
"confirmDeleteProxyGroup": MessageLookupByLibrary.simpleMessage(
"确定要删除当前策略组吗?",
),
"confirmExitWindow": MessageLookupByLibrary.simpleMessage("确定要退出当前窗口吗?"),
"confirmForceCrashCore": MessageLookupByLibrary.simpleMessage("确定要强制崩溃核心?"),
"confirmOverwriteTip": MessageLookupByLibrary.simpleMessage("确定后将会覆盖已有数据"),
"connected": MessageLookupByLibrary.simpleMessage("已连接"),
"connecting": MessageLookupByLibrary.simpleMessage("连接中..."),
"connection": MessageLookupByLibrary.simpleMessage("连接"),
@@ -150,6 +176,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentNotEmpty": MessageLookupByLibrary.simpleMessage("内容不能为空"),
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
"controlGlobalAddedRules": MessageLookupByLibrary.simpleMessage("控制全局附加规则"),
"copy": MessageLookupByLibrary.simpleMessage("复制"),
@@ -169,10 +196,13 @@ class MessageLookup extends MessageLookupByLibrary {
"开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志",
),
"create": MessageLookupByLibrary.simpleMessage("创建"),
"createProfile": MessageLookupByLibrary.simpleMessage("创建配置"),
"creationTime": MessageLookupByLibrary.simpleMessage("创建时间"),
"custom": MessageLookupByLibrary.simpleMessage("自定义"),
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
"dark": MessageLookupByLibrary.simpleMessage("深色"),
"dashboard": MessageLookupByLibrary.simpleMessage("仪表盘"),
"dataChangedSave": MessageLookupByLibrary.simpleMessage("检测到数据有更改,是否保存"),
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
"本应用使用 Firebase Crashlytics 收集崩溃信息以改进应用稳定性。\n收集的数据包括设备信息和崩溃详情,不包含个人敏感数据。\n您可以在设置中关闭此功能。",
),
@@ -200,6 +230,7 @@ class MessageLookup extends MessageLookupByLibrary {
"developerMode": MessageLookupByLibrary.simpleMessage("开发者模式"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage("开发者模式已启用。"),
"direct": MessageLookupByLibrary.simpleMessage("直连"),
"disableUDP": MessageLookupByLibrary.simpleMessage("禁用UDP"),
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
"本软件仅供学习交流、科研等非商业性质的用途,严禁将本软件用于商业目的。如有任何商业行为,均与本软件无关。",
@@ -215,6 +246,8 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("下载"),
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
"editGlobalRules": MessageLookupByLibrary.simpleMessage("编辑全局规则"),
"editProxy": MessageLookupByLibrary.simpleMessage("编辑代理"),
"editProxyGroup": MessageLookupByLibrary.simpleMessage("编辑策略组"),
"editRule": MessageLookupByLibrary.simpleMessage("编辑规则"),
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英语"),
@@ -222,9 +255,16 @@ class MessageLookup extends MessageLookupByLibrary {
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
"excludeProxyFilter": MessageLookupByLibrary.simpleMessage("排除节点过滤器"),
"excludeSsids": MessageLookupByLibrary.simpleMessage("排除SSIDs"),
"excludeSsidsDesc": MessageLookupByLibrary.simpleMessage(
"连接到被排除SSID的WIFI时,将会自动切换应用运行状态",
),
"excludeType": MessageLookupByLibrary.simpleMessage("排除类型"),
"existsTip": m5,
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("预期状态"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"exportFile": MessageLookupByLibrary.simpleMessage("导出文件"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
@@ -265,6 +305,7 @@ class MessageLookup extends MessageLookupByLibrary {
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
"goToConfigureScript": MessageLookupByLibrary.simpleMessage("前往配置脚本"),
"hasCacheChange": MessageLookupByLibrary.simpleMessage("是否缓存修改"),
"hideFromList": MessageLookupByLibrary.simpleMessage("从列表中隐藏"),
"host": MessageLookupByLibrary.simpleMessage("主机"),
"hostsDesc": MessageLookupByLibrary.simpleMessage("追加Hosts"),
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("快捷键冲突"),
@@ -274,19 +315,36 @@ class MessageLookup extends MessageLookupByLibrary {
"hoursAgo": m6,
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
"iconRecords": MessageLookupByLibrary.simpleMessage("图标记录"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
"iconUrl": MessageLookupByLibrary.simpleMessage("图标链接"),
"ignoreBatteryOptimization": MessageLookupByLibrary.simpleMessage("忽略电池优化"),
"import": MessageLookupByLibrary.simpleMessage("导入"),
"importFile": MessageLookupByLibrary.simpleMessage("通过文件导入"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"importUrl": MessageLookupByLibrary.simpleMessage("通过URL导入"),
"includeAllProxies": MessageLookupByLibrary.simpleMessage("包含所有代理"),
"includeAllProxiesTip": MessageLookupByLibrary.simpleMessage(
"引入不包含策略组的所有代理,可在下方额外添加策略组",
),
"includeAllProxyProviders": MessageLookupByLibrary.simpleMessage("包含所有代理集"),
"includeAllProxyProvidersTip": MessageLookupByLibrary.simpleMessage(
"开启后将覆盖引入的代理集",
),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
"inputProxyGroupName": MessageLookupByLibrary.simpleMessage("输入策略组名称"),
"inputRuleContent": MessageLookupByLibrary.simpleMessage("输入规则内容"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
"interval": MessageLookupByLibrary.simpleMessage("间隔"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("无效备份文件"),
"invalidPolicy": m7,
"invalidProxy": m8,
"invalidProxyProvider": m9,
"invalidSubRule": m10,
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
@@ -304,6 +362,14 @@ class MessageLookup extends MessageLookupByLibrary {
"loading": MessageLookupByLibrary.simpleMessage("加载中..."),
"local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"locationPermission": MessageLookupByLibrary.simpleMessage("位置权限"),
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"根据系统要求,获取Wi-Fi名称需要您授予位置权限。",
),
"locationPermissionGuide": m11,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"需要位置权限",
),
"log": MessageLookupByLibrary.simpleMessage("日志"),
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
@@ -314,6 +380,8 @@ class MessageLookup extends MessageLookupByLibrary {
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
"matchSourceIp": MessageLookupByLibrary.simpleMessage("匹配来源IP"),
"maxFailedTimes": MessageLookupByLibrary.simpleMessage("最大失败次数"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
"messageTest": MessageLookupByLibrary.simpleMessage("消息测试"),
"messageTestTip": MessageLookupByLibrary.simpleMessage("这是一条消息。"),
@@ -321,12 +389,12 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"minutesAgo": m7,
"minutesAgo": m12,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
"months": MessageLookupByLibrary.simpleMessage("月"),
"monthsAgo": m8,
"monthsAgo": m13,
"more": MessageLookupByLibrary.simpleMessage("更多"),
"name": MessageLookupByLibrary.simpleMessage("名称"),
"nameSort": MessageLookupByLibrary.simpleMessage("按名称排序"),
@@ -354,12 +422,16 @@ class MessageLookup extends MessageLookupByLibrary {
"noNetworkApp": MessageLookupByLibrary.simpleMessage("无网络应用"),
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
"noRecords": MessageLookupByLibrary.simpleMessage("暂无记录"),
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
"noResolveHostname": MessageLookupByLibrary.simpleMessage("不解析主机名"),
"none": MessageLookupByLibrary.simpleMessage("无"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullTip": m9,
"numberTip": m10,
"nullTip": m14,
"numberTip": m15,
"onDemand": MessageLookupByLibrary.simpleMessage("按需运行"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage("配置程序特定场景运行状态"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
@@ -367,6 +439,7 @@ class MessageLookup extends MessageLookupByLibrary {
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
"开启后,将只统计代理流量",
),
"optional": MessageLookupByLibrary.simpleMessage("可选"),
"options": MessageLookupByLibrary.simpleMessage("选项"),
"other": MessageLookupByLibrary.simpleMessage("其他"),
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
@@ -397,8 +470,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("端口"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
"portTip": m11,
"portTip": m16,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
"prerequisites": MessageLookupByLibrary.simpleMessage("前置条件"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
"preview": MessageLookupByLibrary.simpleMessage("预览"),
"process": MessageLookupByLibrary.simpleMessage("进程"),
@@ -425,18 +499,36 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("项目"),
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
"proxiesEmpty": MessageLookupByLibrary.simpleMessage("代理为空"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
"proxyChains": MessageLookupByLibrary.simpleMessage("代理链"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
"proxyDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"检测到选中的代理存在异常",
),
"proxyFilter": MessageLookupByLibrary.simpleMessage("节点过滤器"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("策略组"),
"proxyGroupDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"检测到当前策略组异常",
),
"proxyGroupEmpty": MessageLookupByLibrary.simpleMessage("策略组为空"),
"proxyGroupNameDuplicate": MessageLookupByLibrary.simpleMessage("策略组名称重复"),
"proxyGroupNameEmpty": MessageLookupByLibrary.simpleMessage("策略组名称不能为空"),
"proxyNameserver": MessageLookupByLibrary.simpleMessage("代理域名服务器"),
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析代理节点的域名"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
"proxyProviderDetectedAbnormal": MessageLookupByLibrary.simpleMessage(
"检测到选中的代理集存在异常",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理集"),
"proxyProvidersEmpty": MessageLookupByLibrary.simpleMessage("代理集为空"),
"proxyProvidersNotEmpty": MessageLookupByLibrary.simpleMessage("代理集不能为空"),
"proxyType": MessageLookupByLibrary.simpleMessage("代理类型"),
"pruneCache": MessageLookupByLibrary.simpleMessage("修剪缓存"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"quickFill": MessageLookupByLibrary.simpleMessage("一键填入"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"),
@@ -482,8 +574,81 @@ class MessageLookup extends MessageLookupByLibrary {
"routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"),
"ru": MessageLookupByLibrary.simpleMessage("俄语"),
"rule": MessageLookupByLibrary.simpleMessage("规则"),
"ruleActionAndDesc": MessageLookupByLibrary.simpleMessage("逻辑规则 AND"),
"ruleActionDomainDesc": MessageLookupByLibrary.simpleMessage("匹配完整域名"),
"ruleActionDomainKeywordDesc": MessageLookupByLibrary.simpleMessage(
"匹配域名关键字",
),
"ruleActionDomainRegexDesc": MessageLookupByLibrary.simpleMessage(
"通配符匹配,仅支持*和?通配符",
),
"ruleActionDomainSuffixDesc": MessageLookupByLibrary.simpleMessage(
"匹配域名后缀",
),
"ruleActionDscpDesc": MessageLookupByLibrary.simpleMessage(
"匹配DSCP标记 (仅限 tproxy udp 入站)",
),
"ruleActionDstPortDesc": MessageLookupByLibrary.simpleMessage("匹配请求目标端口范围"),
"ruleActionGeoipDesc": MessageLookupByLibrary.simpleMessage("匹配 IP 所属国家代码"),
"ruleActionGeositeDesc": MessageLookupByLibrary.simpleMessage(
"匹配 Geosite 内的域名",
),
"ruleActionInNameDesc": MessageLookupByLibrary.simpleMessage("匹配入站名称"),
"ruleActionInPortDesc": MessageLookupByLibrary.simpleMessage("匹配入站端口"),
"ruleActionInTypeDesc": MessageLookupByLibrary.simpleMessage("匹配入站类型"),
"ruleActionInUserDesc": MessageLookupByLibrary.simpleMessage(
"匹配入站用户名,支持使用 / 分隔多个用户名",
),
"ruleActionIpAsnDesc": MessageLookupByLibrary.simpleMessage("匹配 IP 所属 ASN"),
"ruleActionIpCidr6Desc": MessageLookupByLibrary.simpleMessage(
"匹配 IP 地址范围, IP-CIDR6 只是一个别名",
),
"ruleActionIpCidrDesc": MessageLookupByLibrary.simpleMessage("匹配 IP 地址范围"),
"ruleActionIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"匹配 IP 后缀范围",
),
"ruleActionMatchDesc": MessageLookupByLibrary.simpleMessage("匹配所有请求,无需条件"),
"ruleActionNetworkDesc": MessageLookupByLibrary.simpleMessage("匹配TCP或者UDP"),
"ruleActionNotDesc": MessageLookupByLibrary.simpleMessage("逻辑规则 NOT"),
"ruleActionOrDesc": MessageLookupByLibrary.simpleMessage("逻辑规则 OR"),
"ruleActionProcessNameDesc": MessageLookupByLibrary.simpleMessage(
"使用进程匹配,在Android平台可以匹配包名",
),
"ruleActionProcessNameRegexDesc": MessageLookupByLibrary.simpleMessage(
"使用进程名称正则表达式匹配,在Android平台可以匹配包名",
),
"ruleActionProcessPathDesc": MessageLookupByLibrary.simpleMessage(
"使用完整进程路径匹配",
),
"ruleActionProcessPathRegexDesc": MessageLookupByLibrary.simpleMessage(
"使用进程路径正则表达式匹配",
),
"ruleActionRuleSetDesc": MessageLookupByLibrary.simpleMessage(
"引用规则集合,需配置rule-providers",
),
"ruleActionSrcGeoipDesc": MessageLookupByLibrary.simpleMessage(
"匹配来源 IP 所属国家代码",
),
"ruleActionSrcIpAsnDesc": MessageLookupByLibrary.simpleMessage(
"匹配来源 IP 所属 ASN",
),
"ruleActionSrcIpCidrDesc": MessageLookupByLibrary.simpleMessage(
"匹配来源 IP 地址范围",
),
"ruleActionSrcIpSuffixDesc": MessageLookupByLibrary.simpleMessage(
"匹配来源 IP 后缀范围",
),
"ruleActionSrcPortDesc": MessageLookupByLibrary.simpleMessage("匹配请求来源端口范围"),
"ruleActionSubRuleDesc": MessageLookupByLibrary.simpleMessage(
"匹配至子规则,需要注意括号的使用",
),
"ruleActionUidDesc": MessageLookupByLibrary.simpleMessage(
"匹配 Linux USER ID",
),
"ruleEmpty": MessageLookupByLibrary.simpleMessage("规则为空"),
"ruleName": MessageLookupByLibrary.simpleMessage("规则名称"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则集"),
"ruleSet": MessageLookupByLibrary.simpleMessage("规则集"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("规则目标"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
"saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"),
@@ -495,8 +660,13 @@ class MessageLookup extends MessageLookupByLibrary {
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selectProxies": MessageLookupByLibrary.simpleMessage("选择代理"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage("选择代理集"),
"selectRuleSet": MessageLookupByLibrary.simpleMessage("请选择规则集"),
"selectSplitStrategy": MessageLookupByLibrary.simpleMessage("请选择分流策略"),
"selectSubRule": MessageLookupByLibrary.simpleMessage("请选择子规则"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
"selectedCountTitle": m12,
"selectedCountTitle": m17,
"settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
@@ -510,6 +680,9 @@ class MessageLookup extends MessageLookupByLibrary {
"specialProxy": MessageLookupByLibrary.simpleMessage("特殊代理"),
"specialRules": MessageLookupByLibrary.simpleMessage("特殊规则"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("网速统计"),
"splitStrategy": MessageLookupByLibrary.simpleMessage("分流策略"),
"splitStrategyNotEmpty": MessageLookupByLibrary.simpleMessage("分流策略不能为空"),
"ssidsEmpty": MessageLookupByLibrary.simpleMessage("SSIDs为空"),
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
"standard": MessageLookupByLibrary.simpleMessage("标准"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -523,6 +696,8 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
"style": MessageLookupByLibrary.simpleMessage("风格"),
"subRule": MessageLookupByLibrary.simpleMessage("子规则"),
"subRuleEmpty": MessageLookupByLibrary.simpleMessage("子规则为空"),
"subRuleNotEmpty": MessageLookupByLibrary.simpleMessage("子规则不能为空"),
"submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"system": MessageLookupByLibrary.simpleMessage("系统"),
@@ -535,7 +710,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage("仅在移动视图中有效"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
"testInterval": MessageLookupByLibrary.simpleMessage("测试间隔"),
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
"testWhenUsed": MessageLookupByLibrary.simpleMessage("使用时测试"),
"textScale": MessageLookupByLibrary.simpleMessage("文本缩放"),
"theme": MessageLookupByLibrary.simpleMessage("主题"),
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
@@ -544,6 +721,7 @@ class MessageLookup extends MessageLookupByLibrary {
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
"tight": MessageLookupByLibrary.simpleMessage("紧凑"),
"time": MessageLookupByLibrary.simpleMessage("时间"),
"timeout": MessageLookupByLibrary.simpleMessage("超时"),
"tip": MessageLookupByLibrary.simpleMessage("提示"),
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("调性点缀"),
@@ -568,7 +746,7 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("上传"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
"urlTip": m13,
"urlTip": m18,
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage("值"),
@@ -588,7 +766,7 @@ class MessageLookup extends MessageLookupByLibrary {
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
"years": MessageLookupByLibrary.simpleMessage("年"),
"yearsAgo": m14,
"yearsAgo": m19,
"zh_CN": MessageLookupByLibrary.simpleMessage("中文简体"),
};
}
+1020
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -5,6 +5,7 @@ import 'package:fl_clash/pages/error.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rust_api/rust_api.dart';
import 'application.dart';
import 'common/common.dart';
@@ -12,6 +13,7 @@ import 'common/common.dart';
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
await RustLib.init();
final version = await system.version;
final container = await globalState.init(version);
HttpOverrides.global = FlClashHttpOverrides();
+1 -1
View File
@@ -32,7 +32,7 @@ class _AndroidContainerState extends ConsumerState<AndroidManager>
if (prev != next) {
debouncer.call(FunctionTag.saveSharedFile, () async {
preferences.saveShareState(next);
}, duration: Duration(seconds: 1));
}, duration: const Duration(seconds: 1));
if (prev?.needSyncSharedState != next.needSyncSharedState) {
service?.syncState(next.needSyncSharedState);
}
+24 -13
View File
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/manager/window_manager.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -33,12 +32,16 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
});
ref.listenManual(configProvider, (prev, next) {
if (prev != next) {
appController.savePreferencesDebounce();
globalState.container
.read(storeActionProvider.notifier)
.savePreferencesDebounce();
}
});
ref.listenManual(needUpdateGroupsProvider, (prev, next) {
if (prev != next) {
appController.updateGroupsDebounce();
globalState.container
.read(proxiesActionProvider.notifier)
.updateGroupsDebounce();
}
});
if (window == null) {
@@ -66,11 +69,15 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
commonPrint.log('$state');
if (state == AppLifecycleState.resumed) {
Future.delayed(const Duration(milliseconds: 1500), () {
globalState.checkTaskPermissions();
});
render?.resume();
WidgetsBinding.instance.addPostFrameCallback((_) {
appController.tryCheckIp();
final ref = globalState.container;
ref.read(setupActionProvider.notifier).tryCheckIp();
if (system.isAndroid) {
appController.tryStartCore();
ref.read(coreActionProvider.notifier).tryStartCore();
}
});
}
@@ -78,7 +85,7 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
@override
void didChangePlatformBrightness() {
appController.updateBrightness();
globalState.container.read(themeActionProvider.notifier).updateBrightness();
}
@override
@@ -164,6 +171,12 @@ class AppSidebarContainer extends ConsumerWidget {
});
}
void _handleToPage(PageLabel pageLabel) {
globalState.container
.read(currentPageLabelProvider.notifier)
.toPage(pageLabel);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final navigationState = ref.watch(navigationStateProvider);
@@ -182,11 +195,11 @@ class AppSidebarContainer extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (system.isMacOS) SizedBox(height: 22),
SizedBox(height: 10),
if (system.isMacOS) const SizedBox(height: 22),
const SizedBox(height: 10),
if (!system.isMacOS) ...[
ClipRect(child: AppIcon()),
SizedBox(height: 12),
const ClipRect(child: AppIcon()),
const SizedBox(height: 12),
],
Expanded(
child: ScrollConfiguration(
@@ -216,9 +229,7 @@ class AppSidebarContainer extends ConsumerWidget {
)
.toList(),
onDestinationSelected: (index) {
appController.toPage(
navigationItems[index].label,
);
_handleToPage(navigationItems[index].label);
},
extended: false,
selectedIndex: currentIndex,
+8
View File
@@ -1,7 +1,10 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'package:wifi_ssid/wifi_ssid.dart';
class ConnectivityManager extends StatefulWidget {
final Function(List<ConnectivityResult> results)? onConnectivityChanged;
@@ -24,6 +27,11 @@ class _ConnectivityManagerState extends State<ConnectivityManager> {
void initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((results) async {
if (results.contains(ConnectivityResult.wifi)) {
WifiSsidManager.instance.getSsid().then((ssid) {
commonPrint.log('Wifi ssid $ssid', logLevel: LogLevel.info);
});
}
if (widget.onConnectivityChanged != null) {
widget.onConnectivityChanged!(results);
}
+15 -14
View File
@@ -1,8 +1,8 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/providers/state.dart';
@@ -30,17 +30,16 @@ class _CoreContainerState extends ConsumerState<CoreManager>
void initState() {
super.initState();
coreEventManager.addListener(this);
ref.listenManual(
currentSetupStateProvider.select((state) => state?.profileId),
(prev, next) {
if (prev != next) {
appController.fullSetup();
}
},
);
ref.listenManual(currentProfileIdProvider, (prev, next) {
if (prev != next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(setupActionProvider.notifier).fullSetup();
});
}
});
ref.listenManual(updateParamsProvider, (prev, next) {
if (prev != next) {
appController.updateConfigDebounce();
ref.read(setupActionProvider.notifier).updateConfigDebounce();
}
});
ref.listenManual(appSettingProvider.select((state) => state.openLogs), (
@@ -64,15 +63,16 @@ class _CoreContainerState extends ConsumerState<CoreManager>
@override
Future<void> onDelay(Delay delay) async {
super.onDelay(delay);
appController.setDelay(delay);
final proxiesAction = ref.read(proxiesActionProvider.notifier);
proxiesAction.setDelay(delay);
debouncer.call(FunctionTag.updateDelay, () async {
appController.updateGroupsDebounce();
proxiesAction.updateGroupsDebounce();
}, duration: const Duration(milliseconds: 5000));
}
@override
void onLog(Log log) {
ref.read(logsProvider.notifier).addLog(log);
ref.read(logsProvider.notifier).add(log);
if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload);
}
@@ -87,11 +87,12 @@ class _CoreContainerState extends ConsumerState<CoreManager>
@override
Future<void> onLoaded(String providerName) async {
final ref = globalState.container;
ref
.read(providersProvider.notifier)
.setProvider(await coreController.getExternalProvider(providerName));
debouncer.call(FunctionTag.loadedProvider, () async {
appController.updateGroupsDebounce();
ref.read(proxiesActionProvider.notifier).updateGroupsDebounce();
}, duration: const Duration(milliseconds: 5000));
super.onLoaded(providerName);
}
+15 -9
View File
@@ -1,8 +1,9 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -29,17 +30,20 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
}
Future<void> _handleHotKeyAction(HotAction action) async {
final ref = globalState.container;
final commonAction = ref.read(commonActionProvider.notifier);
final systemAction = ref.read(systemActionProvider.notifier);
switch (action) {
case HotAction.mode:
appController.updateMode();
commonAction.updateMode();
case HotAction.start:
appController.updateStart();
commonAction.updateStart();
case HotAction.view:
appController.updateVisible();
systemAction.updateVisible();
case HotAction.proxy:
appController.updateSystemProxy();
systemAction.updateSystemProxy();
case HotAction.tun:
appController.updateTun();
systemAction.updateTun();
}
}
@@ -59,7 +63,7 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
key: PhysicalKeyboardKey(hotKeyAction.key!),
modifiers: modifiers,
);
return await hotKeyManager.register(
return hotKeyManager.register(
hotKey,
keyDownHandler: (_) {
_handleHotKeyAction(hotKeyAction.action);
@@ -73,12 +77,14 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
return Shortcuts(
shortcuts: {
utils.controlSingleActivator(LogicalKeyboardKey.keyW):
CloseWindowIntent(),
const CloseWindowIntent(),
},
child: Actions(
actions: {
CloseWindowIntent: CallbackAction<CloseWindowIntent>(
onInvoke: (_) => appController.handleBackOrExit(),
onInvoke: (_) => globalState.container
.read(systemActionProvider.notifier)
.handleBackOrExit(),
),
DoNothingIntent: CallbackAction<DoNothingIntent>(
onInvoke: (_) => null,
+5 -5
View File
@@ -102,7 +102,7 @@ class StatusManagerState extends State<StatusManager> {
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: AnimatedSize(
duration: animateDuration,
child: ValueListenableBuilder(
@@ -111,7 +111,7 @@ class StatusManagerState extends State<StatusManager> {
return FadeThroughBox(
alignment: Alignment.centerRight,
child: messages.isEmpty
? SizedBox()
? const SizedBox()
: LayoutBuilder(
key: Key(messages.last.id),
builder: (_, constraints) {
@@ -132,10 +132,10 @@ class StatusManagerState extends State<StatusManager> {
.surfaceContainerHigh,
child: Container(
width: min(constraints.maxWidth, 500),
constraints: BoxConstraints(
constraints: const BoxConstraints(
minHeight: 54,
),
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
@@ -158,7 +158,7 @@ class StatusManagerState extends State<StatusManager> {
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: 16),
const SizedBox(width: 16),
if (messages.last.actionState !=
null)
CommonMinFilledButtonTheme(
+7 -5
View File
@@ -2,7 +2,7 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
@@ -109,10 +109,12 @@ class ThemeManager extends ConsumerWidget {
),
),
child: LayoutBuilder(
builder: (_, container) {
appController.updateViewSize(
Size(container.maxWidth, container.maxHeight),
);
builder: (_, constraints) {
globalState.container
.read(themeActionProvider.notifier)
.updateViewSize(
Size(constraints.maxWidth, constraints.maxHeight),
);
return _buildSystemUi(child);
},
),
+4 -5
View File
@@ -1,5 +1,4 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
@@ -29,8 +28,8 @@ class _TileContainerState extends ConsumerState<TileManager> with TileListener {
if (isStart && coreController.isCompleted) {
return;
}
appController.updateStatus(true);
app?.tip(appLocalizations.startVpn);
ref.read(setupActionProvider.notifier).updateStatus(true);
app?.tip(currentAppLocalizations.startVpn);
super.onStart();
}
@@ -39,8 +38,8 @@ class _TileContainerState extends ConsumerState<TileManager> with TileListener {
if (!isStart) {
return;
}
appController.updateStatus(false);
app?.tip(appLocalizations.stopVpn);
ref.read(setupActionProvider.notifier).updateStatus(false);
app?.tip(currentAppLocalizations.stopVpn);
super.onStop();
}
+4 -4
View File
@@ -1,5 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -21,7 +21,7 @@ class _TrayContainerState extends ConsumerState<TrayManager> with TrayListener {
trayManager.addListener(this);
ref.listenManual(trayStateProvider, (prev, next) {
if (prev != next) {
appController.updateTray();
ref.read(systemActionProvider.notifier).updateTray();
}
});
if (system.isMacOS) {
@@ -54,12 +54,12 @@ class _TrayContainerState extends ConsumerState<TrayManager> with TrayListener {
}
@override
onTrayIconMouseDown() {
void onTrayIconMouseDown() {
window?.show();
}
@override
dispose() {
void dispose() {
trayManager.removeListener(this);
super.dispose();
}
+6 -5
View File
@@ -1,7 +1,7 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/state.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -35,12 +35,13 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
return;
}
globalState.showNotifier(
appLocalizations.vpnConfigChangeDetected,
currentAppLocalizations.vpnConfigChangeDetected,
actionState: MessageActionState(
actionText: appLocalizations.restart,
actionText: currentAppLocalizations.restart,
action: () async {
await globalState.handleStop();
await appController.updateStatus(true);
final setupAction = ref.read(setupActionProvider.notifier);
await setupAction.handleStop();
await setupAction.updateStatus(true);
},
),
);
+22 -16
View File
@@ -1,9 +1,9 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:window_ext/window_ext.dart';
@@ -44,7 +44,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override
void onWindowClose() async {
await appController.handleBackOrExit();
await ref.read(systemActionProvider.notifier).handleBackOrExit();
super.onWindowClose();
}
@@ -57,7 +57,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override
Future<void> onShouldTerminate() async {
await appController.handleExit();
await ref.read(systemActionProvider.notifier).handleExit();
super.onShouldTerminate();
}
@@ -65,8 +65,9 @@ class _WindowContainerState extends ConsumerState<WindowManager>
void onWindowMoved() {
super.onWindowMoved();
windowManager.getPosition().then((offset) {
ref.read(windowSettingProvider.notifier);
// .update((state) => state.copyWith(top: offset.dy, left: offset.dx));
ref
.read(windowSettingProvider.notifier)
.update((state) => state.copyWith(top: offset.dy, left: offset.dx));
});
}
@@ -83,7 +84,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override
void onWindowMinimize() async {
appController.savePreferencesDebounce();
ref.read(storeActionProvider.notifier).savePreferencesDebounce();
commonPrint.log('minimize');
render?.pause();
super.onWindowMinimize();
@@ -166,13 +167,16 @@ class _WindowHeaderState extends State<WindowHeader> {
Future<void> _updateMaximized() async {
final isMaximized = await windowManager.isMaximized();
switch (isMaximized) {
case true:
await windowManager.unmaximize();
break;
case false:
await windowManager.maximize();
break;
if (isMaximized) {
await windowManager.unmaximize();
if (system.isWindows) {
windowExtManager.setWindowCornerPreference(round: true);
}
} else {
await windowManager.maximize();
if (system.isWindows) {
windowExtManager.setWindowCornerPreference(round: false);
}
}
isMaximizedNotifier.value = await windowManager.isMaximized();
}
@@ -220,7 +224,9 @@ class _WindowHeaderState extends State<WindowHeader> {
),
IconButton(
onPressed: () {
appController.handleBackOrExit();
globalState.container
.read(systemActionProvider.notifier)
.handleBackOrExit();
},
icon: const Icon(Icons.close),
),
@@ -275,9 +281,9 @@ class AppIcon extends StatelessWidget {
borderRadius: BorderRadius.circular(14),
),
),
padding: EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
child: Transform.translate(
offset: Offset(0, -1),
offset: const Offset(0, -1),
child: Image.asset('assets/images/icon.png', width: 34, height: 34),
),
);
+183 -81
View File
@@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/clash_config.freezed.dart';
part 'generated/clash_config.g.dart';
const defaultClashConfig = ClashConfig();
const defaultClashConfig = PatchClashConfig();
const defaultTun = Tun();
const defaultDns = Dns();
@@ -102,27 +102,56 @@ const defaultBypassPrivateRouteAddress = [
@freezed
abstract class ProxyGroup with _$ProxyGroup {
const factory ProxyGroup({
int? profileId,
@JsonKey(fromJson: Snowflake.buildId) required int id,
required String name,
@JsonKey(fromJson: GroupType.parseProfileType) required GroupType type,
required GroupType type,
List<String>? proxies,
List<String>? use,
int? interval,
bool? lazy,
@JsonKey(name: 'disable-udp') bool? disableUDP,
String? url,
int? timeout,
@JsonKey(name: 'max-failed-times') int? maxFailedTimes,
String? filter,
@JsonKey(name: 'expected-filter') String? excludeFilter,
@JsonKey(name: 'exclude-filter') String? excludeFilter,
@JsonKey(name: 'exclude-type') String? excludeType,
@JsonKey(name: 'expected-status') dynamic expectedStatus,
@JsonKey(name: 'expected-status') String? expectedStatus,
@JsonKey(name: 'include-all') bool? includeAll,
@JsonKey(name: 'include-all-proxies') bool? includeAllProxies,
@JsonKey(name: 'include-all-providers') bool? includeAllProviders,
bool? hidden,
String? icon,
String? order,
}) = _ProxyGroup;
factory ProxyGroup.fromJson(Map<String, Object?> json) =>
_$ProxyGroupFromJson(json);
}
@freezed
abstract class Proxy with _$Proxy {
const factory Proxy({
required String name,
required String type,
String? now,
}) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
}
@freezed
abstract class CustomOverwriteDate with _$CustomOverwriteDate {
const factory CustomOverwriteDate({
@Default([]) List<Proxy> proxies,
@Default([]) List<ProxyGroup> proxyGroups,
@Default({}) Set<String> proxyProviders,
@Default({}) Set<String> ruleTargets,
@Default({}) Set<String> subRules,
}) = _CustomOverwriteDate;
}
@freezed
abstract class RuleProvider with _$RuleProvider {
const factory RuleProvider({required String name}) = _RuleProvider;
@@ -131,6 +160,14 @@ abstract class RuleProvider with _$RuleProvider {
_$RuleProviderFromJson(json);
}
@freezed
abstract class ProxyProvider with _$ProxyProvider {
const factory ProxyProvider({required String name}) = _ProxyProvider;
factory ProxyProvider.fromJson(Map<String, Object?> json) =>
_$ProxyProviderFromJson(json);
}
@freezed
abstract class Sniffer with _$Sniffer {
const factory Sniffer({
@@ -211,7 +248,7 @@ abstract class FallbackFilter with _$FallbackFilter {
const factory FallbackFilter({
@Default(true) bool geoip,
@Default('CN') @JsonKey(name: 'geoip-code') String geoipCode,
@Default(['gfw']) List<String> geosite,
@Default([]) List<String> geosite,
@Default(['240.0.0.0/4']) List<String> ipcidr,
@Default(['+.google.com', '+.facebook.com', '+.youtube.com'])
List<String> domain,
@@ -309,21 +346,48 @@ abstract class GeoXUrl with _$GeoXUrl {
}
@freezed
abstract class ParsedRule with _$ParsedRule {
const factory ParsedRule({
required RuleAction ruleAction,
abstract class Rule with _$Rule {
const factory Rule({
@Default(-1) int id,
@Default(RuleAction.DOMAIN) RuleAction ruleAction,
String? content,
String? ruleTarget,
String? ruleProvider,
String? subRule,
@Default(false) bool noResolve,
@Default(false) bool src,
}) = _ParsedRule;
String? order,
}) = _Rule;
factory ParsedRule.parseString(String value) {
// factory Rule.parseString(String? value) {
// return Rule.parse(Rule.value(value ?? ''));
// }
factory Rule.init() {
return Rule(
ruleAction: RuleAction.DOMAIN,
ruleTarget: RuleTarget.DIRECT.name,
);
}
factory Rule.parse(String value, {int? id}) {
id ??= snowflake.id;
if (value.isEmpty) {
return Rule(
id: id,
ruleAction: RuleAction.DOMAIN,
ruleTarget: RuleTarget.DIRECT.name,
);
}
final splits = value.split(',');
final shortSplits = splits
.where((item) => !item.contains('src') && !item.contains('no-resolve'))
.where(
(item) =>
!item.contains('src') &&
!item.contains('no-resolve') &&
item.isNotEmpty,
)
.map((item) => item.trim())
.toList();
final ruleAction = RuleAction.values.firstWhere(
(item) => item.value == shortSplits.first,
@@ -342,12 +406,13 @@ abstract class ParsedRule with _$ParsedRule {
String? ruleProvider;
if (ruleAction == RuleAction.RULE_SET) {
ruleProvider = shortSplits.sublist(1, shortSplits.length - 1).join(',');
ruleProvider = shortSplits[1];
} else {
content = shortSplits.sublist(1, shortSplits.length - 1).join(',');
content = shortSplits[1];
}
return ParsedRule(
return Rule(
id: id,
ruleAction: ruleAction,
content: content,
src: splits.contains('src'),
@@ -357,14 +422,44 @@ abstract class ParsedRule with _$ParsedRule {
ruleTarget: ruleTarget,
);
}
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
}
extension ParsedRuleExt on ParsedRule {
String get value {
extension RuleExt on Rule {
Rule autoOrder(Rule rule, String? a, String? b) {
final newRule = rule.order?.isNotEmpty != true
? rule.copyWith(order: indexing.generateKeyBetween(a, b))
: rule;
return newRule;
}
String? get realContent {
return switch (ruleAction == RuleAction.RULE_SET) {
true => ruleProvider,
false => content,
};
}
String? get realTarget {
return switch (ruleAction == RuleAction.SUB_RULE) {
true => subRule,
false => ruleTarget,
};
}
String? targetErrorTip(String invalidSubRuleTip, String invalidPolicyTip) {
return switch (ruleAction == RuleAction.SUB_RULE) {
true => invalidSubRuleTip,
false => invalidPolicyTip,
};
}
String get rawValue {
return [
ruleAction.value,
ruleAction == RuleAction.RULE_SET ? ruleProvider : content,
ruleAction == RuleAction.SUB_RULE ? subRule : ruleTarget,
realContent,
realTarget,
if (ruleAction.hasParams) ...[
if (src) 'src',
if (noResolve) 'no-resolve',
@@ -373,74 +468,83 @@ extension ParsedRuleExt on ParsedRule {
}
}
@freezed
abstract class Rule with _$Rule {
const factory Rule({required int id, required String value, String? order}) =
_Rule;
// @freezed
// abstract class Rule with _$Rule {
// const factory Rule({required int id, required String value, String? order}) =
// _Rule;
//
// factory Rule.value(String value) {
// return Rule(value: value, id: snowflake.id);
// }
//
// factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
// }
//
// extension RulesExt on List<Rule> {
// List<Rule> copyAndPut(Rule rule) {
// var newList = List<Rule>.from(this);
// final index = newList.indexWhere((item) => item.id == rule.id);
// if (index != -1) {
// rule = newList[index] = rule;
// } else {
// newList.insert(0, rule);
// }
// return newList;
// }
// }
factory Rule.value(String value) {
return Rule(value: value, id: snowflake.id);
}
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
}
extension RulesExt on List<Rule> {
List<Rule> copyAndPut(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;
factory SubRule.fromJson(Map<String, Object?> json) =>
_$SubRuleFromJson(json);
}
List<Rule> _genRule(List<dynamic>? rules) {
// @freezed
// abstract class SubRule with _$SubRule {
// const factory SubRule({required String name}) = _SubRule;
//
// factory SubRule.fromJson(Map<String, Object?> json) =>
// _$SubRuleFromJson(json);
// }
//
List<Rule> _genRules(List<dynamic>? rules) {
if (rules == null) {
return [];
}
return rules.map((item) => Rule.value(item)).toList();
return rules.map((item) => Rule.parse(item)).toList();
}
List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
}
// List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
// return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
// }
//
// List<SubRule> _genSubRules(Map<String, dynamic> json) {
// return json.entries.map((entry) => SubRule(name: entry.key)).toList();
// }
List<SubRule> _genSubRules(Map<String, dynamic> json) {
return json.entries.map((entry) => SubRule(name: entry.key)).toList();
}
@freezed
abstract class ClashConfigSnippet with _$ClashConfigSnippet {
const factory ClashConfigSnippet({
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRule, name: 'rules') @Default([]) List<Rule> rule,
@JsonKey(name: 'rule-providers', fromJson: _genRuleProviders)
@Default([])
List<RuleProvider> ruleProvider,
@JsonKey(name: 'sub-rules', fromJson: _genSubRules)
@Default([])
List<SubRule> subRules,
}) = _ClashConfigSnippet;
factory ClashConfigSnippet.fromJson(Map<String, Object?> json) =>
_$ClashConfigSnippetFromJson(json);
List<String> _genList(Map<String, dynamic> json) {
return json.entries.map((entry) => entry.key).toList();
}
@freezed
abstract class ClashConfig with _$ClashConfig {
const factory ClashConfig({
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRules) @Default([]) List<Rule> rules,
@Default([]) List<Proxy> proxies,
@JsonKey(name: 'proxy-providers', fromJson: _genList)
@Default([])
List<String> proxyProviders,
@JsonKey(name: 'rule-providers', fromJson: _genList)
@Default([])
List<String> ruleProviders,
@JsonKey(name: 'sub-rules', fromJson: _genList)
@Default([])
List<String> subRules,
@Default({}) Map<String, String> proxyTypeMap,
}) = _ClashConfig;
factory ClashConfig.fromJson(Map<String, Object?> json) =>
_$ClashConfigFromJson(json);
}
@freezed
abstract class PatchClashConfig with _$PatchClashConfig {
const factory PatchClashConfig({
@Default(defaultMixedPort) @JsonKey(name: 'mixed-port') int mixedPort,
@Default(0) @JsonKey(name: 'socks-port') int socksPort,
@Default(0) @JsonKey(name: 'port') int port,
@@ -469,24 +573,22 @@ abstract class ClashConfig with _$ClashConfig {
@Default(GeodataLoader.memconservative)
@JsonKey(name: 'geodata-loader')
GeodataLoader geodataLoader,
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@Default([]) List<String> rule,
@JsonKey(name: 'global-ua') String? globalUa,
@Default(ExternalControllerStatus.close)
@JsonKey(name: 'external-controller')
ExternalControllerStatus externalController,
@Default({}) Map<String, String> hosts,
}) = _ClashConfig;
}) = _PatchClashConfig;
factory ClashConfig.fromJson(Map<String, Object?> json) =>
_$ClashConfigFromJson(json);
factory PatchClashConfig.fromJson(Map<String, Object?> json) =>
_$PatchClashConfigFromJson(json);
factory ClashConfig.safeFormJson(Map<String, Object?>? json) {
factory PatchClashConfig.safeFormJson(Map<String, Object?>? json) {
if (json == null) {
return defaultClashConfig;
}
try {
return ClashConfig.fromJson(json);
return PatchClashConfig.fromJson(json);
} catch (_) {
return defaultClashConfig;
}
+18 -19
View File
@@ -6,6 +6,8 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'clash_config.dart';
part 'generated/common.freezed.dart';
part 'generated/common.g.dart';
@@ -236,8 +238,8 @@ abstract class FileInfo with _$FileInfo {
}
extension FileInfoExt on FileInfo {
String get desc =>
'${size.traffic.show} · ${lastModified.lastUpdateTimeDesc}';
String getDesc(BuildContext context) =>
'${size.traffic.show} · ${lastModified.getLastUpdateTimeDesc(context)}';
}
@freezed
@@ -285,21 +287,10 @@ extension TrafficShowExt on TrafficShow {
String get show => '$value$unit';
}
@freezed
abstract class Proxy with _$Proxy {
const factory Proxy({
required String name,
required String type,
String? now,
}) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
}
@freezed
abstract class Group with _$Group {
const factory Group({
required GroupType type,
@JsonKey(fromJson: GroupType.parse) required GroupType type,
@Default([]) List<Proxy> all,
String? now,
bool? hidden,
@@ -350,7 +341,7 @@ extension ColorSchemesExt on ColorSchemes {
dynamicSchemeVariant: schemeVariant,
)
: ColorScheme.fromSeed(
seedColor: Color(defaultPrimaryColor),
seedColor: const Color(defaultPrimaryColor),
brightness: Brightness.dark,
dynamicSchemeVariant: schemeVariant,
);
@@ -361,7 +352,7 @@ extension ColorSchemesExt on ColorSchemes {
dynamicSchemeVariant: schemeVariant,
)
: ColorScheme.fromSeed(
seedColor: Color(defaultPrimaryColor),
seedColor: const Color(defaultPrimaryColor),
dynamicSchemeVariant: schemeVariant,
);
}
@@ -534,7 +525,7 @@ extension ScriptsExt on List<Script> {
extension ScriptExt on Script {
String get fileName => '$id.js';
Future<String> get path async => await appPath.getScriptPath(id.toString());
Future<String> get path async => appPath.getScriptPath(id.toString());
Future<String?> get content async {
final file = File(await path);
@@ -583,8 +574,8 @@ extension DelayStateExt on DelayState {
if (delay != other.delay) {
return delay.compareTo(other.delay);
}
if (group && !group) return -1;
if (!group && group) return 1;
if (group && !other.group) return -1;
if (!group && other.group) return 1;
return 0;
}
}
@@ -596,3 +587,11 @@ abstract class UpdatingMessage with _$UpdatingMessage {
required String message,
}) = _UpdatingMessage;
}
@freezed
abstract class IconButtonData with _$IconButtonData {
const factory IconButtonData({
required IconData icon,
required VoidCallback onPressed,
}) = _IconButtonData;
}
+12 -7
View File
@@ -34,7 +34,7 @@ const defaultNetworkProps = NetworkProps();
const defaultProxiesStyleProps = ProxiesStyleProps();
const defaultWindowProps = WindowProps();
const defaultAccessControlProps = AccessControlProps();
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
const defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
const List<DashboardWidget> defaultDashboardWidgets = [
DashboardWidget.networkSpeed,
@@ -90,9 +90,13 @@ abstract class AppSettingProps with _$AppSettingProps {
_$AppSettingPropsFromJson(json);
factory AppSettingProps.safeFromJson(Map<String, Object?>? json) {
return json == null
? defaultAppSettingProps
: AppSettingProps.fromJson(json);
try {
return json == null
? defaultAppSettingProps
: AppSettingProps.fromJson(json);
} catch (_) {
return defaultAppSettingProps;
}
}
}
@@ -140,7 +144,7 @@ abstract class WindowProps with _$WindowProps {
extension WindowPropsExt on WindowProps {
Size get _size => Size(width, height);
Size get size => _size.isEmpty ? Size(680, 580) : _size;
Size get size => _size.isEmpty ? const Size(680, 580) : _size;
}
@freezed
@@ -239,14 +243,15 @@ abstract class Config with _$Config {
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
@Default(defaultProxiesStyleProps) ProxiesStyleProps proxiesStyleProps,
@Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default(defaultClashConfig) PatchClashConfig patchClashConfig,
@Default([]) List<String> excludeSSIDs,
}) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);
factory Config.realFromJson(Map<String, Object?>? json) {
if (json == null) {
return Config(themeProps: defaultThemeProps);
return const Config(themeProps: defaultThemeProps);
}
return _$ConfigFromJson(json);
}
File diff suppressed because it is too large Load Diff
+171 -90
View File
@@ -7,50 +7,76 @@ part of '../clash_config.dart';
// **************************************************************************
_ProxyGroup _$ProxyGroupFromJson(Map<String, dynamic> json) => _ProxyGroup(
profileId: (json['profileId'] as num?)?.toInt(),
id: Snowflake.buildId((json['id'] as num?)?.toInt()),
name: json['name'] as String,
type: GroupType.parseProfileType(json['type'] as String),
type: $enumDecode(_$GroupTypeEnumMap, json['type']),
proxies: (json['proxies'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
use: (json['use'] as List<dynamic>?)?.map((e) => e as String).toList(),
interval: (json['interval'] as num?)?.toInt(),
lazy: json['lazy'] as bool?,
disableUDP: json['disable-udp'] as bool?,
url: json['url'] as String?,
timeout: (json['timeout'] as num?)?.toInt(),
maxFailedTimes: (json['max-failed-times'] as num?)?.toInt(),
filter: json['filter'] as String?,
excludeFilter: json['expected-filter'] as String?,
excludeFilter: json['exclude-filter'] as String?,
excludeType: json['exclude-type'] as String?,
expectedStatus: json['expected-status'],
expectedStatus: json['expected-status'] as String?,
includeAll: json['include-all'] as bool?,
includeAllProxies: json['include-all-proxies'] as bool?,
includeAllProviders: json['include-all-providers'] as bool?,
hidden: json['hidden'] as bool?,
icon: json['icon'] as String?,
order: json['order'] as String?,
);
Map<String, dynamic> _$ProxyGroupToJson(_ProxyGroup instance) =>
<String, dynamic>{
'profileId': instance.profileId,
'id': instance.id,
'name': instance.name,
'type': _$GroupTypeEnumMap[instance.type]!,
'proxies': instance.proxies,
'use': instance.use,
'interval': instance.interval,
'lazy': instance.lazy,
'disable-udp': instance.disableUDP,
'url': instance.url,
'timeout': instance.timeout,
'max-failed-times': instance.maxFailedTimes,
'filter': instance.filter,
'expected-filter': instance.excludeFilter,
'exclude-filter': instance.excludeFilter,
'exclude-type': instance.excludeType,
'expected-status': instance.expectedStatus,
'include-all': instance.includeAll,
'include-all-proxies': instance.includeAllProxies,
'include-all-providers': instance.includeAllProviders,
'hidden': instance.hidden,
'icon': instance.icon,
'order': instance.order,
};
const _$GroupTypeEnumMap = {
GroupType.Selector: 'Selector',
GroupType.URLTest: 'URLTest',
GroupType.Fallback: 'Fallback',
GroupType.LoadBalance: 'LoadBalance',
GroupType.Relay: 'Relay',
GroupType.Selector: 'select',
GroupType.URLTest: 'url-test',
GroupType.Fallback: 'fallback',
GroupType.LoadBalance: 'load-balance',
GroupType.Relay: 'relay',
};
_Proxy _$ProxyFromJson(Map<String, dynamic> json) => _Proxy(
name: json['name'] as String,
type: json['type'] as String,
now: json['now'] as String?,
);
Map<String, dynamic> _$ProxyToJson(_Proxy instance) => <String, dynamic>{
'name': instance.name,
'type': instance.type,
'now': instance.now,
};
_RuleProvider _$RuleProviderFromJson(Map<String, dynamic> json) =>
@@ -59,6 +85,12 @@ _RuleProvider _$RuleProviderFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$RuleProviderToJson(_RuleProvider instance) =>
<String, dynamic>{'name': instance.name};
_ProxyProvider _$ProxyProviderFromJson(Map<String, dynamic> json) =>
_ProxyProvider(name: json['name'] as String);
Map<String, dynamic> _$ProxyProviderToJson(_ProxyProvider instance) =>
<String, dynamic>{'name': instance.name};
_Sniffer _$SnifferFromJson(Map<String, dynamic> json) => _Sniffer(
enable: json['enable'] as bool? ?? false,
overrideDest: json['override-destination'] as bool? ?? true,
@@ -168,7 +200,7 @@ _FallbackFilter _$FallbackFilterFromJson(
geoipCode: json['geoip-code'] as String? ?? 'CN',
geosite:
(json['geosite'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const ['gfw'],
const [],
ipcidr:
(json['ipcidr'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const ['240.0.0.0/4'],
@@ -286,107 +318,158 @@ Map<String, dynamic> _$GeoXUrlToJson(_GeoXUrl instance) => <String, dynamic>{
};
_Rule _$RuleFromJson(Map<String, dynamic> json) => _Rule(
id: (json['id'] as num).toInt(),
value: json['value'] as String,
id: (json['id'] as num?)?.toInt() ?? -1,
ruleAction:
$enumDecodeNullable(_$RuleActionEnumMap, json['ruleAction']) ??
RuleAction.DOMAIN,
content: json['content'] as String?,
ruleTarget: json['ruleTarget'] as String?,
ruleProvider: json['ruleProvider'] as String?,
subRule: json['subRule'] as String?,
noResolve: json['noResolve'] as bool? ?? false,
src: json['src'] as bool? ?? false,
order: json['order'] as String?,
);
Map<String, dynamic> _$RuleToJson(_Rule instance) => <String, dynamic>{
'id': instance.id,
'value': instance.value,
'ruleAction': _$RuleActionEnumMap[instance.ruleAction]!,
'content': instance.content,
'ruleTarget': instance.ruleTarget,
'ruleProvider': instance.ruleProvider,
'subRule': instance.subRule,
'noResolve': instance.noResolve,
'src': instance.src,
'order': instance.order,
};
_SubRule _$SubRuleFromJson(Map<String, dynamic> json) =>
_SubRule(name: json['name'] as String);
Map<String, dynamic> _$SubRuleToJson(_SubRule instance) => <String, dynamic>{
'name': instance.name,
const _$RuleActionEnumMap = {
RuleAction.DOMAIN: 'DOMAIN',
RuleAction.DOMAIN_SUFFIX: 'DOMAIN_SUFFIX',
RuleAction.DOMAIN_KEYWORD: 'DOMAIN_KEYWORD',
RuleAction.DOMAIN_REGEX: 'DOMAIN_REGEX',
RuleAction.GEOSITE: 'GEOSITE',
RuleAction.IP_CIDR: 'IP_CIDR',
RuleAction.IP_CIDR6: 'IP_CIDR6',
RuleAction.IP_SUFFIX: 'IP_SUFFIX',
RuleAction.IP_ASN: 'IP_ASN',
RuleAction.GEOIP: 'GEOIP',
RuleAction.SRC_GEOIP: 'SRC_GEOIP',
RuleAction.SRC_IP_ASN: 'SRC_IP_ASN',
RuleAction.SRC_IP_CIDR: 'SRC_IP_CIDR',
RuleAction.SRC_IP_SUFFIX: 'SRC_IP_SUFFIX',
RuleAction.DST_PORT: 'DST_PORT',
RuleAction.SRC_PORT: 'SRC_PORT',
RuleAction.IN_PORT: 'IN_PORT',
RuleAction.IN_TYPE: 'IN_TYPE',
RuleAction.IN_USER: 'IN_USER',
RuleAction.IN_NAME: 'IN_NAME',
RuleAction.PROCESS_PATH: 'PROCESS_PATH',
RuleAction.PROCESS_PATH_REGEX: 'PROCESS_PATH_REGEX',
RuleAction.PROCESS_NAME: 'PROCESS_NAME',
RuleAction.PROCESS_NAME_REGEX: 'PROCESS_NAME_REGEX',
RuleAction.UID: 'UID',
RuleAction.NETWORK: 'NETWORK',
RuleAction.DSCP: 'DSCP',
RuleAction.RULE_SET: 'RULE_SET',
RuleAction.AND: 'AND',
RuleAction.OR: 'OR',
RuleAction.NOT: 'NOT',
RuleAction.SUB_RULE: 'SUB_RULE',
RuleAction.MATCH: 'MATCH',
};
_ClashConfigSnippet _$ClashConfigSnippetFromJson(Map<String, dynamic> json) =>
_ClashConfigSnippet(
proxyGroups:
(json['proxy-groups'] as List<dynamic>?)
?.map((e) => ProxyGroup.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
rule: json['rules'] == null ? const [] : _genRule(json['rules'] as List?),
ruleProvider: json['rule-providers'] == null
? const []
: _genRuleProviders(json['rule-providers'] as Map<String, dynamic>),
subRules: json['sub-rules'] == null
? const []
: _genSubRules(json['sub-rules'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ClashConfigSnippetToJson(_ClashConfigSnippet instance) =>
<String, dynamic>{
'proxy-groups': instance.proxyGroups,
'rules': instance.rule,
'rule-providers': instance.ruleProvider,
'sub-rules': instance.subRules,
};
_ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => _ClashConfig(
mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort,
socksPort: (json['socks-port'] as num?)?.toInt() ?? 0,
port: (json['port'] as num?)?.toInt() ?? 0,
redirPort: (json['redir-port'] as num?)?.toInt() ?? 0,
tproxyPort: (json['tproxy-port'] as num?)?.toInt() ?? 0,
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false,
logLevel:
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
LogLevel.error,
ipv6: json['ipv6'] as bool? ?? false,
findProcessMode:
$enumDecodeNullable(
_$FindProcessModeEnumMap,
json['find-process-mode'],
unknownValue: FindProcessMode.always,
) ??
FindProcessMode.always,
keepAliveInterval:
(json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval,
unifiedDelay: json['unified-delay'] as bool? ?? true,
tcpConcurrent: json['tcp-concurrent'] as bool? ?? true,
tun: json['tun'] == null
? defaultTun
: Tun.safeFormJson(json['tun'] as Map<String, Object?>?),
dns: json['dns'] == null
? defaultDns
: Dns.safeDnsFromJson(json['dns'] as Map<String, Object?>),
geoXUrl: json['geox-url'] == null
? defaultGeoXUrl
: GeoXUrl.safeFormJson(json['geox-url'] as Map<String, Object?>?),
geodataLoader:
$enumDecodeNullable(_$GeodataLoaderEnumMap, json['geodata-loader']) ??
GeodataLoader.memconservative,
proxyGroups:
(json['proxy-groups'] as List<dynamic>?)
?.map((e) => ProxyGroup.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
rule:
(json['rule'] as List<dynamic>?)?.map((e) => e as String).toList() ??
rules: json['rules'] == null ? const [] : _genRules(json['rules'] as List?),
proxies:
(json['proxies'] as List<dynamic>?)
?.map((e) => Proxy.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
globalUa: json['global-ua'] as String?,
externalController:
$enumDecodeNullable(
_$ExternalControllerStatusEnumMap,
json['external-controller'],
) ??
ExternalControllerStatus.close,
hosts:
(json['hosts'] as Map<String, dynamic>?)?.map(
proxyProviders: json['proxy-providers'] == null
? const []
: _genList(json['proxy-providers'] as Map<String, dynamic>),
ruleProviders: json['rule-providers'] == null
? const []
: _genList(json['rule-providers'] as Map<String, dynamic>),
subRules: json['sub-rules'] == null
? const []
: _genList(json['sub-rules'] as Map<String, dynamic>),
proxyTypeMap:
(json['proxyTypeMap'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
);
Map<String, dynamic> _$ClashConfigToJson(_ClashConfig instance) =>
<String, dynamic>{
'proxy-groups': instance.proxyGroups,
'rules': instance.rules,
'proxies': instance.proxies,
'proxy-providers': instance.proxyProviders,
'rule-providers': instance.ruleProviders,
'sub-rules': instance.subRules,
'proxyTypeMap': instance.proxyTypeMap,
};
_PatchClashConfig _$PatchClashConfigFromJson(Map<String, dynamic> json) =>
_PatchClashConfig(
mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort,
socksPort: (json['socks-port'] as num?)?.toInt() ?? 0,
port: (json['port'] as num?)?.toInt() ?? 0,
redirPort: (json['redir-port'] as num?)?.toInt() ?? 0,
tproxyPort: (json['tproxy-port'] as num?)?.toInt() ?? 0,
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false,
logLevel:
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
LogLevel.error,
ipv6: json['ipv6'] as bool? ?? false,
findProcessMode:
$enumDecodeNullable(
_$FindProcessModeEnumMap,
json['find-process-mode'],
unknownValue: FindProcessMode.always,
) ??
FindProcessMode.always,
keepAliveInterval:
(json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval,
unifiedDelay: json['unified-delay'] as bool? ?? true,
tcpConcurrent: json['tcp-concurrent'] as bool? ?? true,
tun: json['tun'] == null
? defaultTun
: Tun.safeFormJson(json['tun'] as Map<String, Object?>?),
dns: json['dns'] == null
? defaultDns
: Dns.safeDnsFromJson(json['dns'] as Map<String, Object?>),
geoXUrl: json['geox-url'] == null
? defaultGeoXUrl
: GeoXUrl.safeFormJson(json['geox-url'] as Map<String, Object?>?),
geodataLoader:
$enumDecodeNullable(_$GeodataLoaderEnumMap, json['geodata-loader']) ??
GeodataLoader.memconservative,
globalUa: json['global-ua'] as String?,
externalController:
$enumDecodeNullable(
_$ExternalControllerStatusEnumMap,
json['external-controller'],
) ??
ExternalControllerStatus.close,
hosts:
(json['hosts'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
);
Map<String, dynamic> _$PatchClashConfigToJson(_PatchClashConfig instance) =>
<String, dynamic>{
'mixed-port': instance.mixedPort,
'socks-port': instance.socksPort,
@@ -405,8 +488,6 @@ Map<String, dynamic> _$ClashConfigToJson(_ClashConfig instance) =>
'dns': instance.dns,
'geox-url': instance.geoXUrl,
'geodata-loader': _$GeodataLoaderEnumMap[instance.geodataLoader]!,
'proxy-groups': instance.proxyGroups,
'rule': instance.rule,
'global-ua': instance.globalUa,
'external-controller':
_$ExternalControllerStatusEnumMap[instance.externalController]!,

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