diff --git a/.agents/agent-config.md b/.agents/agent-config.md
new file mode 100644
index 00000000..bcd0b158
--- /dev/null
+++ b/.agents/agent-config.md
@@ -0,0 +1,28 @@
+# Agent Configuration Model
+
+Use the smallest durable surface that matches the job.
+
+## Surfaces
+
+- `AGENTS.md`: auto-loaded repository entry point. Keep it small and reserve it for always-on rules, routing, and high-priority expectations.
+- `.agents/*.md`: human- and agent-readable reference docs linked from `AGENTS.md`. Use these for detailed project context, commands, architecture, and conventions.
+- `.agents/skills/*/SKILL.md`: repo-scoped Codex skills. Use these for repeatable workflows that should be discoverable by name and description.
+- `.codex/config.toml`: trusted project Codex settings such as MCP, hooks, sandbox, approval, or model defaults.
+- `.codex/rules/*.rules`: command permission rules for running commands outside the sandbox. Do not mix these with coding conventions in `.agents/rules.md`.
+- `.codex/hooks.json` or `.codex/config.toml` hooks: lifecycle automation such as prompt checks, command validation, or final verification gates.
+
+## Placement Rules
+
+- Put stable team conventions in `AGENTS.md` only when every task must see them without opening another file.
+- Put detailed explanations in `.agents/*.md` and link them from `AGENTS.md`.
+- Put reusable task workflows in `.agents/skills//SKILL.md`.
+- Put mechanical enforcement in linters, tests, hooks, or `.codex/rules`; do not rely on prose when tooling can enforce the rule.
+- Keep user-specific preferences out of the repository. They belong in user-level Codex configuration or user-level skills.
+
+## Skill Authoring Rules
+
+- Use lowercase hyphenated names.
+- Start descriptions with `Use when...`.
+- Describe trigger conditions in the description, not the workflow.
+- Keep `SKILL.md` lean; link to `.agents/*.md` for large reference material.
+- Add scripts only when deterministic behavior is needed repeatedly.
diff --git a/.agents/architecture.md b/.agents/architecture.md
new file mode 100644
index 00000000..dc541683
--- /dev/null
+++ b/.agents/architecture.md
@@ -0,0 +1,156 @@
+# Architecture
+
+## Core Integration
+
+The Go proxy core in `core/` operates in two modes.
+
+Android lib mode:
+
+- Go core is compiled as a C shared library, `libclash.so`, through `go build -buildmode=c-shared` with CGO.
+- Flutter calls it via FFI through the `service` plugin.
+- Dart-side implementation: `lib/core/lib.dart` (`CoreLib`).
+
+Desktop core mode:
+
+- Go core runs as a separate process with `CGO_ENABLED=0`.
+- Flutter communicates via JSON over socket, using a Unix socket on macOS/Linux and TCP on Windows.
+- Dart-side implementation: `lib/core/service.dart` (`CoreService`).
+
+`lib/core/controller.dart` (`CoreController`) selects the implementation based on platform. `lib/core/interface.dart` defines the shared `CoreHandlerInterface`.
+
+Key Go core files:
+
+- `core/hub.go`: handler functions.
+- `core/action.go`: dispatch.
+- `core/lib.go`: CGO exports.
+- `core/server.go`: socket server.
+
+## State Management
+
+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` in `lib/state.dart` is a singleton holding app lifecycle, timers, theme, and start/stop state. Providers are generated into `lib/providers/generated/`.
+
+## Database
+
+The app uses Drift/SQLite in `lib/database/`. Current schema version is 2.
+
+Tables:
+
+- `Profiles`
+- `Scripts`
+- `Rules`
+- `ProfileRuleLinks` (`profile_rule_mapping`)
+- `ProxyGroups`
+- `IconRecords` (`icon_records`)
+
+Rule scenes distinguish global added rules, profile added rules, profile custom rules, and disabled links. Rule and proxy-group ordering use fractional indexing.
+
+Generated Drift output lives in `lib/database/generated/database.g.dart`. After schema changes, run code generation and add or update focused database tests under `test/database/` when converter or migration behavior changes.
+
+## Manager Stack
+
+Managers are nested `InheritedWidget`/`StatefulWidget` components in `lib/application.dart`:
+
+```text
+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 and Actions
+
+`lib/core/controller.dart` (`CoreController`) is a singleton facade over `CoreHandlerInterface`. Public methods delegate to the platform-specific interface, either Android FFI or desktop socket. It has an `@visibleForTesting` constructor and `resetInstance()` for test injection.
+
+Business logic lives in Riverpod notifier classes in `lib/providers/action.dart`:
+
+- `CommonAction`: update check and common UI operations.
+- `SetupAction`: config setup and 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 and proxy selection.
+- `ProfilesAction`: profile CRUD, auto-update, import.
+
+## Platform Managers
+
+Desktop:
+
+- `WindowManager`
+- `TrayManager`
+- `HotKeyManager`
+- `ProxyManager`
+
+Mobile:
+
+- `AndroidManager`
+- `TileManager`
+- `VpnManager`
+
+Shared:
+
+- `ConnectivityManager`
+- `CoreManager`
+- `AppStateManager`
+- `StatusManager`
+- `ThemeManager`
+
+## Build System
+
+`setup.dart` is the release build orchestrator:
+
+1. On Windows, pre-builds Go core via `dart run build_tool windows` and reads `core_sha256.json`.
+2. Writes `env.json` (`APP_ENV`).
+3. Passes SHA256 as `--dart-define=CORE_SHA256=$val`, embedded at compile time for Windows.
+4. Activates `flutter_distributor` for packaging.
+
+Go core building is handled by `build_tool`, a standalone Dart CLI in `plugins/setup/buildkit/build_tool/`.
+
+Platform build hooks inside `flutter build` trigger `build_tool` automatically:
+
+- macOS: podspec script phase, `build_pod.sh`, `build_tool macos`.
+- Linux: CMake include, `buildkit/cmake/buildkit.cmake`, `build_tool linux`.
+- Windows: CMake include, `buildkit/cmake/buildkit.cmake`, `build_tool windows`. Debug passes `--dev` via `CMAKE_BUILD_TYPE`.
+- Android: Gradle include, `buildkit/gradle/plugin.gradle`, `build_tool android`.
+
+Windows helper auth:
+
+- Release: Core SHA256 is embedded in both the Flutter app and the Rust helper. The app pings the helper and verifies the token matches.
+- Debug: The Rust helper skips token verification when built in debug mode, so `flutter run` works without the SHA256 flow.
+
+`plugins/setup/` is an FFI plugin that exists only as a build harness. It carries no Dart API, only platform build hooks that trigger Go compilation. Windows builds also compile a Rust helper in `services/helper/` through `RustBuilder`.
+
+Build configuration defaults live in `build_tool/lib/src/options.dart` and can be overridden via `build_config.yaml`.
+
+Architecture detection is automatic. The `--description` flag passed to `flutter_distributor` adds arch suffixes to artifact names, such as `FlClash-0.8.93-macos-arm64.dmg`.
+
+## Local Plugins
+
+- `setup`: build harness FFI plugin.
+- `proxy`: system proxy configuration.
+- `rust_api`: Flutter Rust Bridge FFI plugin.
+- `tray_manager`: system tray fork/customization.
+- `wifi_ssid`: Wi-Fi SSID detection.
+- `window_ext`: window extensions.
+- `flutter_distributor`: app packaging/distribution.
+
+## Rust Helper Service
+
+`services/helper/` is a Windows-only privileged helper for starting the core as admin and managing TUN. It is built with:
+
+```bash
+cargo build --release --features windows-service
+```
+
+It uses token-based auth with the Flutter app.
diff --git a/.agents/commands.md b/.agents/commands.md
new file mode 100644
index 00000000..6de8c960
--- /dev/null
+++ b/.agents/commands.md
@@ -0,0 +1,106 @@
+# Commands
+
+## Building
+
+Update submodules first. The ClashMeta Go core lives in `core/Clash.Meta/`.
+
+```bash
+git submodule update --init --recursive
+```
+
+Full package build, including Go core, Flutter, and packaging, runs through `setup.dart`:
+
+```bash
+dart setup.dart macos
+dart setup.dart linux
+dart setup.dart windows
+dart setup.dart android
+```
+
+Build only the Go core and skip Flutter packaging:
+
+```bash
+make core-macos
+make core-linux
+make core-windows
+make core-android
+```
+
+Pass `ARCH` or `TARGET_PLATFORM` through `make` when needed, for example:
+
+```bash
+make core-macos ARCH=arm64
+make core-android TARGET_PLATFORM=android-arm64
+```
+
+The Makefile wraps `plugins/setup/buildkit/run_build_tool.sh`; prefer the `make` entry points unless debugging the build tool itself.
+
+## Flutter Development
+
+The project is pinned with FVM.
+
+```bash
+fvm flutter pub get
+fvm flutter run
+fvm flutter test
+```
+
+Plain Flutter also works when the global SDK matches project constraints:
+
+```bash
+flutter pub get
+flutter run
+flutter test
+```
+
+Use `flutter test`, not `dart test`, because models pull in Flutter types.
+
+## Code Generation
+
+Run code generation after modifying models, providers, or database schema:
+
+```bash
+dart run build_runner build --delete-conflicting-outputs
+dart run build_runner watch
+```
+
+Code generation covers:
+
+- Riverpod providers through `riverpod_generator`.
+- Models through `freezed` and `json_serializable`.
+- Database tables through `drift_dev`.
+
+Generated output paths, configured in `build.yaml`:
+
+- `lib/models/generated/*.g.dart`, `*.freezed.dart`.
+- `lib/providers/generated/*.g.dart`.
+- `lib/database/generated/*.g.dart`.
+
+## Testing
+
+Tests use `package:test/test.dart` for pure Dart logic and `flutter_test` for provider and widget tests. `mocktail` is the mocking framework.
+
+```bash
+flutter test test/models/
+flutter test test/core/
+flutter test test/providers/
+flutter test test/common/
+flutter test test/database/
+flutter test test/widgets/
+flutter test test/setup_test.dart
+flutter test plugins/proxy/test/proxy_test.dart
+```
+
+Root `flutter test` only discovers the root package's `test/` directory by default. Include bundled plugin Dart tests by passing paths explicitly, or run `flutter test` from that plugin package directory. Native plugin tests under platform folders are not run by `flutter test`.
+
+## Verify
+
+CI runs these in order:
+
+```bash
+flutter pub get
+flutter analyze --no-fatal-infos
+flutter test --reporter expanded
+```
+
+Run `flutter analyze` locally before committing when practical.
diff --git a/.agents/project.md b/.agents/project.md
new file mode 100644
index 00000000..9ca9529f
--- /dev/null
+++ b/.agents/project.md
@@ -0,0 +1,28 @@
+# Project Context
+
+FlClash is a multi-platform proxy client based on ClashMeta (mihomo), built with Flutter. It supports Android, Windows, macOS, and Linux, using a Material You design with Surfboard-like UI.
+
+## Version Notes
+
+- `.fvmrc` pins Flutter 3.35.7 for local development.
+- CI uses Flutter 3.41.9. These may diverge; trust CI as the source of truth for release builds.
+- Dart SDK constraint: `>=3.8.0 <4.0.0`.
+
+## Build Dependencies
+
+Linux:
+
+```bash
+sudo apt-get install libayatana-appindicator3-dev libkeybinder-3.0-dev
+```
+
+Windows:
+
+- GCC and Inno Setup.
+- `ANDROID_NDK` env var for Android builds.
+
+macOS:
+
+```bash
+npm install -g appdmg
+```
diff --git a/.agents/rules.md b/.agents/rules.md
new file mode 100644
index 00000000..fef46309
--- /dev/null
+++ b/.agents/rules.md
@@ -0,0 +1,47 @@
+# Rules
+
+These are repository coding and testing conventions. Codex command permission rules belong in `.codex/rules/*.rules`; see `.agents/agent-config.md` before adding those.
+
+## Dart and Flutter Style
+
+`analysis_options.yaml` enforces these non-default rules:
+
+- `prefer_single_quotes: true`: always use single quotes.
+- `require_trailing_commas: true`: use trailing commas in multi-line argument lists.
+- `sort_child_properties_last: true`: `child:` must be the last named parameter.
+- `avoid_print: true`: do not use `print()` calls.
+- `prefer_const_constructors: true` and `prefer_const_declarations: true`.
+- `prefer_final_locals: true` and `prefer_final_in_for_each: true`.
+- `always_declare_return_types: true`.
+
+Generated directories are excluded from analysis:
+
+- `build/**`
+- `lib/l10n/intl/**`
+- `lib/**/generated/**`
+- `plugins/**`
+
+## Testing Rules
+
+Use `CoreController.test(mock)` to inject a mocked `CoreHandlerInterface`. Call `CoreController.resetInstance()` in `tearDown` to clean up the singleton between tests.
+
+Register fallback values for freezed params used with `any()` matchers.
+
+Use `ProviderContainer` directly for simple Riverpod provider tests. The generated Riverpod `update()` method takes a callback:
+
+```dart
+notifier.update((state) => newValue);
+```
+
+When testing freezed models with nested objects, always round-trip through `jsonEncode` and `jsonDecode`. Direct `fromJson(toJson())` fails for nested freezed types because `toJson()` stores child objects directly instead of maps.
+
+## Generated Code
+
+Do not manually edit generated files under:
+
+- `lib/models/generated/`
+- `lib/providers/generated/`
+- `lib/database/generated/`
+- `lib/l10n/intl/`
+
+After schema, model, or provider changes, run build generation and include focused tests when behavior changes.
diff --git a/.agents/skills.md b/.agents/skills.md
new file mode 100644
index 00000000..09ad53e6
--- /dev/null
+++ b/.agents/skills.md
@@ -0,0 +1,17 @@
+# Agent Skills
+
+Repo-scoped Codex skills live under `.agents/skills/*/SKILL.md`. Codex can discover these by skill `name` and `description`, then load the full instructions only when a task matches.
+
+## Available Repo Skills
+
+- `localization`: hardcoded UI text scans, ARB updates, locale generation, and localization verification.
+- `provider-tests`: Riverpod provider, notifier, and state-management tests.
+- `ui-work`: Flutter UI, widgets, Material You styling, navigation surfaces, and user-facing interactions.
+- `core-platform`: core integration, platform managers, Go core communication, desktop/mobile behavior, and Windows helper flow.
+
+## Authoring Notes
+
+- Add new repeatable workflows as `.agents/skills//SKILL.md`.
+- Keep skill descriptions trigger-focused and start them with `Use when...`.
+- Keep long reference material in `.agents/*.md`; skills should link to it instead of duplicating it.
+- Put command permission rules in `.codex/rules/*.rules`, not in this file.
diff --git a/.agents/skills/core-platform/SKILL.md b/.agents/skills/core-platform/SKILL.md
new file mode 100644
index 00000000..02c23612
--- /dev/null
+++ b/.agents/skills/core-platform/SKILL.md
@@ -0,0 +1,33 @@
+---
+name: core-platform
+description: Use when changing FlClash core integration, platform managers, Go core communication, desktop/mobile platform behavior, or Windows helper flow.
+---
+
+# Core And Platform
+
+## When To Use
+
+Use this for changes touching `lib/core/`, `lib/manager/`, `core/`, `services/helper/`, build hooks, system proxy, tray, VPN, TUN, or platform-specific desktop/mobile behavior.
+
+## Workflow
+
+1. Identify which boundary owns the behavior:
+ - Android lib mode: `lib/core/lib.dart`.
+ - Desktop process/socket mode: `lib/core/service.dart`.
+ - Shared facade: `lib/core/controller.dart` and `lib/core/interface.dart`.
+ - Platform lifecycle: `lib/manager/`.
+2. Route feature code through `CoreController` and `CoreHandlerInterface`; avoid direct calls to platform implementations outside their boundary.
+3. Keep desktop and mobile paths explicit.
+4. For action-layer behavior, inspect `lib/providers/action.dart` and relevant generated providers.
+5. Add or update shared Dart tests for logic that can be isolated.
+6. Manually verify native behavior when automated coverage is not practical.
+
+## Reference Files
+
+Read `.agents/architecture.md` for the current core modes, manager stack, build hooks, local plugins, and Windows helper notes.
+
+## Pitfalls
+
+- Debug Windows helper auth differs from release token verification.
+- `plugins/setup/` is a build harness, not a Dart API plugin.
+- Build hooks can trigger Go or Rust compilation indirectly through Flutter platform builds.
diff --git a/.agents/skills/localization/SKILL.md b/.agents/skills/localization/SKILL.md
new file mode 100644
index 00000000..95a3e404
--- /dev/null
+++ b/.agents/skills/localization/SKILL.md
@@ -0,0 +1,45 @@
+---
+name: localization
+description: Use when changing FlClash UI text, scanning for hardcoded Chinese strings, updating ARB localization, or fixing generated locale output in this repository.
+---
+
+# Localization
+
+## When To Use
+
+Use this for localization work inside `/Users/Shared/follow/FlClash`, especially hardcoded UI text in `lib/`, ARB updates, missing translations, or generated `lib/l10n/` output that does not match source ARB values.
+
+Do not use this for README translation sync or manual edits to generated localization Dart.
+
+## Workflow
+
+1. Confirm `pubspec.yaml` still uses `flutter_intl`, source ARBs under `arb/`, and generated output under `lib/l10n/`.
+2. Scan user-facing Dart text before opening many files:
+
+ ```bash
+ rg -n "[\\p{Han}]" lib -g '!lib/l10n/intl/**' -g '!lib/**/generated/**'
+ ```
+
+3. Inspect the smallest relevant call sites and nearby ARB keys.
+4. Add or update every source ARB:
+ - `arb/intl_en.arb`
+ - `arb/intl_zh_CN.arb`
+ - `arb/intl_ja.arb`
+ - `arb/intl_ru.arb`
+5. Replace inline strings with existing project accessors:
+ - Widgets with `BuildContext`: `context.appLocalizations.key` from `common.dart`.
+ - Controllers/providers/non-widget code: `currentAppLocalizations.key` from `app_localizations.dart`.
+6. Regenerate:
+
+ ```bash
+ dart run intl_utils:generate
+ ```
+
+7. Verify changed Dart files with `flutter analyze` when practical.
+8. Re-run the Han-text scan for targeted strings.
+
+## Pitfalls
+
+- If ja/ru still show English, fix ja/ru source ARB values and regenerate. Do not edit generated Dart.
+- Ignore `lib/l10n/intl/**` and `lib/**/generated/**` during text scans.
+- If generator or analyzer hits local cache permission friction, rerun serially before treating it as a code issue.
diff --git a/.agents/skills/provider-tests/SKILL.md b/.agents/skills/provider-tests/SKILL.md
new file mode 100644
index 00000000..6b6cfb41
--- /dev/null
+++ b/.agents/skills/provider-tests/SKILL.md
@@ -0,0 +1,37 @@
+---
+name: provider-tests
+description: Use when adding or updating FlClash Riverpod provider tests, notifier tests, or state-management tests in this repository.
+---
+
+# Provider Tests
+
+## When To Use
+
+Use this for tests under `test/providers/` or any change that validates Riverpod providers, generated notifiers, app state defaults, or provider interactions.
+
+For broader test expansion, pair this with `.agents/rules.md` and `.agents/commands.md`.
+
+## Workflow
+
+1. Read the provider under test and its generated public API before writing assertions.
+2. Use `ProviderContainer` directly when no widget tree is needed.
+3. Dispose containers in teardown or with `addTearDown(container.dispose)`.
+4. Prefer generated notifier APIs over implementation details. Generated `update()` takes a callback:
+
+ ```dart
+ notifier.update((state) => newValue);
+ ```
+
+5. Mock external dependencies with `mocktail`; register fallback values for freezed params used with `any()`.
+6. Keep tests focused on behavior: defaults, state transitions, persistence boundaries, and side effects.
+7. Run the narrowest relevant test first:
+
+ ```bash
+ flutter test test/providers/
+ ```
+
+## Pitfalls
+
+- Do not use `dart test`; FlClash models and provider tests may depend on Flutter types.
+- Re-check source defaults before asserting them; provider defaults can drift.
+- If async provider timing matters, wait on provider futures or state changes instead of fixed sleeps.
diff --git a/.agents/skills/ui-work/SKILL.md b/.agents/skills/ui-work/SKILL.md
new file mode 100644
index 00000000..c2cab0a2
--- /dev/null
+++ b/.agents/skills/ui-work/SKILL.md
@@ -0,0 +1,32 @@
+---
+name: ui-work
+description: Use when changing FlClash Flutter UI, widgets, screens, Material You styling, navigation surfaces, or user-facing interactions.
+---
+
+# UI Work
+
+## When To Use
+
+Use this for user-facing Flutter UI changes in `lib/`, including widgets, screens, navigation surfaces, settings rows, dialogs, and interaction behavior.
+
+## Workflow
+
+1. Locate existing nearby widgets and reuse their patterns before adding new abstractions.
+2. Follow current Material You and Surfboard-like visual conventions.
+3. Use existing providers, notifiers, and helpers where possible.
+4. Keep `child:` last in widget constructors.
+5. Prefer `const` constructors and final locals.
+6. Localize user-facing text through ARB; use `localization` when text changes are non-trivial.
+7. Add focused widget tests when behavior changes, especially for rendering states, taps, scrolling, and empty/error states.
+8. Run targeted verification:
+
+ ```bash
+ flutter analyze
+ flutter test test/widgets/
+ ```
+
+## Pitfalls
+
+- Do not introduce a new visual system for one screen.
+- Do not manually edit generated localization or provider files.
+- Avoid broad layout rewrites unless the requested change requires them.
diff --git a/.claude/commands/i18n.md b/.claude/commands/i18n.md
deleted file mode 100644
index d85b8148..00000000
--- a/.claude/commands/i18n.md
+++ /dev/null
@@ -1,93 +0,0 @@
-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`
diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore
new file mode 100644
index 00000000..d20c0fe4
--- /dev/null
+++ b/.codegraph/.gitignore
@@ -0,0 +1,5 @@
+# CodeGraph data files — local to each machine, not for committing.
+# Ignore everything in .codegraph/ except this file itself, so transient
+# files (the database, daemon.pid, sockets, logs) never show up in git.
+*
+!.gitignore
diff --git a/.gemini/settings.json b/.gemini/settings.json
new file mode 100644
index 00000000..87ca7dea
--- /dev/null
+++ b/.gemini/settings.json
@@ -0,0 +1,12 @@
+{
+ "mcpServers": {
+ "codegraph": {
+ "type": "stdio",
+ "command": "codegraph",
+ "args": [
+ "serve",
+ "--mcp"
+ ]
+ }
+ }
+}
diff --git a/.github/homebrew_cask_template.rb b/.github/homebrew_cask_template.rb
new file mode 100644
index 00000000..3a9a493a
--- /dev/null
+++ b/.github/homebrew_cask_template.rb
@@ -0,0 +1,39 @@
+cask "flclash" do
+ version "VERSION"
+
+ on_macos do
+ arch arm: "arm64", intel: "amd64"
+
+ sha256 arm: "ARM_SHA256",
+ intel: "AMD_SHA256"
+
+ url "https://github.com/chen08209/FlClash/releases/download/v#{version}/FlClash-#{version}-macos-#{arch}.dmg"
+ end
+
+ name "FlClash"
+ desc "Multi-platform proxy client based on ClashMeta"
+ homepage "https://github.com/chen08209/FlClash"
+
+ livecheck do
+ url :url
+ strategy :github_latest
+ end
+
+ depends_on :macos
+
+ app "FlClash.app"
+
+ postflight do
+ system_command "xattr",
+ args: ["-rd", "com.apple.quarantine", "#{appdir}/FlClash.app"]
+ end
+
+ uninstall quit: "com.follow.clash"
+
+ zap trash: [
+ "~/Library/Application Support/com.follow.clash",
+ "~/Library/Caches/com.follow.clash",
+ "~/Library/Preferences/com.follow.clash.plist",
+ "~/Library/Saved Application State/com.follow.clash.savedState",
+ ]
+end
diff --git a/.github/release_template.md b/.github/release_template.md
index d1300848..c52b363d 100644
--- a/.github/release_template.md
+++ b/.github/release_template.md
@@ -42,7 +42,7 @@
-
+
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index abb91b98..a33fba3d 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -6,6 +6,7 @@ on:
- 'v*'
env:
IS_STABLE: ${{ !contains(github.ref, '-') }}
+ FLUTTER_VERSION: '3.44.4'
jobs:
test:
@@ -21,7 +22,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: stable
- flutter-version: 3.41.9
+ flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install dependencies
@@ -63,6 +64,7 @@ jobs:
steps:
- name: Setup rust
if: startsWith(matrix.os, 'windows-11-arm')
+ shell: pwsh
run: |
Invoke-WebRequest -Uri "https://win.rustup.rs/aarch64" -OutFile rustup-init.exe
.\rustup-init.exe -y --default-toolchain stable
@@ -79,14 +81,16 @@ jobs:
run: |
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
echo "${{ secrets.SERVICE_JSON }}" | base64 --decode > android/app/google-services.json
- echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
- echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
- echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
+ {
+ echo "keyAlias=${{ secrets.KEY_ALIAS }}"
+ echo "storePassword=${{ secrets.STORE_PASSWORD }}"
+ echo "keyPassword=${{ secrets.KEY_PASSWORD }}"
+ } >> android/local.properties
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: '1.24.0'
+ go-version: '1.26.4'
cache-dependency-path: |
core/go.sum
@@ -95,20 +99,18 @@ jobs:
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.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.44.0-0.1.pre
+ flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
+ - name: Setup Xcode
+ if: startsWith(matrix.platform,'macos')
+ run: |
+ sudo xcode-select -s /Applications/Xcode_26.3.app || sudo xcode-select -s /Applications/Xcode_26.2.app
+ xcodebuild -version
+
- name: Setup NDK
if: startsWith(matrix.platform,'android')
uses: nttld/setup-ndk@v1
@@ -133,17 +135,18 @@ jobs:
overwrite: true
changelog:
+ if: ${{ !contains(github.ref, '-') }}
+ permissions:
+ contents: write
runs-on: ubuntu-latest
needs: [ build ]
steps:
- name: Checkout
uses: actions/checkout@v4
- if: ${{ env.IS_STABLE == 'true' }}
with:
fetch-depth: 0
ref: refs/heads/main
- name: Generate
- if: ${{ env.IS_STABLE == 'true' }}
run: |
last_ver=$(grep -m1 '^## ' CHANGELOG.md 2>/dev/null | sed 's/^## //')
@@ -167,7 +170,6 @@ jobs:
mv "$temp" CHANGELOG.md
- name: Commit
- if: ${{ env.IS_STABLE == 'true' }}
run: |
git add CHANGELOG.md
if ! git diff --cached --quiet; then
@@ -175,8 +177,7 @@ jobs:
git config --local user.email "chen08209@gmail.com"
git config --local user.name "chen08209"
git commit -m "Update changelog"
- git push
- if [ $? -eq 0 ]; then
+ if git push; then
echo "Push succeeded"
else
echo "Push failed"
@@ -188,7 +189,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
upload:
- permissions: write-all
+ permissions:
+ contents: write
needs: [ build ]
runs-on: ubuntu-latest
services:
@@ -251,7 +253,7 @@ jobs:
if: ${{ env.IS_STABLE == 'true' }}
run: |
cd ./dist
- sha256sum * > SHA256SUMS
+ sha256sum ./* > SHA256SUMS
- name: Release
if: ${{ env.IS_STABLE == 'true' }}
@@ -260,6 +262,35 @@ jobs:
files: ./dist/*
body_path: './release.md'
+ - name: Create Homebrew Cask Source Dir
+ if: ${{ env.IS_STABLE == 'true' }}
+ run: |
+ version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
+ arm_sha=$(sha256sum "./dist/FlClash-${version}-macos-arm64.dmg" | awk '{print $1}')
+ amd_sha=$(sha256sum "./dist/FlClash-${version}-macos-amd64.dmg" | awk '{print $1}')
+
+ mkdir -p ./brew/Casks
+ sed \
+ -e "s|VERSION|$version|g" \
+ -e "s|ARM_SHA256|$arm_sha|g" \
+ -e "s|AMD_SHA256|$amd_sha|g" \
+ ./.github/homebrew_cask_template.rb > ./brew/Casks/flclash.rb
+
+ - name: Push to Homebrew tap
+ if: ${{ env.IS_STABLE == 'true' }}
+ uses: cpina/github-action-push-to-another-repository@v1.7.2
+ env:
+ SSH_DEPLOY_KEY: ${{ secrets.HOMEBREW_SSH_DEPLOY_KEY }}
+ with:
+ source-directory: ./brew/
+ destination-github-username: chen08209
+ destination-repository-name: homebrew-tap
+ user-name: 'chen08029'
+ user-email: 'chen08209@gmail.com'
+ target-branch: main
+ commit-message: Update Homebrew cask from ${{ github.ref_name }}
+ target-directory: /
+
- name: Create Fdroid Source Dir
if: ${{ env.IS_STABLE == 'true' }}
run: |
@@ -276,8 +307,8 @@ jobs:
source-directory: ./tmp/
destination-github-username: chen08209
destination-repository-name: FlClash-fdroid-repo
- user-name: 'github-actions[bot]'
- user-email: 'github-actions[bot]@users.noreply.github.com'
+ user-name: 'chen08029'
+ user-email: 'chen08209@gmail.com'
target-branch: main
commit-message: Update from ${{ github.ref_name }}
target-directory: /tmp/
diff --git a/.gitmodules b/.gitmodules
index 70bf7cd8..f373ba78 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,13 +2,3 @@
path = core/Clash.Meta
url = git@github.com:chen08209/Clash.Meta.git
branch = FlClash
-[submodule "plugins/flutter_distributor"]
- path = plugins/flutter_distributor
- url = git@github.com:chen08209/flutter_distributor.git
- branch = FlClash
-[submodule "plugins/tray_manager"]
- path = plugins/tray_manager
- url = git@github.com:chen08209/tray_manager.git
- branch = main
-
-
diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 00000000..87ca7dea
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,12 @@
+{
+ "mcpServers": {
+ "codegraph": {
+ "type": "stdio",
+ "command": "codegraph",
+ "args": [
+ "serve",
+ "--mcp"
+ ]
+ }
+ }
+}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..1d977681
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+ - repo: local
+ hooks:
+ - id: dart-format
+ name: dart format
+ entry: dart format
+ language: system
+ files: \.dart$
+ - id: flutter-analyze
+ name: flutter analyze
+ entry: flutter analyze --no-fatal-infos
+ language: system
+ pass_filenames: false
+ files: \.dart$
+ - id: flutter-test
+ name: flutter test
+ entry: flutter test --reporter expanded
+ language: system
+ pass_filenames: false
+ files: \.dart$
diff --git a/.zed/settings.json b/.zed/settings.json
new file mode 100644
index 00000000..1bd257a7
--- /dev/null
+++ b/.zed/settings.json
@@ -0,0 +1,13 @@
+{
+ "lsp": {
+ "gopls": {
+ "binary": {
+ "env": {
+ "CGO_ENABLED": "0",
+ // "GOOS": "android",
+ // "GOARCH": "arm64",
+ },
+ },
+ },
+ },
+}
diff --git a/AGENTS.md b/AGENTS.md
index fba9e5d9..9a555441 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,233 +1,35 @@
# AGENTS.md
-This file provides guidance for AI coding agents working with code in this repository.
+This file is the entry point for AI coding agents working in this repository. Keep it small: detailed guidance lives under
+`.agents/`, and discoverable repo skills live under `.agents/skills/*/SKILL.md`.
-## Project Overview
+## Start Here
-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.
+Read these files before making changes:
-## Common Development Commands
+- [.agents/project.md](.agents/project.md): project overview, versions, and build dependencies.
+- [.agents/commands.md](.agents/commands.md): build, development, code generation, and test commands.
+- [.agents/rules.md](.agents/rules.md): lint, testing, generated-code, and workflow rules.
-### Building
+Read these only when the task touches their area:
-```bash
-# Update submodules first (ClashMeta Go core lives in core/Clash.Meta/)
-git submodule update --init --recursive
+- [.agents/architecture.md](.agents/architecture.md): core integration, providers, database, managers, build system, and
+ local plugins.
+- [.agents/agent-config.md](.agents/agent-config.md): how to choose between `AGENTS.md`, `.agents`, skills, Codex config,
+ command rules, and hooks.
+- [.agents/skills.md](.agents/skills.md): index of repo-scoped skills in `.agents/skills/`.
-# Full package build (Go core + Flutter + packaging) via setup.dart
-dart setup.dart macos
-dart setup.dart linux
-dart setup.dart windows
-dart setup.dart android
+## Highest Priority Rules
-# Build only the Go core (skip Flutter packaging)
-bash plugins/setup/buildkit/run_build_tool.sh macos
-bash plugins/setup/buildkit/run_build_tool.sh linux
-bash plugins/setup/buildkit/run_build_tool.sh windows
-bash plugins/setup/buildkit/run_build_tool.sh android
-```
+- Use `flutter test`, not `dart test`, because models pull in Flutter types.
+- Run code generation after modifying models, providers, or database schema.
+- Do not manually edit generated files.
+- Follow `analysis_options.yaml`, especially single quotes, trailing commas, `child:` last, no `print()`, const/final
+ preferences, and declared return types.
+- For CI parity, verify with `flutter pub get`, `flutter analyze --no-fatal-infos`, and
+ `flutter test --reporter expanded` when practical.
-### Flutter Development
+## Repo Skills
-```bash
-# Project is pinned with FVM (.fvmrc currently uses Flutter 3.35.7)
-fvm flutter pub get
-fvm flutter run
-fvm flutter test
-
-# Plain Flutter also works when your global SDK matches the project constraints
-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
-flutter test test/widgets/ # Widget-level rendering/interaction tests
-flutter test test/setup_test.dart
-flutter test plugins/proxy/test/proxy_test.dart # Dart tests for bundled plugin packages
-```
-
-Root `flutter test` only discovers the root package's `test/` directory by default. Include bundled plugin Dart tests by
-passing their paths explicitly, or run `flutter test` from that plugin package directory. Native plugin tests under
-platform folders (for example Windows C++ tests) are not run by `flutter test`.
-
-**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/`. Current schema version is 2. Tables are `Profiles`, `Scripts`, `Rules`,
-`ProfileRuleLinks` (`profile_rule_mapping`), `ProxyGroups`, and `IconRecords` (`icon_records`). Rule scenes distinguish
-global added rules, profile added rules, profile custom rules, and disabled links. Uses fractional indexing for rule and
-proxy-group ordering.
-
-Generated Drift output lives in `lib/database/generated/database.g.dart`. After schema changes, run code generation and
-add/update focused database tests under `test/database/` when converter or migration behavior changes.
-
-### 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`
-
-### Build System
-
-`setup.dart` (project root) is the release build orchestrator:
-
-1. On Windows, pre-builds Go core via `dart run build_tool windows` and reads `core_sha256.json`
-2. Writes `env.json` (APP_ENV)
-3. Passes SHA256 as `--dart-define=CORE_SHA256=$val` (compile-time embedded, secure; Windows only)
-4. Activates `flutter_distributor` for packaging
-
-Go core building is handled by `build_tool`, a standalone Dart CLI in `plugins/setup/buildkit/build_tool/`.
-Platform build hooks inside `flutter build` trigger `build_tool` automatically:
-
-- **macOS:** podspec script phase → `build_pod.sh` → `build_tool macos`
-- **Linux:** CMake include → `buildkit/cmake/buildkit.cmake` → `build_tool linux`
-- **Windows:** CMake include → `buildkit/cmake/buildkit.cmake` → `build_tool windows` (debug: `--dev` via `CMAKE_BUILD_TYPE`)
-- **Android:** Gradle include → `buildkit/gradle/plugin.gradle` → `build_tool android`
-
-**Windows helper auth (release):** Core SHA256 is embedded in both the Flutter app (`--dart-define`) and the Rust
-helper (`TOKEN` env var during cargo build). The Dart app pings the helper and verifies the token matches.
-
-**Windows helper auth (debug):** The Rust helper skips token verification when built in debug mode
-(`cfg!(debug_assertions)`), so `flutter run` works without any SHA256 dance.
-
-`plugins/setup/` is an FFI plugin that exists solely as a build harness — it carries no Dart API, only platform build hooks
-(podspec, CMake, Gradle) that trigger Go compilation. Windows builds also compile a Rust helper (`services/helper/`) via
-`RustBuilder`.
-
-Build configuration defaults live in `build_tool/lib/src/options.dart` and can be overridden via `build_config.yaml`
-in the project root.
-
-Architecture detection is automatic (host arch via `uname -m` on Unix, `PROCESSOR_ARCHITECTURE` on Windows). The
-`--description` flag passed to flutter_distributor adds arch suffix to artifact names (e.g.,
-`FlClash-0.8.93-macos-arm64.dmg`).
-
-### Local Plugins (`plugins/`)
-
-- `setup` - Build harness FFI plugin (triggers Go/Rust compilation per platform)
-- `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`)
+Use repo skills from `.agents/skills/` when a task matches their descriptions. Current skills cover localization,
+provider tests, UI work, and core/platform changes.
diff --git a/CLAUDE.md b/CLAUDE.md
index 71993a20..8de06f90 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,5 +1,4 @@
-# CLAUDE.md - Claude Code instructions
+# CLAUDE.md
-Please read AGENTS.md for all project guidelines and conventions.
-
-@AGENTS.md
\ No newline at end of file
+This repository uses [AGENTS.md](AGENTS.md) as the canonical agent entry point.
+Read that file first, then follow the `.agents/` references it routes to.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..c1bef1db
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+SHELL := /bin/bash
+
+PLATFORM ?= macos
+BUILDKIT := plugins/setup/buildkit/run_build_tool.sh
+ARCH_ARG := $(if $(ARCH),--arch $(ARCH),)
+TARGET_PLATFORM_ARG := $(if $(TARGET_PLATFORM),--target-platform $(TARGET_PLATFORM),)
+
+.PHONY: help submodules core core-macos core-linux core-windows core-android
+
+help:
+ @echo 'make core # build macOS core by default'
+ @echo 'make core PLATFORM=linux ARCH=amd64'
+ @echo 'make core-macos ARCH=arm64'
+ @echo 'make core-android ARCH=arm64'
+ @echo 'make core-android TARGET_PLATFORM=android-arm64'
+
+submodules:
+ git submodule update --init --recursive
+
+core:
+ bash $(BUILDKIT) $(PLATFORM) $(ARCH_ARG) $(TARGET_PLATFORM_ARG)
+
+core-macos:
+ $(MAKE) core PLATFORM=macos
+
+core-linux:
+ $(MAKE) core PLATFORM=linux
+
+core-windows:
+ $(MAKE) core PLATFORM=windows
+
+core-android:
+ $(MAKE) core PLATFORM=android
diff --git a/README.md b/README.md
index 2e148be1..26b6b975 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,13 @@ Support the following actions
+### Homebrew
+
+```bash
+brew tap chen08209/tap
+brew install --cask flclash
+```
+
## Build
1. Update submodules
@@ -129,4 +136,4 @@ The easiest way to support developers is to click on the star (⭐) at the top o
-
\ No newline at end of file
+
diff --git a/README_zh_CN.md b/README_zh_CN.md
index df192ebb..eb517a60 100644
--- a/README_zh_CN.md
+++ b/README_zh_CN.md
@@ -61,6 +61,13 @@ on Mobile:
+### Homebrew
+
+```bash
+brew tap chen08209/tap
+brew install --cask flclash
+```
+
## Build
1. 更新 submodules
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 4cef3e8a..b64ccea8 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -3,14 +3,13 @@ import java.util.Properties
plugins {
id("com.android.application")
- id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}
-val localPropertiesFile = rootProject.file("local.properties")
val localProperties = Properties().apply {
+ val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { load(it) }
}
@@ -106,4 +105,4 @@ dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics.ndk)
implementation(libs.firebase.analytics)
-}
\ No newline at end of file
+}
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index dbee657b..18fecbd9 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -15,10 +15,25 @@ subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
+
subprojects {
project.evaluationDependsOn(":app")
}
+gradle.projectsEvaluated {
+ allprojects {
+ tasks.withType().configureEach {
+ sourceCompatibility = JavaVersion.VERSION_17.toString()
+ targetCompatibility = JavaVersion.VERSION_17.toString()
+ }
+ tasks.withType().configureEach {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
+ }
+ }
+ }
+}
+
tasks.register("clean") {
delete(rootProject.layout.buildDirectory)
}
diff --git a/android/common/build.gradle.kts b/android/common/build.gradle.kts
index b46b3a60..35e2c7c6 100644
--- a/android/common/build.gradle.kts
+++ b/android/common/build.gradle.kts
@@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.android.library")
- id("org.jetbrains.kotlin.android")
}
android {
@@ -42,4 +41,4 @@ dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics.ndk)
implementation(libs.firebase.analytics)
-}
\ No newline at end of file
+}
diff --git a/android/common/consumer-rules.pro b/android/common/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/build.gradle.kts b/android/core/build.gradle.kts
index 593a503c..915d5c96 100644
--- a/android/core/build.gradle.kts
+++ b/android/core/build.gradle.kts
@@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.android.library")
- id("org.jetbrains.kotlin.android")
}
android {
diff --git a/android/gradle.properties b/android/gradle.properties
index 598d13fe..f2122b21 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
+# This builtInKotlin flag was added automatically by Flutter migrator
+android.builtInKotlin=false
+# This newDsl flag was added automatically by Flutter migrator
+android.newDsl=false
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index f425f9a8..ed7237ad 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
#agp = "8.10.1"
-firebaseBom = "34.2.0"
+firebaseBom = "34.15.0"
minSdk = "23"
targetSdk = "36"
compileSdk = "36"
@@ -9,13 +9,12 @@ coreKtx = "1.17.0"
annotationJvm = "1.9.1"
coreSplashscreen = "1.0.1"
gson = "2.13.1"
-kotlin = "2.2.10"
+kotlin = "2.2.20"
smaliDexlib2 = "3.0.9"
-firebaseCrashlyticsKtx = "20.0.1"
-firebaseCommonKtx = "22.0.0"
+firebaseCrashlyticsKtx = "20.0.6"
+firebaseCommonKtx = "22.1.0"
[libraries]
-build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
annotation-jvm = { module = "androidx.annotation:annotation-jvm", version.ref = "annotationJvm" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
@@ -25,4 +24,4 @@ firebase-crashlytics-ndk = { module = "com.google.firebase:firebase-crashlytics-
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
firebase-crashlytics-ktx = { group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "firebaseCrashlyticsKtx" }
-firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
\ No newline at end of file
+firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 6064aebd..c1501a7e 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip
diff --git a/android/service/build.gradle.kts b/android/service/build.gradle.kts
index 8d9637b8..ac715317 100644
--- a/android/service/build.gradle.kts
+++ b/android/service/build.gradle.kts
@@ -2,8 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.android.library")
- id("org.jetbrains.kotlin.android")
- id("kotlin-parcelize")
}
android {
@@ -45,4 +43,4 @@ dependencies {
implementation(project(":common"))
implementation(libs.gson)
implementation(libs.androidx.core)
-}
\ No newline at end of file
+}
diff --git a/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt b/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
index 7196005d..dc18eeb4 100644
--- a/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
+++ b/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
@@ -1,11 +1,36 @@
package com.follow.clash.service.models
+import android.os.Parcel
import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-@Parcelize
data class NotificationParams(
val title: String = "FlClash",
val stopText: String = "STOP",
val onlyStatisticsProxy: Boolean = false,
-) : Parcelable
\ No newline at end of file
+) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ title = parcel.readString() ?: "FlClash",
+ stopText = parcel.readString() ?: "STOP",
+ onlyStatisticsProxy = parcel.readByte() != 0.toByte(),
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(title)
+ parcel.writeString(stopText)
+ parcel.writeByte(if (onlyStatisticsProxy) 1.toByte() else 0.toByte())
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): NotificationParams {
+ return NotificationParams(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
index 0e2b4acf..27264aa0 100644
--- a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
+++ b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
@@ -1,19 +1,45 @@
package com.follow.clash.service.models
+import android.os.Parcel
import android.os.Parcelable
import com.follow.clash.common.AccessControlMode
-import kotlinx.parcelize.Parcelize
import java.net.InetAddress
-@Parcelize
data class AccessControlProps(
val enable: Boolean,
val mode: AccessControlMode,
val acceptList: List,
val rejectList: List,
-) : Parcelable
+) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ enable = parcel.readByte() != 0.toByte(),
+ mode = AccessControlMode.valueOf(parcel.readString() ?: AccessControlMode.ACCEPT_SELECTED.name),
+ acceptList = parcel.createStringArrayList() ?: emptyList(),
+ rejectList = parcel.createStringArrayList() ?: emptyList(),
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeByte(if (enable) 1.toByte() else 0.toByte())
+ parcel.writeString(mode.name)
+ parcel.writeStringList(acceptList)
+ parcel.writeStringList(rejectList)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): AccessControlProps {
+ return AccessControlProps(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
-@Parcelize
data class VpnOptions(
val enable: Boolean,
val port: Int,
@@ -25,7 +51,59 @@ data class VpnOptions(
val bypassDomain: List,
val stack: String,
val routeAddress: List,
-) : Parcelable
+) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ enable = parcel.readByte() != 0.toByte(),
+ port = parcel.readInt(),
+ ipv6 = parcel.readByte() != 0.toByte(),
+ dnsHijacking = parcel.readByte() != 0.toByte(),
+ accessControlProps = readAccessControlProps(parcel),
+ allowBypass = parcel.readByte() != 0.toByte(),
+ systemProxy = parcel.readByte() != 0.toByte(),
+ bypassDomain = parcel.createStringArrayList() ?: emptyList(),
+ stack = parcel.readString() ?: "",
+ routeAddress = parcel.createStringArrayList() ?: emptyList(),
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeByte(if (enable) 1.toByte() else 0.toByte())
+ parcel.writeInt(port)
+ parcel.writeByte(if (ipv6) 1.toByte() else 0.toByte())
+ parcel.writeByte(if (dnsHijacking) 1.toByte() else 0.toByte())
+ parcel.writeParcelable(accessControlProps, flags)
+ parcel.writeByte(if (allowBypass) 1.toByte() else 0.toByte())
+ parcel.writeByte(if (systemProxy) 1.toByte() else 0.toByte())
+ parcel.writeStringList(bypassDomain)
+ parcel.writeString(stack)
+ parcel.writeStringList(routeAddress)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ @Suppress("DEPRECATION")
+ private fun readAccessControlProps(parcel: Parcel): AccessControlProps {
+ return parcel.readParcelable(
+ AccessControlProps::class.java.classLoader,
+ ) ?: AccessControlProps(
+ enable = false,
+ mode = AccessControlMode.ACCEPT_SELECTED,
+ acceptList = emptyList(),
+ rejectList = emptyList(),
+ )
+ }
+
+ override fun createFromParcel(parcel: Parcel): VpnOptions {
+ return VpnOptions(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
data class CIDR(val address: InetAddress, val prefixLength: Int)
@@ -80,4 +158,4 @@ fun String.toCIDR(): CIDR {
}
return CIDR(address, prefixLength)
-}
\ No newline at end of file
+}
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index dd51c864..5564c35e 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -19,10 +19,10 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
- id("com.android.application") version "8.12.2" apply false
- id("org.jetbrains.kotlin.android") version "2.2.10" apply false
- id("com.google.gms.google-services") version ("4.3.15") apply false
- id("com.google.firebase.crashlytics") version ("2.8.1") apply false
+ id("com.android.application") version "9.0.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.4.0" apply false
+ id("com.google.gms.google-services") version ("4.5.0") apply false
+ id("com.google.firebase.crashlytics") version ("3.0.7") apply false
}
diff --git a/arb/intl_en.arb b/arb/intl_en.arb
index 82ef0f95..6951fb3a 100644
--- a/arb/intl_en.arb
+++ b/arb/intl_en.arb
@@ -133,6 +133,7 @@
"app": "App",
"general": "General",
"systemProxyDesc": "Attach HTTP proxy to VpnService",
+ "userAgent": "User-Agent",
"unifiedDelay": "Unified delay",
"unifiedDelayDesc": "Remove extra delays such as handshaking",
"tcpConcurrent": "TCP concurrent",
@@ -537,5 +538,17 @@
"editSsid": "Edit SSID",
"authorized": "Authorized",
"tapToAuthorize": "Tap to authorize",
- "suspended": "Suspended..."
+ "suspended": "Suspended...",
+ "geoOptions": "Geo Options",
+ "geoAutoUpdate": "Auto Update",
+ "geoAutoUpdateInterval": "Auto Update Interval",
+ "geoAutoUpdateIntervalTip": "Auto update interval must be greater than 0",
+ "hours": "hours",
+ "hoursCount": "{count} hours",
+ "geoResources": "Geo Resources",
+ "geoUpdating": "Updating {name}...",
+ "geoSkipped": "{name} skipped",
+ "geoUpdated": "{name} updated",
+ "secondsCount": "{count} seconds",
+ "entriesCount": "{count} entries"
}
diff --git a/arb/intl_ja.arb b/arb/intl_ja.arb
index 4f8aa0cb..a310126c 100644
--- a/arb/intl_ja.arb
+++ b/arb/intl_ja.arb
@@ -133,6 +133,7 @@
"app": "アプリ",
"general": "一般",
"systemProxyDesc": "HTTPプロキシをVpnServiceに接続",
+ "userAgent": "ユーザーエージェント",
"unifiedDelay": "統一遅延",
"unifiedDelayDesc": "ハンドシェイクなどの余分な遅延を削除",
"tcpConcurrent": "TCP並列処理",
@@ -537,5 +538,17 @@
"editSsid": "SSIDを編集",
"authorized": "許可済み",
"tapToAuthorize": "タップして許可",
- "suspended": "一時停止中..."
+ "suspended": "一時停止中...",
+ "geoOptions": "Geoオプション",
+ "geoAutoUpdate": "自動更新",
+ "geoAutoUpdateInterval": "自動更新間隔",
+ "geoAutoUpdateIntervalTip": "自動更新間隔は0より大きくなければなりません",
+ "hours": "時間",
+ "hoursCount": "{count} 時間",
+ "geoResources": "Geoリソース",
+ "geoUpdating": "{name}を更新中...",
+ "geoSkipped": "{name} スキップ済み",
+ "geoUpdated": "{name} 更新済み",
+ "secondsCount": "{count} 秒",
+ "entriesCount": "{count} エントリ"
}
diff --git a/arb/intl_ru.arb b/arb/intl_ru.arb
index e43f9078..4cdfdf30 100644
--- a/arb/intl_ru.arb
+++ b/arb/intl_ru.arb
@@ -133,6 +133,7 @@
"app": "Приложение",
"general": "Общие",
"systemProxyDesc": "Прикрепить HTTP-прокси к VpnService",
+ "userAgent": "User-Agent",
"unifiedDelay": "Унифицированная задержка",
"unifiedDelayDesc": "Убрать дополнительные задержки, такие как рукопожатие",
"tcpConcurrent": "TCP параллелизм",
@@ -537,5 +538,17 @@
"editSsid": "Изменить SSID",
"authorized": "Разрешено",
"tapToAuthorize": "Нажмите, чтобы разрешить",
- "suspended": "Приостановлено..."
+ "suspended": "Приостановлено...",
+ "geoOptions": "Настройки Geo",
+ "geoAutoUpdate": "Автообновление",
+ "geoAutoUpdateInterval": "Интервал автообновления",
+ "geoAutoUpdateIntervalTip": "Интервал автообновления должен быть больше 0",
+ "hours": "часов",
+ "hoursCount": "{count} часов",
+ "geoResources": "Ресурсы Geo",
+ "geoUpdating": "Обновление {name}...",
+ "geoSkipped": "{name} пропущено",
+ "geoUpdated": "{name} обновлено",
+ "secondsCount": "{count} секунд",
+ "entriesCount": "{count} записей"
}
diff --git a/arb/intl_zh_CN.arb b/arb/intl_zh_CN.arb
index e6df9df8..28435c10 100644
--- a/arb/intl_zh_CN.arb
+++ b/arb/intl_zh_CN.arb
@@ -133,6 +133,7 @@
"app": "应用",
"general": "常规",
"systemProxyDesc": "设置系统代理",
+ "userAgent": "用户代理",
"unifiedDelay": "统一延迟",
"unifiedDelayDesc": "去除握手等额外延迟",
"tcpConcurrent": "TCP并发",
@@ -537,5 +538,17 @@
"editSsid": "编辑SSID",
"authorized": "已授权",
"tapToAuthorize": "点击授权",
- "suspended": "挂起中..."
+ "suspended": "挂起中...",
+ "geoOptions": "Geo 选项",
+ "geoAutoUpdate": "自动更新",
+ "geoAutoUpdateInterval": "自动更新间隔",
+ "geoAutoUpdateIntervalTip": "自动更新间隔必须大于0",
+ "hours": "小时",
+ "hoursCount": "{count} 小时",
+ "geoResources": "Geo 资源",
+ "geoUpdating": "正在更新 {name}...",
+ "geoSkipped": "{name} 已跳过",
+ "geoUpdated": "{name} 已更新",
+ "secondsCount": "{count} 秒",
+ "entriesCount": "{count} 个条目"
}
diff --git a/assets/images/icon/status_2.ico b/assets/images/icon/status_2.ico
index 517c8184..c13a3065 100644
Binary files a/assets/images/icon/status_2.ico and b/assets/images/icon/status_2.ico differ
diff --git a/assets/images/icon/status_2.png b/assets/images/icon/status_2.png
index 7e216dbe..2529f1db 100644
Binary files a/assets/images/icon/status_2.png and b/assets/images/icon/status_2.png differ
diff --git a/assets/images/icon/status_3.ico b/assets/images/icon/status_3.ico
index 5626c15e..5989d611 100644
Binary files a/assets/images/icon/status_3.ico and b/assets/images/icon/status_3.ico differ
diff --git a/assets/images/icon/status_3.png b/assets/images/icon/status_3.png
index 183f9148..f4e7ae0c 100644
Binary files a/assets/images/icon/status_3.png and b/assets/images/icon/status_3.png differ
diff --git a/assets_source/images/icon/status_1.svg b/assets_source/images/icon/status_1.svg
new file mode 100644
index 00000000..1a52bf34
--- /dev/null
+++ b/assets_source/images/icon/status_1.svg
@@ -0,0 +1,40 @@
+
+
+ status_1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets_source/images/icon/status_2.svg b/assets_source/images/icon/status_2.svg
new file mode 100644
index 00000000..75df10e9
--- /dev/null
+++ b/assets_source/images/icon/status_2.svg
@@ -0,0 +1,36 @@
+
+
+ status_2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets_source/images/icon/status_3.svg b/assets_source/images/icon/status_3.svg
new file mode 100644
index 00000000..091d256a
--- /dev/null
+++ b/assets_source/images/icon/status_3.svg
@@ -0,0 +1,40 @@
+
+
+ status_3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/Clash.Meta b/core/Clash.Meta
index 7bdcd60d..80362fc1 160000
--- a/core/Clash.Meta
+++ b/core/Clash.Meta
@@ -1 +1 @@
-Subproject commit 7bdcd60da9db10b681d7507af96d4fad797f3774
+Subproject commit 80362fc1895dcf60b79b562896653046e0687413
diff --git a/core/action.go b/core/action.go
index 41a3b680..a7d6c8f9 100644
--- a/core/action.go
+++ b/core/action.go
@@ -129,19 +129,11 @@ func handleAction(action *Action, result ActionResult) {
case getExternalProviderMethod:
externalProviderName := action.Data.(string)
result.success(handleGetExternalProvider(externalProviderName))
+ return
case updateGeoDataMethod:
- paramsString := action.Data.(string)
- var params = map[string]string{}
- err := json.Unmarshal([]byte(paramsString), ¶ms)
- if err != nil {
- result.success(err.Error())
- return
- }
- geoType := params["geo-type"]
- geoName := params["geo-name"]
- handleUpdateGeoData(geoType, geoName, func(value string) {
- result.success(value)
- })
+ geoType := action.Data.(string)
+ handleUpdateGeoData(geoType)
+ result.success("")
return
case updateExternalProviderMethod:
providerName := action.Data.(string)
diff --git a/core/common.go b/core/common.go
index bdde7616..46d9bdfd 100644
--- a/core/common.go
+++ b/core/common.go
@@ -6,6 +6,11 @@ import (
"encoding/json"
"errors"
"fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -13,6 +18,7 @@ import (
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
+ "github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
@@ -24,10 +30,6 @@ import (
"github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel"
- "os"
- "path/filepath"
- "runtime"
- "sync"
)
var (
@@ -234,7 +236,17 @@ func updateConfig(params *UpdateParams) {
general.Tun.Stack = *params.Tun.Stack
}
+ if params.GeoAutoUpdate != nil {
+ updater.SetGeoAutoUpdate(*params.GeoAutoUpdate)
+ }
+ if params.GeoUpdateInterval != nil {
+ updater.SetGeoUpdateInterval(*params.GeoUpdateInterval)
+ }
+
updateListeners()
+ if updater.GeoAutoUpdate() {
+ updater.RegisterGeoUpdaterWithCancel()
+ }
}
func applyConfig(params *SetupParams) error {
@@ -250,6 +262,9 @@ func applyConfig(params *SetupParams) error {
hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap)
updateListeners()
+ if updater.GeoAutoUpdate() {
+ updater.RegisterGeoUpdaterWithCancel()
+ }
return err
}
diff --git a/core/constant.go b/core/constant.go
index 5177fe4c..ed16fea2 100644
--- a/core/constant.go
+++ b/core/constant.go
@@ -34,6 +34,8 @@ type UpdateParams struct {
ExternalController *string `json:"external-controller"`
Interface *string `json:"interface-name"`
UnifiedDelay *bool `json:"unified-delay"`
+ GeoAutoUpdate *bool `json:"geo-auto-update"`
+ GeoUpdateInterval *int `json:"geo-update-interval"`
}
type tunSchema struct {
@@ -123,12 +125,20 @@ type Message struct {
}
const (
- LogMessage MessageType = "log"
- DelayMessage MessageType = "delay"
- RequestMessage MessageType = "request"
- LoadedMessage MessageType = "loaded"
+ LogMessage MessageType = "log"
+ DelayMessage MessageType = "delay"
+ RequestMessage MessageType = "request"
+ LoadedMessage MessageType = "loaded"
+ GeoUpdateMessage MessageType = "geoUpdate"
)
+type GeoUpdateStatus struct {
+ Type string `json:"type"`
+ Updating bool `json:"updating"`
+ Skipped bool `json:"skipped,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
diff --git a/core/go.mod b/core/go.mod
index fd557185..e04990f8 100644
--- a/core/go.mod
+++ b/core/go.mod
@@ -17,13 +17,15 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
- github.com/andybalholm/brotli v1.0.6 // indirect
+ github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
+ github.com/bodgit/plumbing v1.3.0 // indirect
+ github.com/bodgit/windows v1.0.1 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/coreos/go-iptables v0.8.0 // 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/enfein/mieru/v3 v3.33.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
@@ -42,6 +44,7 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect
@@ -51,6 +54,7 @@ require (
github.com/klauspost/reedsolomon v1.12.3 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
+ github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d // indirect
github.com/metacubex/ascon v0.1.0 // indirect
github.com/metacubex/bart v0.26.0 // indirect
@@ -67,30 +71,31 @@ require (
github.com/metacubex/hkdf v0.1.0 // indirect
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.6 // indirect
- github.com/metacubex/jsonv2 v0.0.0-20260513175203-1c6abea7534c // indirect
+ github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df // 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-20260426003805-208c2c1ba2cb // indirect
github.com/metacubex/qpack v0.6.0 // indirect
- github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306 // indirect
+ github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect
+ github.com/metacubex/sevenzip v1.6.4 // 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-20260512151354-8475655be853 // indirect
+ github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3 // 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.18 // indirect
+ github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc // indirect
+ github.com/metacubex/sing-tun v0.4.20 // indirect
github.com/metacubex/sing-vmess v0.2.5 // indirect
- github.com/metacubex/sing-wireguard v0.0.0-20260507084707-690d479ec947 // indirect
+ github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c // indirect
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
github.com/metacubex/ssh v0.1.0 // indirect
- github.com/metacubex/tailscale v0.0.0-20260516120020-a21c2c99dcbe // indirect
- github.com/metacubex/tailscale-wireguard-go v0.0.0-20260513233728-8bc7ee255d04 // indirect
+ github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7 // indirect
+ github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af // indirect
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
- github.com/metacubex/tls v0.1.5 // indirect
+ github.com/metacubex/tls v0.1.6 // 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
@@ -100,8 +105,9 @@ require (
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
- github.com/pierrec/lz4/v4 v4.1.14 // indirect
+ github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/pires/go-proxyproto v0.8.0 // indirect
+ github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.53.0 // indirect
@@ -109,11 +115,13 @@ require (
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.4 // indirect
+ github.com/stangelandcl/ppmd v0.1.1 // indirect
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
+ github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
diff --git a/core/go.sum b/core/go.sum
index c63494ac..f9206d68 100644
--- a/core/go.sum
+++ b/core/go.sum
@@ -12,11 +12,15 @@ github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
-github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
-github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
+github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
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/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
+github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
+github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
+github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
@@ -30,8 +34,8 @@ github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz
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/enfein/mieru/v3 v3.33.0 h1:hv2jK8nqYHwpSG86U2rpZR2I8Aff1/J3ifRmd9NBbFc=
+github.com/enfein/mieru/v3 v3.33.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=
@@ -75,6 +79,8 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu
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/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
@@ -90,10 +96,16 @@ 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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
+github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 h1:LqWr0vb9zDNuQS+jJd4fnRYk/SEI7KJ7TDe/L4WFK48=
+github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78/go.mod h1:BTBG/iVY7rg3qq5WdVCg0GFk58CSvCDSbjy8I7kEx/c=
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c=
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
@@ -126,8 +138,8 @@ 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.6 h1:xvXuvXMCMxCWMF5nEJF4yiKvXL+p2atWMzs37e80m1I=
github.com/metacubex/http v0.1.6/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
-github.com/metacubex/jsonv2 v0.0.0-20260513175203-1c6abea7534c h1:KGhBHDe6FveU0ury+9RyX329nclM1CHODa0Fi+uOAYM=
-github.com/metacubex/jsonv2 v0.0.0-20260513175203-1c6abea7534c/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
+github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df h1:S0vBzqjXok24VopstOgPd1JdgglW9tXehrqvwpQWbQ8=
+github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
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=
@@ -138,42 +150,44 @@ github.com/metacubex/nftables v0.0.0-20260426003805-208c2c1ba2cb h1:wk6mHYPURSUv
github.com/metacubex/nftables v0.0.0-20260426003805-208c2c1ba2cb/go.mod h1:73ZrCfhdkW4F2E2GAlta3km/S2RHhFNogCMtWZV2anQ=
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.20260413153657-53bb22f2c306 h1:HlGLmLsWJMLSu0CMI9z/BmEnithB4oXM5Rom6/0Qxtg=
-github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
+github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace h1:KXacx7dp1GYVMgxezwXRt5BMsEbvAYuA6rPFUmdAvcQ=
+github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace/go.mod h1:2YEQEvFrZ5V76oynMBDTlN+4fdnSHCa2uNJxv3cm1HU=
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/sevenzip v1.6.4 h1:OIL+DeOeSAbKNsjqxcYUMiarRmX6Kaxakb0GT7E9Oik=
+github.com/metacubex/sevenzip v1.6.4/go.mod h1:FP3X9bzFKj9wPxifGN9B3w2fIEicMjzKYIGIhnu+1pw=
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-20260512151354-8475655be853 h1:nZ5WNU6kjj6kBu4+2eMySFkUVGCop64rZnLMm+HPh8w=
-github.com/metacubex/sing-quic v0.0.0-20260512151354-8475655be853/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
+github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3 h1:PnMby5+kZXTl/CFDHfxMbMTaSRD+uMKMsrDYVQyAmX8=
+github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3/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.18 h1:WRzAosG0YkT3aZq5RJWtF+RdCgeJ8EpooS5ZM1lkXo0=
-github.com/metacubex/sing-tun v0.4.18/go.mod h1:g4I/JNplDBhXLF+aQWgFbhNeJPSXQOWS9HvLeNvkgeA=
+github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc h1:8wLoFfYQ88iGPL+krQ5tJsI8IAmkFjKpQL2q+y3pvss=
+github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
+github.com/metacubex/sing-tun v0.4.20 h1:xdupzizRoZKyDzP0l68WAx5Sk4ooiuT1GiWsiJyOGPw=
+github.com/metacubex/sing-tun v0.4.20/go.mod h1:g4I/JNplDBhXLF+aQWgFbhNeJPSXQOWS9HvLeNvkgeA=
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-20260507084707-690d479ec947 h1:IB03BvRQtvjWScyOK5jSQVJYY8osmZXHL+4VCEFMWcM=
-github.com/metacubex/sing-wireguard v0.0.0-20260507084707-690d479ec947/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
+github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c h1:tH9FuQW357zp2xAGzkoZTGpNGMVmEFZov0iV5M2S5ew=
+github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c/go.mod h1:eQZDJTx+IH3k4mXqaOJ3VJ9h9ZqOl60F7TLi5wAU51Q=
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/ssh v0.1.0 h1:iGfr99qk/eMHzUnQ/0bTxXT8+8SWqLSHBWDHoAhngzw=
github.com/metacubex/ssh v0.1.0/go.mod h1:NUtl0d+/f2cG9ECEpMM8iCVOpmggQlC13oLeDUONDlU=
-github.com/metacubex/tailscale v0.0.0-20260516120020-a21c2c99dcbe h1:ZdAKshacNruZGuKTE8WMTuxyGgpv/LySLoE/EEmgF9c=
-github.com/metacubex/tailscale v0.0.0-20260516120020-a21c2c99dcbe/go.mod h1:2G1V82OGXgxT7m7046GA80I9SlcvczljCK0C7NQ3c10=
-github.com/metacubex/tailscale-wireguard-go v0.0.0-20260513233728-8bc7ee255d04 h1:zk+mDDSBl5lv80WWtaFUbpj8XLb7AhjCUbn2pB37N0U=
-github.com/metacubex/tailscale-wireguard-go v0.0.0-20260513233728-8bc7ee255d04/go.mod h1:pKUKBy7IcQ5r0i66gWENHgxKvBn8tlgAGx0DZMq8h5M=
+github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7 h1:LoJR4NMyNKHeEJoeGDtcsao7sV0NRkzMeV5H/0J0MIE=
+github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7/go.mod h1:MAo3HhE7968rIwmDvYTYE8xCsV4x+hLnkChdXeP3X4c=
+github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af h1:c60IbBMUq2h1M2m7+grMJJmBmrObxL8SwvNtm6Ozbwk=
+github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af/go.mod h1:i3zLKytWkOnyT1i9OmiLevWvrN5J5HE1+yjE7UYNfcQ=
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.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
-github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
+github.com/metacubex/tls v0.1.6 h1:t2ubLneYa4ceyIC++54a57BLqZFA/QYUrhdjLk2GPwo=
+github.com/metacubex/tls v0.1.6/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=
@@ -195,13 +209,16 @@ github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
-github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
+github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
+github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
@@ -216,10 +233,19 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIF
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
+github.com/stangelandcl/ppmd v0.1.1 h1:c25QazhlWUn5nmR1QOzafKhQxBicAr7GGCKER2aJ8H8=
+github.com/stangelandcl/ppmd v0.1.1/go.mod h1:Rrv7M+/2P5jYr/GMLhBl7Ug3uJ1bUiVzr5LbbaV6xgY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
@@ -232,6 +258,8 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
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/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
+github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
@@ -245,6 +273,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
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=
@@ -302,8 +332,9 @@ golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/core/hub.go b/core/hub.go
index 1d2894a6..aad92083 100644
--- a/core/hub.go
+++ b/core/hub.go
@@ -347,36 +347,22 @@ func handleGetExternalProvider(externalProviderName string) string {
return string(data)
}
-func handleUpdateGeoData(geoType string, geoName string, fn func(value string)) {
+func handleUpdateGeoData(geoType string) {
go func() {
- path := constant.Path.Resolve(geoName)
switch geoType {
case "MMDB":
- err := updater.UpdateMMDBWithPath(path)
- if err != nil {
- fn(err.Error())
- return
- }
+ updater.UpdateMMDB()
+ return
case "ASN":
- err := updater.UpdateASNWithPath(path)
- if err != nil {
- fn(err.Error())
- return
- }
+ updater.UpdateASN()
+ return
case "GEOIP":
- err := updater.UpdateGeoIpWithPath(path)
- if err != nil {
- fn(err.Error())
- return
- }
+ updater.UpdateGeoIp()
+ return
case "GEOSITE":
- err := updater.UpdateGeoSiteWithPath(path)
- if err != nil {
- fn(err.Error())
- return
- }
+ updater.UpdateGeoSite()
+ return
}
- fn("")
}()
}
@@ -568,4 +554,18 @@ func init() {
Data: providerName,
})
}
+ updater.GeoUpdateHook = func(geoType string, updating bool, skipped bool, updateErr error) {
+ status := GeoUpdateStatus{
+ Type: geoType,
+ Updating: updating,
+ Skipped: skipped,
+ }
+ if updateErr != nil {
+ status.Error = updateErr.Error()
+ }
+ sendMessage(Message{
+ Type: GeoUpdateMessage,
+ Data: status,
+ })
+ }
}
diff --git a/lib/common/common.dart b/lib/common/common.dart
index 20909fd9..72fc85dc 100644
--- a/lib/common/common.dart
+++ b/lib/common/common.dart
@@ -12,6 +12,7 @@ export 'future.dart';
export 'http.dart';
export 'icons.dart';
export 'indexing.dart';
+export 'input_limits.dart';
export 'iterable.dart';
export 'javascript.dart';
export 'keyboard.dart';
@@ -29,7 +30,6 @@ export 'package.dart';
export 'path.dart';
export 'permission.dart';
export 'picker.dart';
-export 'pipe.dart';
export 'preferences.dart';
export 'print.dart';
export 'protocol.dart';
diff --git a/lib/common/fixed.dart b/lib/common/fixed.dart
index 55e1429a..e1f81e9b 100644
--- a/lib/common/fixed.dart
+++ b/lib/common/fixed.dart
@@ -7,7 +7,7 @@ class FixedList {
final List _list;
FixedList(this.maxLength, {List? list})
- : _list = (list ?? [])..truncate(maxLength);
+ : _list = (list ?? [])..truncate(maxLength);
void add(T item) {
_list.add(item);
@@ -25,10 +25,7 @@ class FixedList {
T operator [](int index) => _list[index];
FixedList copyWith() {
- return FixedList(
- maxLength,
- list: List.of(_list),
- );
+ return FixedList(maxLength, list: List.of(_list));
}
}
@@ -41,10 +38,7 @@ class FixedMap {
}
V updateCacheValue(K key, ValueCallback callback) {
- final realValue = _map.updateCacheValue(
- key,
- callback,
- );
+ final realValue = _map.updateCacheValue(key, callback);
_adjustMap();
return realValue;
}
@@ -65,9 +59,7 @@ class FixedMap {
void _adjustMap() {
if (_map.length > maxLength) {
- _map = Map.fromEntries(
- map.entries.toList()..truncate(maxLength),
- );
+ _map = Map.fromEntries(map.entries.toList()..truncate(maxLength));
}
}
diff --git a/lib/common/input_limits.dart b/lib/common/input_limits.dart
new file mode 100644
index 00000000..9a2a4bdf
--- /dev/null
+++ b/lib/common/input_limits.dart
@@ -0,0 +1,38 @@
+import 'package:flutter/services.dart';
+
+abstract final class TextInputLimits {
+ static const name = 64;
+ static const groupName = 64;
+ static const url = 2048;
+ static const uri = url;
+ static const iconUrl = url;
+ static const dnsServer = url;
+ static const hostValue = url;
+ static const userName = 128;
+ static const password = 512;
+ static const userAgent = 512;
+ static const fileName = 255;
+ static const port = 5;
+ static const number = 10;
+ static const interval = number;
+ static const search = 256;
+ static const rule = 1024;
+ static const filter = 1024;
+ static const status = 128;
+ static const dnsListen = 255;
+ static const domain = 512;
+ static const geoSite = 128;
+ static const geoIpCode = 16;
+ static const cidr = 64;
+
+ static List limit(int maxLength) {
+ return [LengthLimitingTextInputFormatter(maxLength)];
+ }
+
+ static List digitsOnly(int maxLength) {
+ return [
+ FilteringTextInputFormatter.digitsOnly,
+ LengthLimitingTextInputFormatter(maxLength),
+ ];
+ }
+}
diff --git a/lib/common/iterable.dart b/lib/common/iterable.dart
index 41324c39..d0fffcf4 100644
--- a/lib/common/iterable.dart
+++ b/lib/common/iterable.dart
@@ -80,6 +80,13 @@ extension ListExt on List {
return newList;
}
+ List copyAndReorder(int oldIndex, int newIndex) {
+ final newList = List.from(this);
+ final item = newList.removeAt(oldIndex);
+ newList.insert(newIndex, item);
+ return newList;
+ }
+
List safeSublist(int start, [int? end]) {
if (start <= 0) return this;
if (start > length) return [];
diff --git a/lib/common/keyboard.dart b/lib/common/keyboard.dart
index be2ee42d..c6970bdb 100644
--- a/lib/common/keyboard.dart
+++ b/lib/common/keyboard.dart
@@ -5,90 +5,90 @@ import 'system.dart';
final Map _knownKeyLabels =
{
- PhysicalKeyboardKey.keyA: 'A',
- PhysicalKeyboardKey.keyB: 'B',
- PhysicalKeyboardKey.keyC: 'C',
- PhysicalKeyboardKey.keyD: 'D',
- PhysicalKeyboardKey.keyE: 'E',
- PhysicalKeyboardKey.keyF: 'F',
- PhysicalKeyboardKey.keyG: 'G',
- PhysicalKeyboardKey.keyH: 'H',
- PhysicalKeyboardKey.keyI: 'I',
- PhysicalKeyboardKey.keyJ: 'J',
- PhysicalKeyboardKey.keyK: 'K',
- PhysicalKeyboardKey.keyL: 'L',
- PhysicalKeyboardKey.keyM: 'M',
- PhysicalKeyboardKey.keyN: 'N',
- PhysicalKeyboardKey.keyO: 'O',
- PhysicalKeyboardKey.keyP: 'P',
- PhysicalKeyboardKey.keyQ: 'Q',
- PhysicalKeyboardKey.keyR: 'R',
- PhysicalKeyboardKey.keyS: 'S',
- PhysicalKeyboardKey.keyT: 'T',
- PhysicalKeyboardKey.keyU: 'U',
- PhysicalKeyboardKey.keyV: 'V',
- PhysicalKeyboardKey.keyW: 'W',
- PhysicalKeyboardKey.keyX: 'X',
- PhysicalKeyboardKey.keyY: 'Y',
- PhysicalKeyboardKey.keyZ: 'Z',
- PhysicalKeyboardKey.digit1: '1',
- PhysicalKeyboardKey.digit2: '2',
- PhysicalKeyboardKey.digit3: '3',
- PhysicalKeyboardKey.digit4: '4',
- PhysicalKeyboardKey.digit5: '5',
- PhysicalKeyboardKey.digit6: '6',
- PhysicalKeyboardKey.digit7: '7',
- PhysicalKeyboardKey.digit8: '8',
- PhysicalKeyboardKey.digit9: '9',
- PhysicalKeyboardKey.digit0: '0',
- PhysicalKeyboardKey.enter: 'ENTER',
- PhysicalKeyboardKey.escape: 'ESCAPE',
- PhysicalKeyboardKey.backspace: 'BACKSPACE',
- PhysicalKeyboardKey.tab: 'TAB',
- PhysicalKeyboardKey.space: 'SPACE',
- PhysicalKeyboardKey.minus: '-',
- PhysicalKeyboardKey.equal: '=',
- PhysicalKeyboardKey.bracketLeft: '[',
- PhysicalKeyboardKey.bracketRight: ']',
- PhysicalKeyboardKey.backslash: '\\',
- PhysicalKeyboardKey.semicolon: ';',
- PhysicalKeyboardKey.quote: '"',
- PhysicalKeyboardKey.backquote: '`',
- PhysicalKeyboardKey.comma: ',',
- PhysicalKeyboardKey.period: '.',
- PhysicalKeyboardKey.slash: '/',
- PhysicalKeyboardKey.capsLock: 'CAPSLOCK',
- PhysicalKeyboardKey.f1: 'F1',
- PhysicalKeyboardKey.f2: 'F2',
- PhysicalKeyboardKey.f3: 'F3',
- PhysicalKeyboardKey.f4: 'F4',
- PhysicalKeyboardKey.f5: 'F5',
- PhysicalKeyboardKey.f6: 'F6',
- PhysicalKeyboardKey.f7: 'F7',
- PhysicalKeyboardKey.f8: 'F8',
- PhysicalKeyboardKey.f9: 'F9',
- PhysicalKeyboardKey.f10: 'F10',
- PhysicalKeyboardKey.f11: 'F11',
- PhysicalKeyboardKey.f12: 'F12',
- PhysicalKeyboardKey.home: 'HOME',
- PhysicalKeyboardKey.pageUp: 'PAGEUP',
- PhysicalKeyboardKey.delete: 'DELETE',
- PhysicalKeyboardKey.end: 'END',
- PhysicalKeyboardKey.pageDown: 'PAGEDOWN',
- PhysicalKeyboardKey.arrowRight: '→',
- PhysicalKeyboardKey.arrowLeft: '←',
- PhysicalKeyboardKey.arrowDown: '↓',
- PhysicalKeyboardKey.arrowUp: '↑',
- PhysicalKeyboardKey.controlLeft: 'CTRL',
- PhysicalKeyboardKey.shiftLeft: 'SHIFT',
- PhysicalKeyboardKey.altLeft: 'ALT',
- PhysicalKeyboardKey.metaLeft: system.isMacOS ? '⌘' : 'WIN',
- PhysicalKeyboardKey.controlRight: 'CTRL',
- PhysicalKeyboardKey.shiftRight: 'SHIFT',
- PhysicalKeyboardKey.altRight: 'ALT',
- PhysicalKeyboardKey.metaRight: system.isMacOS ? '⌘' : 'WIN',
- PhysicalKeyboardKey.fn: 'FN',
-};
+ PhysicalKeyboardKey.keyA: 'A',
+ PhysicalKeyboardKey.keyB: 'B',
+ PhysicalKeyboardKey.keyC: 'C',
+ PhysicalKeyboardKey.keyD: 'D',
+ PhysicalKeyboardKey.keyE: 'E',
+ PhysicalKeyboardKey.keyF: 'F',
+ PhysicalKeyboardKey.keyG: 'G',
+ PhysicalKeyboardKey.keyH: 'H',
+ PhysicalKeyboardKey.keyI: 'I',
+ PhysicalKeyboardKey.keyJ: 'J',
+ PhysicalKeyboardKey.keyK: 'K',
+ PhysicalKeyboardKey.keyL: 'L',
+ PhysicalKeyboardKey.keyM: 'M',
+ PhysicalKeyboardKey.keyN: 'N',
+ PhysicalKeyboardKey.keyO: 'O',
+ PhysicalKeyboardKey.keyP: 'P',
+ PhysicalKeyboardKey.keyQ: 'Q',
+ PhysicalKeyboardKey.keyR: 'R',
+ PhysicalKeyboardKey.keyS: 'S',
+ PhysicalKeyboardKey.keyT: 'T',
+ PhysicalKeyboardKey.keyU: 'U',
+ PhysicalKeyboardKey.keyV: 'V',
+ PhysicalKeyboardKey.keyW: 'W',
+ PhysicalKeyboardKey.keyX: 'X',
+ PhysicalKeyboardKey.keyY: 'Y',
+ PhysicalKeyboardKey.keyZ: 'Z',
+ PhysicalKeyboardKey.digit1: '1',
+ PhysicalKeyboardKey.digit2: '2',
+ PhysicalKeyboardKey.digit3: '3',
+ PhysicalKeyboardKey.digit4: '4',
+ PhysicalKeyboardKey.digit5: '5',
+ PhysicalKeyboardKey.digit6: '6',
+ PhysicalKeyboardKey.digit7: '7',
+ PhysicalKeyboardKey.digit8: '8',
+ PhysicalKeyboardKey.digit9: '9',
+ PhysicalKeyboardKey.digit0: '0',
+ PhysicalKeyboardKey.enter: 'ENTER',
+ PhysicalKeyboardKey.escape: 'ESCAPE',
+ PhysicalKeyboardKey.backspace: 'BACKSPACE',
+ PhysicalKeyboardKey.tab: 'TAB',
+ PhysicalKeyboardKey.space: 'SPACE',
+ PhysicalKeyboardKey.minus: '-',
+ PhysicalKeyboardKey.equal: '=',
+ PhysicalKeyboardKey.bracketLeft: '[',
+ PhysicalKeyboardKey.bracketRight: ']',
+ PhysicalKeyboardKey.backslash: '\\',
+ PhysicalKeyboardKey.semicolon: ';',
+ PhysicalKeyboardKey.quote: '"',
+ PhysicalKeyboardKey.backquote: '`',
+ PhysicalKeyboardKey.comma: ',',
+ PhysicalKeyboardKey.period: '.',
+ PhysicalKeyboardKey.slash: '/',
+ PhysicalKeyboardKey.capsLock: 'CAPSLOCK',
+ PhysicalKeyboardKey.f1: 'F1',
+ PhysicalKeyboardKey.f2: 'F2',
+ PhysicalKeyboardKey.f3: 'F3',
+ PhysicalKeyboardKey.f4: 'F4',
+ PhysicalKeyboardKey.f5: 'F5',
+ PhysicalKeyboardKey.f6: 'F6',
+ PhysicalKeyboardKey.f7: 'F7',
+ PhysicalKeyboardKey.f8: 'F8',
+ PhysicalKeyboardKey.f9: 'F9',
+ PhysicalKeyboardKey.f10: 'F10',
+ PhysicalKeyboardKey.f11: 'F11',
+ PhysicalKeyboardKey.f12: 'F12',
+ PhysicalKeyboardKey.home: 'HOME',
+ PhysicalKeyboardKey.pageUp: 'PAGEUP',
+ PhysicalKeyboardKey.delete: 'DELETE',
+ PhysicalKeyboardKey.end: 'END',
+ PhysicalKeyboardKey.pageDown: 'PAGEDOWN',
+ PhysicalKeyboardKey.arrowRight: '→',
+ PhysicalKeyboardKey.arrowLeft: '←',
+ PhysicalKeyboardKey.arrowDown: '↓',
+ PhysicalKeyboardKey.arrowUp: '↑',
+ PhysicalKeyboardKey.controlLeft: 'CTRL',
+ PhysicalKeyboardKey.shiftLeft: 'SHIFT',
+ PhysicalKeyboardKey.altLeft: 'ALT',
+ PhysicalKeyboardKey.metaLeft: system.isMacOS ? '⌘' : 'WIN',
+ PhysicalKeyboardKey.controlRight: 'CTRL',
+ PhysicalKeyboardKey.shiftRight: 'SHIFT',
+ PhysicalKeyboardKey.altRight: 'ALT',
+ PhysicalKeyboardKey.metaRight: system.isMacOS ? '⌘' : 'WIN',
+ PhysicalKeyboardKey.fn: 'FN',
+ };
extension KeyboardKeyExt on KeyboardKey {
String get label {
diff --git a/lib/common/link.dart b/lib/common/link.dart
index 719e8b22..6f1775f6 100644
--- a/lib/common/link.dart
+++ b/lib/common/link.dart
@@ -16,21 +16,20 @@ class LinkManager {
}
Future initAppLinksListen(
- Function(String url) installConfigCallBack) async {
+ Function(String url) installConfigCallBack,
+ ) async {
commonPrint.log('initAppLinksListen');
destroy();
- subscription = _appLinks.uriLinkStream.listen(
- (uri) {
- commonPrint.log('onAppLink: $uri');
- if (uri.host == 'install-config') {
- final parameters = uri.queryParameters;
- final url = parameters['url'];
- if (url != null) {
- installConfigCallBack(url);
- }
+ subscription = _appLinks.uriLinkStream.listen((uri) {
+ commonPrint.log('onAppLink: $uri');
+ if (uri.host == 'install-config') {
+ final parameters = uri.queryParameters;
+ final url = parameters['url'];
+ if (url != null) {
+ installConfigCallBack(url);
}
- },
- );
+ }
+ });
}
void destroy() {
diff --git a/lib/common/package.dart b/lib/common/package.dart
index 2560187d..972fced1 100644
--- a/lib/common/package.dart
+++ b/lib/common/package.dart
@@ -6,8 +6,8 @@ import 'common.dart';
extension PackageInfoExtension on PackageInfo {
String get ua => [
- '$appName/v$version',
- 'clash-verge',
- 'Platform/${Platform.operatingSystem}',
- ].join(' ');
+ '$appName/v$version',
+ 'clash-verge',
+ 'Platform/${Platform.operatingSystem}',
+ ].join(' ');
}
diff --git a/lib/common/picker.dart b/lib/common/picker.dart
index 52f2514a..fbb08f18 100644
--- a/lib/common/picker.dart
+++ b/lib/common/picker.dart
@@ -7,13 +7,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class Picker {
- Future pickerFile({bool withData = true}) async {
- final filePickerResult = await FilePicker.pickFiles(
- withData: withData,
- allowMultiple: false,
- initialDirectory: await appPath.downloadDirPath,
- );
- return filePickerResult?.files.first;
+ Future pickerFile() async {
+ return FilePicker.pickFile(initialDirectory: await appPath.downloadDirPath);
}
Future saveFile(String fileName, Uint8List bytes) async {
@@ -34,15 +29,12 @@ class Picker {
if (!await localFile.exists()) {
await localFile.create(recursive: true);
}
- final bytes = Platform.isAndroid ? await localFile.readAsBytes() : null;
+ final bytes = await localFile.readAsBytes();
final path = await FilePicker.saveFile(
fileName: fileName,
initialDirectory: await appPath.downloadDirPath,
bytes: bytes,
);
- if (path != null && bytes == null) {
- await localFile.copy(path);
- }
await localFile.safeDelete();
return path;
}
@@ -65,4 +57,10 @@ class Picker {
}
}
+extension PlatformFileExt on PlatformFile {
+ Future readBytes() {
+ return readAsBytes();
+ }
+}
+
final picker = Picker();
diff --git a/lib/common/pipe.dart b/lib/common/pipe.dart
deleted file mode 100644
index 54e6a53a..00000000
--- a/lib/common/pipe.dart
+++ /dev/null
@@ -1,234 +0,0 @@
-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 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(_pipeBufferSize);
- final bytesRead = calloc();
- final overlapped = calloc();
- 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(msg.data!.length);
- final written = calloc();
- 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();
- 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.broadcast();
- Completer _connectionCompleter = Completer();
-
- void Function()? onDisconnect;
-
- NamedPipeServer._(this.pipeName);
-
- Stream get dataStream => _dataController.stream;
- Completer get connectionCompleter => _connectionCompleter;
-
- static Future 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();
- }
- server._connectionCompleter.complete();
- } else if (message is Uint8List) {
- server._dataController.add(message);
- } else if (message == 'closed') {
- server._connectionCompleter = Completer();
- 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 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;
- }
-}
diff --git a/lib/common/protocol.dart b/lib/common/protocol.dart
index 45fa1800..f3e241fb 100644
--- a/lib/common/protocol.dart
+++ b/lib/common/protocol.dart
@@ -2,6 +2,26 @@ import 'dart:io';
import 'package:win32_registry/win32_registry.dart';
+class ProtocolRegistrationPlan {
+ final String scheme;
+ final String executable;
+
+ const ProtocolRegistrationPlan({
+ required this.scheme,
+ required this.executable,
+ });
+
+ String get protocolKey => 'Software\\Classes\\$scheme';
+
+ String get commandKey => 'shell\\open\\command';
+
+ String get protocolValueName => 'URL Protocol';
+
+ String get protocolValue => '';
+
+ String get command => '"$executable" "%1"';
+}
+
class Protocol {
static Protocol? _instance;
@@ -13,19 +33,25 @@ class Protocol {
}
void register(String scheme) {
- final String protocolRegKey = 'Software\\Classes\\$scheme';
- const RegistryValue protocolRegValue = RegistryValue.string(
- 'URL Protocol',
- '',
+ final plan = ProtocolRegistrationPlan(
+ scheme: scheme,
+ executable: Platform.resolvedExecutable,
);
- const String protocolCmdRegKey = 'shell\\open\\command';
- final RegistryValue protocolCmdRegValue = RegistryValue.string(
- '',
- '"${Platform.resolvedExecutable}" "%1"',
- );
- final regKey = Registry.currentUser.createKey(protocolRegKey);
- regKey.createValue(protocolRegValue);
- regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue);
+ final regKey = CURRENT_USER.create(plan.protocolKey);
+ try {
+ regKey.setValue(
+ plan.protocolValueName,
+ RegistryValue.string(plan.protocolValue),
+ );
+ final commandKey = regKey.create(plan.commandKey);
+ try {
+ commandKey.setValue('', RegistryValue.string(plan.command));
+ } finally {
+ commandKey.close();
+ }
+ } finally {
+ regKey.close();
+ }
}
}
diff --git a/lib/common/request.dart b/lib/common/request.dart
index 2ae19edf..1bbe9f3c 100644
--- a/lib/common/request.dart
+++ b/lib/common/request.dart
@@ -113,30 +113,30 @@ class Request {
final future = dio
.get>(
- source.key,
- cancelToken: token,
- options: Options(responseType: ResponseType.json),
- )
+ source.key,
+ cancelToken: token,
+ options: Options(responseType: ResponseType.json),
+ )
.timeout(const Duration(seconds: 10));
future
.then((res) {
- if (res.statusCode == HttpStatus.ok && res.data != null) {
- completer.complete(Result.success(source.value(res.data!)));
- return;
- }
- commonPrint.log('checkIp data empty', logLevel: LogLevel.info);
- failureCount++;
- handleFailRes();
- })
+ if (res.statusCode == HttpStatus.ok && res.data != null) {
+ completer.complete(Result.success(source.value(res.data!)));
+ return;
+ }
+ commonPrint.log('checkIp data empty', logLevel: LogLevel.info);
+ failureCount++;
+ handleFailRes();
+ })
.catchError((e) {
- failureCount++;
- if (e is DioException && e.type == DioExceptionType.cancel) {
- completer.complete(Result.error('cancelled'));
- return;
- }
- commonPrint.log('checkIp error $e', logLevel: LogLevel.warning);
- handleFailRes();
- });
+ 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;
});
final res = await Future.any(futures);
@@ -149,9 +149,9 @@ class Request {
try {
final response = await dio
.get(
- 'http://$localhost:$helperPort/ping',
- options: Options(responseType: ResponseType.plain),
- )
+ 'http://$localhost:$helperPort/ping',
+ options: Options(responseType: ResponseType.plain),
+ )
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
@@ -166,10 +166,10 @@ class Request {
try {
final response = await dio
.post(
- 'http://$localhost:$helperPort/start',
- data: json.encode({'path': appPath.corePath, 'arg': arg}),
- options: Options(responseType: ResponseType.plain),
- )
+ 'http://$localhost:$helperPort/start',
+ data: json.encode({'path': appPath.corePath, 'arg': arg}),
+ options: Options(responseType: ResponseType.plain),
+ )
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
@@ -185,9 +185,9 @@ class Request {
try {
final response = await dio
.post(
- 'http://$localhost:$helperPort/stop',
- options: Options(responseType: ResponseType.plain),
- )
+ 'http://$localhost:$helperPort/stop',
+ options: Options(responseType: ResponseType.plain),
+ )
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
diff --git a/lib/common/system.dart b/lib/common/system.dart
index 0844fc5f..f86178b5 100644
--- a/lib/common/system.dart
+++ b/lib/common/system.dart
@@ -104,6 +104,7 @@ class System {
obscureText: true,
title: currentAppLocalizations.pleaseInputAdminPassword,
value: '',
+ inputFormatters: TextInputLimits.limit(TextInputLimits.password),
),
);
if (password == null || password.isEmpty) {
diff --git a/lib/common/task.dart b/lib/common/task.dart
index 13b8a744..ea29017a 100644
--- a/lib/common/task.dart
+++ b/lib/common/task.dart
@@ -183,7 +183,7 @@ Future> _makeRealProfileTask(
}
}
rawConfig['profile']['store-selected'] = false;
- rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
+ rawConfig['geox-url'] = realPatchConfig.geoXUrl.raw;
rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA;
if (rawConfig['hosts'] == null) {
rawConfig['hosts'] = {};
@@ -331,10 +331,10 @@ Future _encodeLogsTask(List data) async {
Future oldToNowTask(Map data) async {
final homeDir = await appPath.homeDirPath;
- return compute<
- VM3, String, String>,
- MigrationData
- >(_oldToNowTask, VM3(data, homeDir, homeDir));
+ return compute, String, String>, MigrationData>(
+ _oldToNowTask,
+ VM3(data, homeDir, homeDir),
+ );
}
Future _oldToNowTask(
diff --git a/lib/common/text.dart b/lib/common/text.dart
index 043df405..ecfb28ca 100644
--- a/lib/common/text.dart
+++ b/lib/common/text.dart
@@ -11,11 +11,8 @@ extension TextStyleExtension on TextStyle {
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
- TextStyle get toJetBrainsMono => copyWith(
- fontFamily: FontFamily.jetBrainsMono.value,
- );
+ TextStyle get toJetBrainsMono =>
+ copyWith(fontFamily: FontFamily.jetBrainsMono.value);
- TextStyle adjustSize(int size) => copyWith(
- fontSize: fontSize! + size,
- );
+ TextStyle adjustSize(int size) => copyWith(fontSize: fontSize! + size);
}
diff --git a/lib/core/controller.dart b/lib/core/controller.dart
index 5dd04ce1..c6b3bd54 100644
--- a/lib/core/controller.dart
+++ b/lib/core/controller.dart
@@ -177,8 +177,8 @@ class CoreController {
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
}
- Future updateGeoData(UpdateGeoDataParams params) {
- return _interface.updateGeoData(params);
+ Future updateGeoData(String type) {
+ return _interface.updateGeoData(type);
}
Future sideLoadExternalProvider({
diff --git a/lib/core/event.dart b/lib/core/event.dart
index 51f4d990..a6719f7d 100644
--- a/lib/core/event.dart
+++ b/lib/core/event.dart
@@ -14,6 +14,13 @@ abstract mixin class CoreEventListener {
void onLoaded(String providerName) {}
void onCrash(String message) {}
+
+ void onGeoUpdate(
+ String geoType,
+ bool updating,
+ bool skipped,
+ String? error,
+ ) {}
}
class CoreEventManager {
@@ -38,6 +45,15 @@ class CoreEventManager {
case CoreEventType.crash:
listener.onCrash(event.data);
break;
+ case CoreEventType.geoUpdate:
+ final data = event.data as Map;
+ listener.onGeoUpdate(
+ data['type'] as String,
+ data['updating'] as bool,
+ data['skipped'] as bool? ?? false,
+ data['error'] as String?,
+ );
+ break;
}
}
});
diff --git a/lib/core/interface.dart b/lib/core/interface.dart
index 4123cb63..81540172 100644
--- a/lib/core/interface.dart
+++ b/lib/core/interface.dart
@@ -38,7 +38,7 @@ mixin CoreInterface {
Future? getExternalProvider(String externalProviderName);
- Future updateGeoData(UpdateGeoDataParams params);
+ Future updateGeoData(String type);
Future sideLoadExternalProvider({
required String providerName,
@@ -214,10 +214,10 @@ abstract class CoreHandlerInterface with CoreInterface {
}
@override
- Future updateGeoData(UpdateGeoDataParams params) async {
+ Future updateGeoData(String type) async {
return await _invoke(
method: ActionMethod.updateGeoData,
- data: json.encode(params),
+ data: type,
) ??
'';
}
diff --git a/lib/core/transport.dart b/lib/core/transport.dart
index 369c2018..e7481bca 100644
--- a/lib/core/transport.dart
+++ b/lib/core/transport.dart
@@ -34,50 +34,50 @@ class IPCCoreTransport {
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) {
+ (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;
- }
- _readyCompleter.complete();
- break;
- case _typeConnected:
- commonPrint.log('IPC Connected');
- if (_completer.isCompleted) {
+ case _typeConnected:
+ commonPrint.log('IPC Connected');
+ if (_completer.isCompleted) {
+ break;
+ }
+ _completer.complete();
break;
- }
- _completer.complete();
- break;
- case _typeDisconnected:
- commonPrint.log('IPC Disconnected');
- _completer = Completer();
- 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;
+ case _typeDisconnected:
+ commonPrint.log('IPC Disconnected');
+ _completer = Completer();
+ 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',
diff --git a/lib/enum/enum.dart b/lib/enum/enum.dart
index 3a58c4f3..6a25b645 100644
--- a/lib/enum/enum.dart
+++ b/lib/enum/enum.dart
@@ -135,7 +135,7 @@ enum ResultType {
error,
}
-enum CoreEventType { log, delay, request, loaded, crash }
+enum CoreEventType { log, delay, request, loaded, crash, geoUpdate }
enum InvokeMessageType { protect, process }
@@ -331,6 +331,40 @@ enum DashboardWidget {
enum GeodataLoader { standard, memconservative }
+enum GeoResource {
+ @JsonValue('mmdb')
+ MMDB,
+ @JsonValue('asn')
+ ASN,
+ @JsonValue('geo-ip')
+ GEOIP,
+ @JsonValue('geo-site')
+ GEOSITE;
+
+ static GeoResource fromJson(String value) {
+ return switch (value) {
+ 'mmdb' => GeoResource.MMDB,
+ 'asn' => GeoResource.ASN,
+ 'geo-ip' || 'geoip' => GeoResource.GEOIP,
+ 'geo-site' || 'geosite' => GeoResource.GEOSITE,
+ _ => throw ArgumentError.value(value, 'value', 'Invalid geo resource'),
+ };
+ }
+}
+
+extension GeoResourceExt on GeoResource {
+ String get value {
+ return switch (this) {
+ GeoResource.MMDB => 'mmdb',
+ GeoResource.ASN => 'asn',
+ GeoResource.GEOIP => 'geo-ip',
+ GeoResource.GEOSITE => 'geo-site',
+ };
+ }
+
+ String get updatingKey => 'geo_resource_$name';
+}
+
enum PageLabel {
dashboard,
proxies,
diff --git a/lib/features/overwrite/rule.dart b/lib/features/overwrite/rule.dart
index 5ef08faa..8262b27e 100644
--- a/lib/features/overwrite/rule.dart
+++ b/lib/features/overwrite/rule.dart
@@ -251,7 +251,7 @@ class _AddOrEditRuleDialogState extends State {
return;
}
final rule = Rule(
- id: widget.rule?.id ?? -1,
+ id: widget.rule?.id ?? snowflake.id,
ruleAction: _ruleAction,
content: _contentController.text,
ruleTarget: _ruleTargetController.text,
@@ -310,6 +310,9 @@ class _AddOrEditRuleDialogState extends State {
const SizedBox(height: 24),
TextFormField(
keyboardType: TextInputType.text,
+ inputFormatters: TextInputLimits.limit(
+ TextInputLimits.rule,
+ ),
onFieldSubmitted: (_) {
_handleSubmit();
},
diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart
index 3782f141..c4da298d 100644
--- a/lib/l10n/intl/messages_en.dart
+++ b/lib/l10n/intl/messages_en.dart
@@ -33,40 +33,52 @@ class MessageLookup extends MessageLookupByLibrary {
static String m4(label) => "${label} cannot be empty";
- static String m5(label) => "Current ${label} already exists";
+ static String m5(count) => "${count} entries";
- static String m6(count) =>
+ static String m6(label) => "Current ${label} already exists";
+
+ static String m7(name) => "${name} skipped";
+
+ static String m8(name) => "${name} updated";
+
+ static String m9(name) => "Updating ${name}...";
+
+ static String m10(count) =>
"${Intl.plural(count, one: '1 hour ago', other: '${count} hours ago')}";
- static String m7(target) => "${target} is an invalid policy";
+ static String m11(count) => "${count} hours";
- static String m8(proxyName) => "${proxyName} is an invalid proxy";
+ static String m12(target) => "${target} is an invalid policy";
- static String m9(providerName) =>
+ static String m13(proxyName) => "${proxyName} is an invalid proxy";
+
+ static String m14(providerName) =>
"${providerName} is an invalid proxy provider";
- static String m10(subRule) => "${subRule} is an invalid SUB_RULE";
+ static String m15(subRule) => "${subRule} is an invalid SUB_RULE";
- static String m11(appName) =>
+ static String m16(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) =>
+ static String m17(count) =>
"${Intl.plural(count, one: '1 minute ago', other: '${count} minutes ago')}";
- static String m13(count) =>
+ static String m18(count) =>
"${Intl.plural(count, one: '1 month ago', other: '${count} months ago')}";
- static String m14(label) => "No ${label} yet";
+ static String m19(label) => "No ${label} yet";
- static String m15(label) => "${label} must be a number";
+ static String m20(label) => "${label} must be a number";
- static String m16(label) => "${label} must be between 1024 and 49151";
+ static String m21(label) => "${label} must be between 1024 and 49151";
- static String m17(count) => "${count} items have been selected";
+ static String m22(count) => "${count} seconds";
- static String m18(label) => "${label} must be a url";
+ static String m23(count) => "${count} items have been selected";
- static String m19(count) =>
+ static String m24(label) => "${label} must be a url";
+
+ static String m25(count) =>
"${Intl.plural(count, one: '1 year ago', other: '${count} years ago')}";
final messages = _notInlinedMessages(_notInlinedMessages);
@@ -343,6 +355,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("English"),
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
+ "entriesCount": m5,
"exclude": MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"),
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"When the app is in the background, the app is hidden from the recent task",
@@ -355,7 +368,7 @@ class MessageLookup extends MessageLookupByLibrary {
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("Exclude type"),
- "existsTip": m5,
+ "existsTip": m6,
"exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("Expected status"),
@@ -394,6 +407,18 @@ class MessageLookup extends MessageLookupByLibrary {
),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("FruitSalad"),
"general": MessageLookupByLibrary.simpleMessage("General"),
+ "geoAutoUpdate": MessageLookupByLibrary.simpleMessage("Auto Update"),
+ "geoAutoUpdateInterval": MessageLookupByLibrary.simpleMessage(
+ "Auto Update Interval",
+ ),
+ "geoAutoUpdateIntervalTip": MessageLookupByLibrary.simpleMessage(
+ "Auto update interval must be greater than 0",
+ ),
+ "geoOptions": MessageLookupByLibrary.simpleMessage("Geo Options"),
+ "geoResources": MessageLookupByLibrary.simpleMessage("Geo Resources"),
+ "geoSkipped": m7,
+ "geoUpdated": m8,
+ "geoUpdating": m9,
"geodataLoader": MessageLookupByLibrary.simpleMessage(
"Geo Low Memory Mode",
),
@@ -420,7 +445,9 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
"Use keyboard to control applications",
),
- "hoursAgo": m6,
+ "hours": MessageLookupByLibrary.simpleMessage("hours"),
+ "hoursAgo": m10,
+ "hoursCount": m11,
"icon": MessageLookupByLibrary.simpleMessage("Icon"),
"iconRecords": MessageLookupByLibrary.simpleMessage("Icon records"),
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
@@ -464,10 +491,10 @@ class MessageLookup extends MessageLookupByLibrary {
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Invalid backup file",
),
- "invalidPolicy": m7,
- "invalidProxy": m8,
- "invalidProxyProvider": m9,
- "invalidSubRule": m10,
+ "invalidPolicy": m12,
+ "invalidProxy": m13,
+ "invalidProxyProvider": m14,
+ "invalidSubRule": m15,
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic",
@@ -501,7 +528,7 @@ class MessageLookup extends MessageLookupByLibrary {
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
- "locationPermissionGuide": m11,
+ "locationPermissionGuide": m16,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
@@ -531,11 +558,11 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
"Modify the default system exit event",
),
- "minutesAgo": m12,
+ "minutesAgo": m17,
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
- "monthsAgo": m13,
+ "monthsAgo": m18,
"more": MessageLookupByLibrary.simpleMessage("More"),
"name": MessageLookupByLibrary.simpleMessage("Name"),
"nameserver": MessageLookupByLibrary.simpleMessage("Nameserver"),
@@ -581,8 +608,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile",
),
- "nullTip": m14,
- "numberTip": m15,
+ "nullTip": m19,
+ "numberTip": m20,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
@@ -631,7 +658,7 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Please enter a different port",
),
- "portTip": m16,
+ "portTip": m21,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Prioritize the use of DOH\'s http/3",
),
@@ -888,6 +915,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
+ "secondsCount": m22,
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selectProxies": MessageLookupByLibrary.simpleMessage("Select proxies"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
@@ -903,7 +931,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Please select sub rule",
),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
- "selectedCountTitle": m17,
+ "selectedCountTitle": m23,
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
@@ -1003,9 +1031,10 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Obtain profile through URL",
),
- "urlTip": m18,
+ "urlTip": m24,
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
+ "userAgent": MessageLookupByLibrary.simpleMessage("User-Agent"),
"value": MessageLookupByLibrary.simpleMessage("Value"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Vibrant"),
"view": MessageLookupByLibrary.simpleMessage("View"),
@@ -1022,7 +1051,7 @@ class MessageLookup extends MessageLookupByLibrary {
"WebDAV configuration",
),
"whitelistMode": MessageLookupByLibrary.simpleMessage("Whitelist mode"),
- "yearsAgo": m19,
+ "yearsAgo": m25,
"zh_CN": MessageLookupByLibrary.simpleMessage("Simplified Chinese"),
};
}
diff --git a/lib/l10n/intl/messages_ja.dart b/lib/l10n/intl/messages_ja.dart
index 7a6df21d..ceac8a46 100644
--- a/lib/l10n/intl/messages_ja.dart
+++ b/lib/l10n/intl/messages_ja.dart
@@ -30,36 +30,48 @@ class MessageLookup extends MessageLookupByLibrary {
static String m4(label) => "${label}は空欄にできません";
- static String m5(label) => "現在の${label}は既に存在しています";
+ static String m5(count) => "${count} エントリ";
- static String m6(count) => "${count}時間前";
+ static String m6(label) => "現在の${label}は既に存在しています";
- static String m7(target) => "${target} は無効なポリシーです";
+ static String m7(name) => "${name} スキップ済み";
- static String m8(proxyName) => "${proxyName} は無効なプロキシです";
+ static String m8(name) => "${name} 更新済み";
- static String m9(providerName) => "${providerName} は無効なプロキシプロバイダーです";
+ static String m9(name) => "${name}を更新中...";
- static String m10(subRule) => "${subRule} は無効なSUB_RULEです";
+ static String m10(count) => "${count}時間前";
- static String m11(appName) =>
+ static String m11(count) => "${count} 時間";
+
+ static String m12(target) => "${target} は無効なポリシーです";
+
+ static String m13(proxyName) => "${proxyName} は無効なプロキシです";
+
+ static String m14(providerName) => "${providerName} は無効なプロキシプロバイダーです";
+
+ static String m15(subRule) => "${subRule} は無効なSUB_RULEです";
+
+ static String m16(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 m17(count) => "${count}分前";
- static String m13(count) => "${count}ヶ月前";
+ static String m18(count) => "${count}ヶ月前";
- static String m14(label) => "まだ${label}はありません";
+ static String m19(label) => "まだ${label}はありません";
- static String m15(label) => "${label}は数字でなければなりません";
+ static String m20(label) => "${label}は数字でなければなりません";
- static String m16(label) => "${label} は 1024 から 49151 の間でなければなりません";
+ static String m21(label) => "${label} は 1024 から 49151 の間でなければなりません";
- static String m17(count) => "${count} 項目が選択されています";
+ static String m22(count) => "${count} 秒";
- static String m18(label) => "${label}はURLである必要があります";
+ static String m23(count) => "${count} 項目が選択されています";
- static String m19(count) => "${count}年前";
+ static String m24(label) => "${label}はURLである必要があります";
+
+ static String m25(count) => "${count}年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map _notInlinedMessages(_) => {
@@ -265,6 +277,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英語"),
"entries": MessageLookupByLibrary.simpleMessage(" エントリ"),
+ "entriesCount": m5,
"exclude": MessageLookupByLibrary.simpleMessage("最近のタスクから非表示"),
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"アプリがバックグラウンド時に最近のタスクから非表示",
@@ -275,7 +288,7 @@ class MessageLookup extends MessageLookupByLibrary {
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("除外タイプ"),
- "existsTip": m5,
+ "existsTip": m6,
"exit": MessageLookupByLibrary.simpleMessage("終了"),
"expand": MessageLookupByLibrary.simpleMessage("標準"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("期待されるステータス"),
@@ -310,6 +323,16 @@ class MessageLookup extends MessageLookupByLibrary {
),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("フルーツサラダ"),
"general": MessageLookupByLibrary.simpleMessage("一般"),
+ "geoAutoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
+ "geoAutoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔"),
+ "geoAutoUpdateIntervalTip": MessageLookupByLibrary.simpleMessage(
+ "自動更新間隔は0より大きくなければなりません",
+ ),
+ "geoOptions": MessageLookupByLibrary.simpleMessage("Geoオプション"),
+ "geoResources": MessageLookupByLibrary.simpleMessage("Geoリソース"),
+ "geoSkipped": m7,
+ "geoUpdated": m8,
+ "geoUpdating": m9,
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低メモリモード"),
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
"有効化するとGeo低メモリローダーを使用",
@@ -328,7 +351,9 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
"キーボードでアプリを制御",
),
- "hoursAgo": m6,
+ "hours": MessageLookupByLibrary.simpleMessage("時間"),
+ "hoursAgo": m10,
+ "hoursCount": m11,
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
"iconRecords": MessageLookupByLibrary.simpleMessage("アイコン履歴"),
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
@@ -360,10 +385,10 @@ class MessageLookup extends MessageLookupByLibrary {
"interval": MessageLookupByLibrary.simpleMessage("インターバル"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("無効なバックアップファイル"),
- "invalidPolicy": m7,
- "invalidProxy": m8,
- "invalidProxyProvider": m9,
- "invalidSubRule": m10,
+ "invalidPolicy": m12,
+ "invalidProxy": m13,
+ "invalidProxyProvider": m14,
+ "invalidSubRule": m15,
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
@@ -391,7 +416,7 @@ class MessageLookup extends MessageLookupByLibrary {
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
- "locationPermissionGuide": m11,
+ "locationPermissionGuide": m16,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
@@ -415,11 +440,11 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
"システムの終了イベントを変更",
),
- "minutesAgo": m12,
+ "minutesAgo": m17,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
"mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
- "monthsAgo": m13,
+ "monthsAgo": m18,
"more": MessageLookupByLibrary.simpleMessage("詳細"),
"name": MessageLookupByLibrary.simpleMessage("名前"),
"nameserver": MessageLookupByLibrary.simpleMessage("ネームサーバー"),
@@ -453,8 +478,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルがありません。追加してください",
),
- "nullTip": m14,
- "numberTip": m15,
+ "nullTip": m19,
+ "numberTip": m20,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
@@ -497,7 +522,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("ポート"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
- "portTip": m16,
+ "portTip": m21,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
"prerequisites": MessageLookupByLibrary.simpleMessage("Prerequisites"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
@@ -716,6 +741,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"search": MessageLookupByLibrary.simpleMessage("検索"),
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
+ "secondsCount": m22,
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
"selectProxies": MessageLookupByLibrary.simpleMessage("プロキシを選択"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
@@ -727,7 +753,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"selectSubRule": MessageLookupByLibrary.simpleMessage("サブルールを選択してください"),
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
- "selectedCountTitle": m17,
+ "selectedCountTitle": m23,
"settings": MessageLookupByLibrary.simpleMessage("設定"),
"show": MessageLookupByLibrary.simpleMessage("表示"),
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
@@ -809,9 +835,10 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
- "urlTip": m18,
+ "urlTip": m24,
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
+ "userAgent": MessageLookupByLibrary.simpleMessage("ユーザーエージェント"),
"value": MessageLookupByLibrary.simpleMessage("値"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("ビブラント"),
"view": MessageLookupByLibrary.simpleMessage("表示"),
@@ -824,7 +851,7 @@ class MessageLookup extends MessageLookupByLibrary {
"vpnTip": MessageLookupByLibrary.simpleMessage("変更はVPN再起動後に有効"),
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV設定"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("ホワイトリストモード"),
- "yearsAgo": m19,
+ "yearsAgo": m25,
"zh_CN": MessageLookupByLibrary.simpleMessage("簡体字中国語"),
};
}
diff --git a/lib/l10n/intl/messages_ru.dart b/lib/l10n/intl/messages_ru.dart
index 0295bcf5..adf4d6de 100644
--- a/lib/l10n/intl/messages_ru.dart
+++ b/lib/l10n/intl/messages_ru.dart
@@ -32,40 +32,52 @@ class MessageLookup extends MessageLookupByLibrary {
static String m4(label) => "${label} не может быть пустым";
- static String m5(label) => "Текущий ${label} уже существует";
+ static String m5(count) => "${count} записей";
- static String m6(count) =>
+ static String m6(label) => "Текущий ${label} уже существует";
+
+ static String m7(name) => "${name} пропущено";
+
+ static String m8(name) => "${name} обновлено";
+
+ static String m9(name) => "Обновление ${name}...";
+
+ static String m10(count) =>
"${Intl.plural(count, one: '${count} час назад', few: '${count} часа назад', many: '${count} часов назад', other: '${count} часа назад')}";
- static String m7(target) => "${target} является недопустимой политикой";
+ static String m11(count) => "${count} часов";
- static String m8(proxyName) => "${proxyName} является недопустимым прокси";
+ static String m12(target) => "${target} является недопустимой политикой";
- static String m9(providerName) =>
+ static String m13(proxyName) => "${proxyName} является недопустимым прокси";
+
+ static String m14(providerName) =>
"${providerName} является недопустимым провайдером прокси";
- static String m10(subRule) => "${subRule} является недопустимым подправилом";
+ static String m15(subRule) => "${subRule} является недопустимым подправилом";
- static String m11(appName) =>
+ static String m16(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) =>
+ static String m17(count) =>
"${Intl.plural(count, one: '${count} минута назад', few: '${count} минуты назад', many: '${count} минут назад', other: '${count} минуты назад')}";
- static String m13(count) =>
+ static String m18(count) =>
"${Intl.plural(count, one: '${count} месяц назад', few: '${count} месяца назад', many: '${count} месяцев назад', other: '${count} месяца назад')}";
- static String m14(label) => "${label} пока отсутствуют";
+ static String m19(label) => "${label} пока отсутствуют";
- static String m15(label) => "${label} должно быть числом";
+ static String m20(label) => "${label} должно быть числом";
- static String m16(label) => "${label} должен быть числом от 1024 до 49151";
+ static String m21(label) => "${label} должен быть числом от 1024 до 49151";
- static String m17(count) => "Выбрано ${count} элементов";
+ static String m22(count) => "${count} секунд";
- static String m18(label) => "${label} должен быть URL";
+ static String m23(count) => "Выбрано ${count} элементов";
- static String m19(count) =>
+ static String m24(label) => "${label} должен быть URL";
+
+ static String m25(count) =>
"${Intl.plural(count, one: '${count} год назад', few: '${count} года назад', many: '${count} лет назад', other: '${count} года назад')}";
final messages = _notInlinedMessages(_notInlinedMessages);
@@ -352,6 +364,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("Английский"),
"entries": MessageLookupByLibrary.simpleMessage(" записей"),
+ "entriesCount": m5,
"exclude": MessageLookupByLibrary.simpleMessage(
"Скрыть из последних задач",
),
@@ -366,7 +379,7 @@ class MessageLookup extends MessageLookupByLibrary {
"When connected to an excluded SSID Wi-Fi, the app running state will be automatically switched.",
),
"excludeType": MessageLookupByLibrary.simpleMessage("Тип исключения"),
- "existsTip": m5,
+ "existsTip": m6,
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("Ожидаемый статус"),
@@ -409,6 +422,18 @@ class MessageLookup extends MessageLookupByLibrary {
),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("Фруктовый микс"),
"general": MessageLookupByLibrary.simpleMessage("Общие"),
+ "geoAutoUpdate": MessageLookupByLibrary.simpleMessage("Автообновление"),
+ "geoAutoUpdateInterval": MessageLookupByLibrary.simpleMessage(
+ "Интервал автообновления",
+ ),
+ "geoAutoUpdateIntervalTip": MessageLookupByLibrary.simpleMessage(
+ "Интервал автообновления должен быть больше 0",
+ ),
+ "geoOptions": MessageLookupByLibrary.simpleMessage("Настройки Geo"),
+ "geoResources": MessageLookupByLibrary.simpleMessage("Ресурсы Geo"),
+ "geoSkipped": m7,
+ "geoUpdated": m8,
+ "geoUpdating": m9,
"geodataLoader": MessageLookupByLibrary.simpleMessage(
"Режим низкого потребления памяти для геоданных",
),
@@ -437,7 +462,9 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
"Использование клавиатуры для управления приложением",
),
- "hoursAgo": m6,
+ "hours": MessageLookupByLibrary.simpleMessage("часов"),
+ "hoursAgo": m10,
+ "hoursCount": m11,
"icon": MessageLookupByLibrary.simpleMessage("Иконка"),
"iconRecords": MessageLookupByLibrary.simpleMessage("История иконок"),
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
@@ -483,10 +510,10 @@ class MessageLookup extends MessageLookupByLibrary {
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Неверный файл резервной копии",
),
- "invalidPolicy": m7,
- "invalidProxy": m8,
- "invalidProxyProvider": m9,
- "invalidSubRule": m10,
+ "invalidPolicy": m12,
+ "invalidProxy": m13,
+ "invalidProxyProvider": m14,
+ "invalidSubRule": m15,
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"При включении будет возможно получать IPv6 трафик",
@@ -520,7 +547,7 @@ class MessageLookup extends MessageLookupByLibrary {
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"According to system requirements, obtaining the Wi-Fi name requires you to grant location permission.",
),
- "locationPermissionGuide": m11,
+ "locationPermissionGuide": m16,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"Location Permission Required",
),
@@ -558,11 +585,11 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
"Изменить стандартное событие выхода из системы",
),
- "minutesAgo": m12,
+ "minutesAgo": m17,
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
- "monthsAgo": m13,
+ "monthsAgo": m18,
"more": MessageLookupByLibrary.simpleMessage("Еще"),
"name": MessageLookupByLibrary.simpleMessage("Имя"),
"nameserver": MessageLookupByLibrary.simpleMessage("Сервер имен"),
@@ -608,8 +635,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"Нет профиля, пожалуйста, добавьте профиль",
),
- "nullTip": m14,
- "numberTip": m15,
+ "nullTip": m19,
+ "numberTip": m20,
"onDemand": MessageLookupByLibrary.simpleMessage("On Demand"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage(
"Configure the program running state for specific scenarios",
@@ -666,7 +693,7 @@ class MessageLookup extends MessageLookupByLibrary {
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Введите другой порт",
),
- "portTip": m16,
+ "portTip": m21,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Приоритетное использование HTTP/3 для DOH",
),
@@ -935,6 +962,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
+ "secondsCount": m22,
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
"selectProxies": MessageLookupByLibrary.simpleMessage("Выбрать прокси"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage(
@@ -950,7 +978,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Пожалуйста, выберите подправило",
),
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
- "selectedCountTitle": m17,
+ "selectedCountTitle": m23,
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"show": MessageLookupByLibrary.simpleMessage("Показать"),
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
@@ -1064,11 +1092,12 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Получить профиль через URL",
),
- "urlTip": m18,
+ "urlTip": m24,
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
"Использовать системные hosts",
),
+ "userAgent": MessageLookupByLibrary.simpleMessage("User-Agent"),
"value": MessageLookupByLibrary.simpleMessage("Значение"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Яркие"),
"view": MessageLookupByLibrary.simpleMessage("Просмотр"),
@@ -1087,7 +1116,7 @@ class MessageLookup extends MessageLookupByLibrary {
"whitelistMode": MessageLookupByLibrary.simpleMessage(
"Режим белого списка",
),
- "yearsAgo": m19,
+ "yearsAgo": m25,
"zh_CN": MessageLookupByLibrary.simpleMessage("Упрощенный китайский"),
};
}
diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart
index 3797ff58..e1982612 100644
--- a/lib/l10n/intl/messages_zh_CN.dart
+++ b/lib/l10n/intl/messages_zh_CN.dart
@@ -30,36 +30,48 @@ class MessageLookup extends MessageLookupByLibrary {
static String m4(label) => "${label}不能为空";
- static String m5(label) => "${label}当前已存在";
+ static String m5(count) => "${count} 个条目";
- static String m6(count) => "${count} 小时前";
+ static String m6(label) => "${label}当前已存在";
- static String m7(target) => "${target} 是一个无效的策略";
+ static String m7(name) => "${name} 已跳过";
- static String m8(proxyName) => "${proxyName} 是一个无效的代理";
+ static String m8(name) => "${name} 已更新";
- static String m9(providerName) => "${providerName} 是一个无效的代理集";
+ static String m9(name) => "正在更新 ${name}...";
- static String m10(subRule) => "${subRule} 是一个无效的SUB_RULE";
+ static String m10(count) => "${count} 小时前";
- static String m11(appName) =>
+ static String m11(count) => "${count} 小时";
+
+ static String m12(target) => "${target} 是一个无效的策略";
+
+ static String m13(proxyName) => "${proxyName} 是一个无效的代理";
+
+ static String m14(providerName) => "${providerName} 是一个无效的代理集";
+
+ static String m15(subRule) => "${subRule} 是一个无效的SUB_RULE";
+
+ static String m16(appName) =>
"1. 打开 系统设置 > 隐私与安全性\n2. 选择 定位服务\n3. 在右侧列表中找到并勾选 ${appName}\n\n完成设置后,返回应用即可正常使用。感谢您的配合。";
- static String m12(count) => "${count} 分钟前";
+ static String m17(count) => "${count} 分钟前";
- static String m13(count) => "${count} 个月前";
+ static String m18(count) => "${count} 个月前";
- static String m14(label) => "暂无${label}";
+ static String m19(label) => "暂无${label}";
- static String m15(label) => "${label}必须为数字";
+ static String m20(label) => "${label}必须为数字";
- static String m16(label) => "${label} 必须在 1024 到 49151 之间";
+ static String m21(label) => "${label} 必须在 1024 到 49151 之间";
- static String m17(count) => "已选择 ${count} 项";
+ static String m22(count) => "${count} 秒";
- static String m18(label) => "${label}必须为URL";
+ static String m23(count) => "已选择 ${count} 项";
- static String m19(count) => "${count} 年前";
+ static String m24(label) => "${label}必须为URL";
+
+ static String m25(count) => "${count} 年前";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map _notInlinedMessages(_) => {
@@ -237,6 +249,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emptyTip": m4,
"en": MessageLookupByLibrary.simpleMessage("英语"),
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
+ "entriesCount": m5,
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
"excludeProxyFilter": MessageLookupByLibrary.simpleMessage("排除节点过滤器"),
@@ -245,7 +258,7 @@ class MessageLookup extends MessageLookupByLibrary {
"连接到被排除SSID的WIFI时,将会自动切换应用运行状态",
),
"excludeType": MessageLookupByLibrary.simpleMessage("排除类型"),
- "existsTip": m5,
+ "existsTip": m6,
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expectedStatus": MessageLookupByLibrary.simpleMessage("预期状态"),
@@ -274,6 +287,16 @@ class MessageLookup extends MessageLookupByLibrary {
"forceRestartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要强制重启核心吗?"),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("果缤纷"),
"general": MessageLookupByLibrary.simpleMessage("常规"),
+ "geoAutoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
+ "geoAutoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔"),
+ "geoAutoUpdateIntervalTip": MessageLookupByLibrary.simpleMessage(
+ "自动更新间隔必须大于0",
+ ),
+ "geoOptions": MessageLookupByLibrary.simpleMessage("Geo 选项"),
+ "geoResources": MessageLookupByLibrary.simpleMessage("Geo 资源"),
+ "geoSkipped": m7,
+ "geoUpdated": m8,
+ "geoUpdating": m9,
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"),
@@ -288,7 +311,9 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("快捷键冲突"),
"hotkeyManagement": MessageLookupByLibrary.simpleMessage("快捷键管理"),
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage("使用键盘控制应用程序"),
- "hoursAgo": m6,
+ "hours": MessageLookupByLibrary.simpleMessage("小时"),
+ "hoursAgo": m10,
+ "hoursCount": m11,
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconRecords": MessageLookupByLibrary.simpleMessage("图标记录"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
@@ -316,10 +341,10 @@ class MessageLookup extends MessageLookupByLibrary {
"interval": MessageLookupByLibrary.simpleMessage("间隔"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("无效备份文件"),
- "invalidPolicy": m7,
- "invalidProxy": m8,
- "invalidProxyProvider": m9,
- "invalidSubRule": m10,
+ "invalidPolicy": m12,
+ "invalidProxy": m13,
+ "invalidProxyProvider": m14,
+ "invalidSubRule": m15,
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
@@ -343,7 +368,7 @@ class MessageLookup extends MessageLookupByLibrary {
"locationPermissionDesc": MessageLookupByLibrary.simpleMessage(
"根据系统要求,获取Wi-Fi名称需要您授予位置权限。",
),
- "locationPermissionGuide": m11,
+ "locationPermissionGuide": m16,
"locationPermissionRequired": MessageLookupByLibrary.simpleMessage(
"需要位置权限",
),
@@ -365,11 +390,11 @@ class MessageLookup extends MessageLookupByLibrary {
"min": MessageLookupByLibrary.simpleMessage("最小"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
- "minutesAgo": m12,
+ "minutesAgo": m17,
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
- "monthsAgo": m13,
+ "monthsAgo": m18,
"more": MessageLookupByLibrary.simpleMessage("更多"),
"name": MessageLookupByLibrary.simpleMessage("名称"),
"nameserver": MessageLookupByLibrary.simpleMessage("域名服务器"),
@@ -395,8 +420,8 @@ class MessageLookup extends MessageLookupByLibrary {
"none": MessageLookupByLibrary.simpleMessage("无"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
- "nullTip": m14,
- "numberTip": m15,
+ "nullTip": m19,
+ "numberTip": m20,
"onDemand": MessageLookupByLibrary.simpleMessage("按需运行"),
"onDemandDesc": MessageLookupByLibrary.simpleMessage("配置程序特定场景运行状态"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
@@ -431,7 +456,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"port": MessageLookupByLibrary.simpleMessage("端口"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
- "portTip": m16,
+ "portTip": m21,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
"prerequisites": MessageLookupByLibrary.simpleMessage("前置条件"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
@@ -614,6 +639,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
+ "secondsCount": m22,
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selectProxies": MessageLookupByLibrary.simpleMessage("选择代理"),
"selectProxyProviders": MessageLookupByLibrary.simpleMessage("选择代理集"),
@@ -621,7 +647,7 @@ class MessageLookup extends MessageLookupByLibrary {
"selectSplitStrategy": MessageLookupByLibrary.simpleMessage("请选择分流策略"),
"selectSubRule": MessageLookupByLibrary.simpleMessage("请选择子规则"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
- "selectedCountTitle": m17,
+ "selectedCountTitle": m23,
"settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
@@ -697,9 +723,10 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("上传"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
- "urlTip": m18,
+ "urlTip": m24,
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
+ "userAgent": MessageLookupByLibrary.simpleMessage("用户代理"),
"value": MessageLookupByLibrary.simpleMessage("值"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("活力"),
"view": MessageLookupByLibrary.simpleMessage("查看"),
@@ -712,7 +739,7 @@ class MessageLookup extends MessageLookupByLibrary {
"vpnTip": MessageLookupByLibrary.simpleMessage("重启VPN后改变生效"),
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
- "yearsAgo": m19,
+ "yearsAgo": m25,
"zh_CN": MessageLookupByLibrary.simpleMessage("中文简体"),
};
}
diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart
index 52825a80..3b96b136 100644
--- a/lib/l10n/l10n.dart
+++ b/lib/l10n/l10n.dart
@@ -1039,6 +1039,11 @@ class AppLocalizations {
);
}
+ /// `User-Agent`
+ String get userAgent {
+ return Intl.message('User-Agent', name: 'userAgent', desc: '', args: []);
+ }
+
/// `Unified delay`
String get unifiedDelay {
return Intl.message(
@@ -4378,6 +4383,116 @@ class AppLocalizations {
String get suspended {
return Intl.message('Suspended...', name: 'suspended', desc: '', args: []);
}
+
+ /// `Geo Options`
+ String get geoOptions {
+ return Intl.message('Geo Options', name: 'geoOptions', desc: '', args: []);
+ }
+
+ /// `Auto Update`
+ String get geoAutoUpdate {
+ return Intl.message(
+ 'Auto Update',
+ name: 'geoAutoUpdate',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Auto Update Interval`
+ String get geoAutoUpdateInterval {
+ return Intl.message(
+ 'Auto Update Interval',
+ name: 'geoAutoUpdateInterval',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Auto update interval must be greater than 0`
+ String get geoAutoUpdateIntervalTip {
+ return Intl.message(
+ 'Auto update interval must be greater than 0',
+ name: 'geoAutoUpdateIntervalTip',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `hours`
+ String get hours {
+ return Intl.message('hours', name: 'hours', desc: '', args: []);
+ }
+
+ /// `{count} hours`
+ String hoursCount(Object count) {
+ return Intl.message(
+ '$count hours',
+ name: 'hoursCount',
+ desc: '',
+ args: [count],
+ );
+ }
+
+ /// `Geo Resources`
+ String get geoResources {
+ return Intl.message(
+ 'Geo Resources',
+ name: 'geoResources',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Updating {name}...`
+ String geoUpdating(Object name) {
+ return Intl.message(
+ 'Updating $name...',
+ name: 'geoUpdating',
+ desc: '',
+ args: [name],
+ );
+ }
+
+ /// `{name} skipped`
+ String geoSkipped(Object name) {
+ return Intl.message(
+ '$name skipped',
+ name: 'geoSkipped',
+ desc: '',
+ args: [name],
+ );
+ }
+
+ /// `{name} updated`
+ String geoUpdated(Object name) {
+ return Intl.message(
+ '$name updated',
+ name: 'geoUpdated',
+ desc: '',
+ args: [name],
+ );
+ }
+
+ /// `{count} seconds`
+ String secondsCount(Object count) {
+ return Intl.message(
+ '$count seconds',
+ name: 'secondsCount',
+ desc: '',
+ args: [count],
+ );
+ }
+
+ /// `{count} entries`
+ String entriesCount(Object count) {
+ return Intl.message(
+ '$count entries',
+ name: 'entriesCount',
+ desc: '',
+ args: [count],
+ );
+ }
}
class AppLocalizationDelegate extends LocalizationsDelegate {
diff --git a/lib/main.dart b/lib/main.dart
index f5d4f1e5..ebe8da09 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -11,8 +11,8 @@ import 'application.dart';
import 'common/common.dart';
Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
try {
- WidgetsFlutterBinding.ensureInitialized();
if (system.isDesktop) {
await RustLib.init();
}
@@ -26,7 +26,7 @@ Future main() async {
),
);
} catch (e, s) {
- return runApp(
+ runApp(
MaterialApp(
home: InitErrorScreen(error: e, stack: s),
),
diff --git a/lib/manager/core_manager.dart b/lib/manager/core_manager.dart
index 2ccf7e48..0ae0927a 100644
--- a/lib/manager/core_manager.dart
+++ b/lib/manager/core_manager.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
@@ -72,7 +74,7 @@ class _CoreContainerState extends ConsumerState
@override
void onLog(Log log) {
- // ref.read(logsProvider.notifier).add(log);
+ ref.read(logsProvider.notifier).add(log);
if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload);
}
@@ -109,4 +111,23 @@ class _CoreContainerState extends ConsumerState
await coreController.shutdown(false);
super.onCrash(message);
}
+
+ @override
+ void onGeoUpdate(String geoType, bool updating, bool skipped, String? error) {
+ final geoResource = GeoResource.fromJson(geoType.toLowerCase());
+ final key = geoResource.updatingKey;
+ final l10n = currentAppLocalizations;
+ if (updating) {
+ globalState.showNotifier(l10n.geoUpdating(geoResource.name));
+ } else if (skipped) {
+ globalState.showNotifier(l10n.geoSkipped(geoResource.name));
+ } else {
+ globalState.showNotifier(l10n.geoUpdated(geoResource.name));
+ }
+ ref.read(isUpdatingProvider(key).notifier).value = updating;
+ if (!updating && error != null && error.isNotEmpty) {
+ globalState.showNotifier(error);
+ }
+ super.onGeoUpdate(geoType, updating, skipped, error);
+ }
}
diff --git a/lib/manager/hotkey_manager.dart b/lib/manager/hotkey_manager.dart
index 2b5e7453..f70f0e53 100644
--- a/lib/manager/hotkey_manager.dart
+++ b/lib/manager/hotkey_manager.dart
@@ -73,7 +73,7 @@ class _HotKeyManagerState extends ConsumerState {
await Future.wait(hotkeyActionHandles);
}
- Shortcuts _buildShortcuts(Widget child) {
+ Shortcuts _buildCloseShortcuts(Widget child) {
return Shortcuts(
shortcuts: {
utils.controlSingleActivator(LogicalKeyboardKey.keyW):
@@ -84,7 +84,7 @@ class _HotKeyManagerState extends ConsumerState {
CloseWindowIntent: CallbackAction(
onInvoke: (_) => globalState.container
.read(systemActionProvider.notifier)
- .handleBackOrExit(),
+ .handleClose(false),
),
DoNothingIntent: CallbackAction(
onInvoke: (_) => null,
@@ -97,6 +97,6 @@ class _HotKeyManagerState extends ConsumerState {
@override
Widget build(BuildContext context) {
- return _buildShortcuts(widget.child);
+ return _buildCloseShortcuts(widget.child);
}
}
diff --git a/lib/manager/theme_manager.dart b/lib/manager/theme_manager.dart
index 11554208..1b6388ef 100644
--- a/lib/manager/theme_manager.dart
+++ b/lib/manager/theme_manager.dart
@@ -3,7 +3,6 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/theme.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';
import 'package:flutter/material.dart';
@@ -21,41 +20,25 @@ class ThemeManager extends ConsumerWidget {
if (!system.isAndroid) {
return child;
}
- return AnnotatedRegion(
- sized: false,
- value: SystemUiMode.edgeToEdge,
- child: Consumer(
- builder: (context, ref, _) {
- final brightness = ref.watch(currentBrightnessProvider);
- final iconBrightness = brightness == Brightness.light
- ? Brightness.dark
- : Brightness.light;
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ref
- .read(systemUiOverlayStyleStateProvider.notifier)
- .update(
- (state) => state.copyWith(
- statusBarColor: Colors.transparent,
- statusBarIconBrightness: iconBrightness,
- systemNavigationBarIconBrightness: iconBrightness,
- systemNavigationBarColor: context.colorScheme.surface,
- systemNavigationBarDividerColor: Colors.transparent,
- ),
- );
- });
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- statusBarColor: Colors.transparent,
- statusBarIconBrightness: iconBrightness,
- systemNavigationBarIconBrightness: iconBrightness,
- systemNavigationBarColor: context.colorScheme.surface,
- systemNavigationBarDividerColor: Colors.transparent,
- ),
- sized: false,
- child: child,
- );
- },
- ),
+ return Consumer(
+ builder: (context, ref, _) {
+ final brightness = ref.watch(currentBrightnessProvider);
+ final iconBrightness = brightness == Brightness.light
+ ? Brightness.dark
+ : Brightness.light;
+ return AnnotatedRegion(
+ value: SystemUiOverlayStyle(
+ statusBarColor: Colors.transparent,
+ statusBarIconBrightness: iconBrightness,
+ systemNavigationBarIconBrightness: iconBrightness,
+ systemNavigationBarColor: Colors.transparent,
+ systemNavigationBarDividerColor: Colors.transparent,
+ systemNavigationBarContrastEnforced: false,
+ ),
+ sized: false,
+ child: child,
+ );
+ },
);
}
diff --git a/lib/manager/window_manager.dart b/lib/manager/window_manager.dart
index 17716e29..ca81cd14 100644
--- a/lib/manager/window_manager.dart
+++ b/lib/manager/window_manager.dart
@@ -44,7 +44,7 @@ class _WindowContainerState extends ConsumerState
@override
void onWindowClose() async {
- await ref.read(systemActionProvider.notifier).handleBackOrExit();
+ await ref.read(systemActionProvider.notifier).handleClose();
super.onWindowClose();
}
@@ -178,7 +178,10 @@ class _WindowHeaderState extends State {
windowExtManager.setWindowCornerPreference(round: false);
}
}
- isMaximizedNotifier.value = await windowManager.isMaximized();
+ final res = await windowManager.isMaximized();
+ if (mounted) {
+ isMaximizedNotifier.value = res;
+ }
}
Future _updatePin() async {
@@ -226,7 +229,7 @@ class _WindowHeaderState extends State {
onPressed: () {
globalState.container
.read(systemActionProvider.notifier)
- .handleBackOrExit();
+ .handleClose();
},
icon: const Icon(Icons.close),
),
diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart
index 552fee87..19fe746e 100644
--- a/lib/models/clash_config.dart
+++ b/lib/models/clash_config.dart
@@ -3,13 +3,23 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/clash_config.freezed.dart';
+
part 'generated/clash_config.g.dart';
const defaultClashConfig = PatchClashConfig();
const defaultTun = Tun();
const defaultDns = Dns();
-const defaultGeoXUrl = GeoXUrl();
+const defaultGeoXUrl = {
+ GeoResource.MMDB:
+ 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
+ GeoResource.ASN:
+ 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
+ GeoResource.GEOIP:
+ 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
+ GeoResource.GEOSITE:
+ 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat',
+};
const defaultMixedPort = 7890;
const defaultKeepAliveInterval = 30;
@@ -309,42 +319,6 @@ abstract class Dns with _$Dns {
}
}
-@freezed
-abstract class GeoXUrl with _$GeoXUrl {
- const factory GeoXUrl({
- @Default(
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
- )
- String mmdb,
- @Default(
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
- )
- String asn,
- @Default(
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
- )
- String geoip,
- @Default(
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat',
- )
- String geosite,
- }) = _GeoXUrl;
-
- factory GeoXUrl.fromJson(Map json) =>
- _$GeoXUrlFromJson(json);
-
- factory GeoXUrl.safeFormJson(Map? json) {
- if (json == null) {
- return defaultGeoXUrl;
- }
- try {
- return GeoXUrl.fromJson(json);
- } catch (_) {
- return defaultGeoXUrl;
- }
- }
-}
-
@freezed
abstract class Rule with _$Rule {
const factory Rule({
@@ -542,6 +516,24 @@ abstract class ClashConfig with _$ClashConfig {
_$ClashConfigFromJson(json);
}
+extension GeoResourceUrlMapExt on Map {
+ Map get raw =>
+ map((key, value) => MapEntry(key.value, value));
+}
+
+Map _geoXUrlFromJson(Map? json) {
+ if (json == null) {
+ return defaultGeoXUrl;
+ }
+ return json.map(
+ (key, value) => MapEntry(GeoResource.fromJson(key), value as String),
+ );
+}
+
+Map _geoXUrlToJson(Map value) {
+ return value.raw;
+}
+
@freezed
abstract class PatchClashConfig with _$PatchClashConfig {
const factory PatchClashConfig({
@@ -568,8 +560,12 @@ abstract class PatchClashConfig with _$PatchClashConfig {
@Default(defaultTun) @JsonKey(fromJson: Tun.safeFormJson) Tun tun,
@Default(defaultDns) @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,
@Default(defaultGeoXUrl)
- @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson)
- GeoXUrl geoXUrl,
+ @JsonKey(
+ name: 'geox-url',
+ fromJson: _geoXUrlFromJson,
+ toJson: _geoXUrlToJson,
+ )
+ Map geoXUrl,
@Default(GeodataLoader.memconservative)
@JsonKey(name: 'geodata-loader')
GeodataLoader geodataLoader,
@@ -578,6 +574,8 @@ abstract class PatchClashConfig with _$PatchClashConfig {
@JsonKey(name: 'external-controller')
ExternalControllerStatus externalController,
@Default({}) Map hosts,
+ @Default(false) @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,
+ @Default(24) @JsonKey(name: 'geo-update-interval') int geoUpdateInterval,
}) = _PatchClashConfig;
factory PatchClashConfig.fromJson(Map json) =>
diff --git a/lib/models/common.dart b/lib/models/common.dart
index b2d89a9b..aa88a7ff 100644
--- a/lib/models/common.dart
+++ b/lib/models/common.dart
@@ -9,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'clash_config.dart';
part 'generated/common.freezed.dart';
+
part 'generated/common.g.dart';
@freezed
@@ -271,7 +272,7 @@ extension TrafficExt on Traffic {
}
String get trayTitle {
- return '${up.shortTraffic.show}/s \n ${down.shortTraffic.show}/s';
+ return '${up.shortTraffic.show}/s\n${down.shortTraffic.show}/s';
}
num get speed => up + down;
diff --git a/lib/models/config.dart b/lib/models/config.dart
index d9fc3918..a71ee4e6 100644
--- a/lib/models/config.dart
+++ b/lib/models/config.dart
@@ -84,6 +84,7 @@ abstract class AppSettingProps with _$AppSettingProps {
@Default(false) bool developerMode,
@Default(RestoreStrategy.compatible) RestoreStrategy restoreStrategy,
@Default(true) bool showTrayTitle,
+ @Default('') String customUserAgent,
}) = _AppSettingProps;
factory AppSettingProps.fromJson(Map json) =>
diff --git a/lib/models/core.dart b/lib/models/core.dart
index 77bc28b6..7cd12576 100644
--- a/lib/models/core.dart
+++ b/lib/models/core.dart
@@ -31,6 +31,8 @@ abstract class UpdateParams with _$UpdateParams {
@JsonKey(name: 'external-controller')
required ExternalControllerStatus externalController,
@JsonKey(name: 'unified-delay') required bool unifiedDelay,
+ @Default(false) @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,
+ @Default(24) @JsonKey(name: 'geo-update-interval') int geoUpdateInterval,
}) = _UpdateParams;
factory UpdateParams.fromJson(Map json) =>
diff --git a/lib/models/generated/clash_config.freezed.dart b/lib/models/generated/clash_config.freezed.dart
index 3a1143d1..b33a1efc 100644
--- a/lib/models/generated/clash_config.freezed.dart
+++ b/lib/models/generated/clash_config.freezed.dart
@@ -3000,278 +3000,6 @@ $FallbackFilterCopyWith<$Res> get fallbackFilter {
}
-/// @nodoc
-mixin _$GeoXUrl {
-
- String get mmdb; String get asn; String get geoip; String get geosite;
-/// Create a copy of GeoXUrl
-/// with the given fields replaced by the non-null parameter values.
-@JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-$GeoXUrlCopyWith get copyWith => _$GeoXUrlCopyWithImpl(this as GeoXUrl, _$identity);
-
- /// Serializes this GeoXUrl to a JSON map.
- Map toJson();
-
-
-@override
-bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is GeoXUrl&&(identical(other.mmdb, mmdb) || other.mmdb == mmdb)&&(identical(other.asn, asn) || other.asn == asn)&&(identical(other.geoip, geoip) || other.geoip == geoip)&&(identical(other.geosite, geosite) || other.geosite == geosite));
-}
-
-@JsonKey(includeFromJson: false, includeToJson: false)
-@override
-int get hashCode => Object.hash(runtimeType,mmdb,asn,geoip,geosite);
-
-@override
-String toString() {
- return 'GeoXUrl(mmdb: $mmdb, asn: $asn, geoip: $geoip, geosite: $geosite)';
-}
-
-
-}
-
-/// @nodoc
-abstract mixin class $GeoXUrlCopyWith<$Res> {
- factory $GeoXUrlCopyWith(GeoXUrl value, $Res Function(GeoXUrl) _then) = _$GeoXUrlCopyWithImpl;
-@useResult
-$Res call({
- String mmdb, String asn, String geoip, String geosite
-});
-
-
-
-
-}
-/// @nodoc
-class _$GeoXUrlCopyWithImpl<$Res>
- implements $GeoXUrlCopyWith<$Res> {
- _$GeoXUrlCopyWithImpl(this._self, this._then);
-
- final GeoXUrl _self;
- final $Res Function(GeoXUrl) _then;
-
-/// Create a copy of GeoXUrl
-/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? mmdb = null,Object? asn = null,Object? geoip = null,Object? geosite = null,}) {
- return _then(_self.copyWith(
-mmdb: null == mmdb ? _self.mmdb : mmdb // ignore: cast_nullable_to_non_nullable
-as String,asn: null == asn ? _self.asn : asn // ignore: cast_nullable_to_non_nullable
-as String,geoip: null == geoip ? _self.geoip : geoip // ignore: cast_nullable_to_non_nullable
-as String,geosite: null == geosite ? _self.geosite : geosite // ignore: cast_nullable_to_non_nullable
-as String,
- ));
-}
-
-}
-
-
-/// Adds pattern-matching-related methods to [GeoXUrl].
-extension GeoXUrlPatterns on GeoXUrl {
-/// A variant of `map` that fallback to returning `orElse`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case _:
-/// return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeMap(TResult Function( _GeoXUrl value)? $default,{required TResult orElse(),}){
-final _that = this;
-switch (_that) {
-case _GeoXUrl() when $default != null:
-return $default(_that);case _:
- return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// Callbacks receives the raw object, upcasted.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case final Subclass2 value:
-/// return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult map(TResult Function( _GeoXUrl value) $default,){
-final _that = this;
-switch (_that) {
-case _GeoXUrl():
-return $default(_that);case _:
- throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `map` that fallback to returning `null`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case _:
-/// return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GeoXUrl value)? $default,){
-final _that = this;
-switch (_that) {
-case _GeoXUrl() when $default != null:
-return $default(_that);case _:
- return null;
-
-}
-}
-/// A variant of `when` that fallback to an `orElse` callback.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case _:
-/// return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeWhen(TResult Function( String mmdb, String asn, String geoip, String geosite)? $default,{required TResult orElse(),}) {final _that = this;
-switch (_that) {
-case _GeoXUrl() when $default != null:
-return $default(_that.mmdb,_that.asn,_that.geoip,_that.geosite);case _:
- return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// As opposed to `map`, this offers destructuring.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case Subclass2(:final field2):
-/// return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult when(TResult Function( String mmdb, String asn, String geoip, String geosite) $default,) {final _that = this;
-switch (_that) {
-case _GeoXUrl():
-return $default(_that.mmdb,_that.asn,_that.geoip,_that.geosite);case _:
- throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `when` that fallback to returning `null`
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case _:
-/// return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? whenOrNull(TResult? Function( String mmdb, String asn, String geoip, String geosite)? $default,) {final _that = this;
-switch (_that) {
-case _GeoXUrl() when $default != null:
-return $default(_that.mmdb,_that.asn,_that.geoip,_that.geosite);case _:
- return null;
-
-}
-}
-
-}
-
-/// @nodoc
-@JsonSerializable()
-
-class _GeoXUrl implements GeoXUrl {
- const _GeoXUrl({this.mmdb = 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb', this.asn = 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb', this.geoip = 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat', this.geosite = 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'});
- factory _GeoXUrl.fromJson(Map json) => _$GeoXUrlFromJson(json);
-
-@override@JsonKey() final String mmdb;
-@override@JsonKey() final String asn;
-@override@JsonKey() final String geoip;
-@override@JsonKey() final String geosite;
-
-/// Create a copy of GeoXUrl
-/// with the given fields replaced by the non-null parameter values.
-@override @JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-_$GeoXUrlCopyWith<_GeoXUrl> get copyWith => __$GeoXUrlCopyWithImpl<_GeoXUrl>(this, _$identity);
-
-@override
-Map toJson() {
- return _$GeoXUrlToJson(this, );
-}
-
-@override
-bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _GeoXUrl&&(identical(other.mmdb, mmdb) || other.mmdb == mmdb)&&(identical(other.asn, asn) || other.asn == asn)&&(identical(other.geoip, geoip) || other.geoip == geoip)&&(identical(other.geosite, geosite) || other.geosite == geosite));
-}
-
-@JsonKey(includeFromJson: false, includeToJson: false)
-@override
-int get hashCode => Object.hash(runtimeType,mmdb,asn,geoip,geosite);
-
-@override
-String toString() {
- return 'GeoXUrl(mmdb: $mmdb, asn: $asn, geoip: $geoip, geosite: $geosite)';
-}
-
-
-}
-
-/// @nodoc
-abstract mixin class _$GeoXUrlCopyWith<$Res> implements $GeoXUrlCopyWith<$Res> {
- factory _$GeoXUrlCopyWith(_GeoXUrl value, $Res Function(_GeoXUrl) _then) = __$GeoXUrlCopyWithImpl;
-@override @useResult
-$Res call({
- String mmdb, String asn, String geoip, String geosite
-});
-
-
-
-
-}
-/// @nodoc
-class __$GeoXUrlCopyWithImpl<$Res>
- implements _$GeoXUrlCopyWith<$Res> {
- __$GeoXUrlCopyWithImpl(this._self, this._then);
-
- final _GeoXUrl _self;
- final $Res Function(_GeoXUrl) _then;
-
-/// Create a copy of GeoXUrl
-/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? mmdb = null,Object? asn = null,Object? geoip = null,Object? geosite = null,}) {
- return _then(_GeoXUrl(
-mmdb: null == mmdb ? _self.mmdb : mmdb // ignore: cast_nullable_to_non_nullable
-as String,asn: null == asn ? _self.asn : asn // ignore: cast_nullable_to_non_nullable
-as String,geoip: null == geoip ? _self.geoip : geoip // ignore: cast_nullable_to_non_nullable
-as String,geosite: null == geosite ? _self.geosite : geosite // ignore: cast_nullable_to_non_nullable
-as String,
- ));
-}
-
-
-}
-
-
/// @nodoc
mixin _$Rule {
@@ -3885,7 +3613,7 @@ as Map,
/// @nodoc
mixin _$PatchClashConfig {
-@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'socks-port') int get socksPort;@JsonKey(name: 'port') int get port;@JsonKey(name: 'redir-port') int get redirPort;@JsonKey(name: 'tproxy-port') int get tproxyPort; Mode get mode;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode get findProcessMode;@JsonKey(name: 'keep-alive-interval') int get keepAliveInterval;@JsonKey(name: 'unified-delay') bool get unifiedDelay;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(fromJson: Tun.safeFormJson) Tun get tun;@JsonKey(fromJson: Dns.safeDnsFromJson) Dns get dns;@JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl get geoXUrl;@JsonKey(name: 'geodata-loader') GeodataLoader get geodataLoader;@JsonKey(name: 'global-ua') String? get globalUa;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController; Map get hosts;
+@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'socks-port') int get socksPort;@JsonKey(name: 'port') int get port;@JsonKey(name: 'redir-port') int get redirPort;@JsonKey(name: 'tproxy-port') int get tproxyPort; Mode get mode;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode get findProcessMode;@JsonKey(name: 'keep-alive-interval') int get keepAliveInterval;@JsonKey(name: 'unified-delay') bool get unifiedDelay;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(fromJson: Tun.safeFormJson) Tun get tun;@JsonKey(fromJson: Dns.safeDnsFromJson) Dns get dns;@JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map get geoXUrl;@JsonKey(name: 'geodata-loader') GeodataLoader get geodataLoader;@JsonKey(name: 'global-ua') String? get globalUa;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController; Map get hosts;@JsonKey(name: 'geo-auto-update') bool get geoAutoUpdate;@JsonKey(name: 'geo-update-interval') int get geoUpdateInterval;
/// Create a copy of PatchClashConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -3898,16 +3626,16 @@ $PatchClashConfigCopyWith get copyWith => _$PatchClashConfigCo
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is PatchClashConfig&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.socksPort, socksPort) || other.socksPort == socksPort)&&(identical(other.port, port) || other.port == port)&&(identical(other.redirPort, redirPort) || other.redirPort == redirPort)&&(identical(other.tproxyPort, tproxyPort) || other.tproxyPort == tproxyPort)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.keepAliveInterval, keepAliveInterval) || other.keepAliveInterval == keepAliveInterval)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.dns, dns) || other.dns == dns)&&(identical(other.geoXUrl, geoXUrl) || other.geoXUrl == geoXUrl)&&(identical(other.geodataLoader, geodataLoader) || other.geodataLoader == geodataLoader)&&(identical(other.globalUa, globalUa) || other.globalUa == globalUa)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&const DeepCollectionEquality().equals(other.hosts, hosts));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is PatchClashConfig&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.socksPort, socksPort) || other.socksPort == socksPort)&&(identical(other.port, port) || other.port == port)&&(identical(other.redirPort, redirPort) || other.redirPort == redirPort)&&(identical(other.tproxyPort, tproxyPort) || other.tproxyPort == tproxyPort)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.keepAliveInterval, keepAliveInterval) || other.keepAliveInterval == keepAliveInterval)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.dns, dns) || other.dns == dns)&&const DeepCollectionEquality().equals(other.geoXUrl, geoXUrl)&&(identical(other.geodataLoader, geodataLoader) || other.geodataLoader == geodataLoader)&&(identical(other.globalUa, globalUa) || other.globalUa == globalUa)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&const DeepCollectionEquality().equals(other.hosts, hosts)&&(identical(other.geoAutoUpdate, geoAutoUpdate) || other.geoAutoUpdate == geoAutoUpdate)&&(identical(other.geoUpdateInterval, geoUpdateInterval) || other.geoUpdateInterval == geoUpdateInterval));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hashAll([runtimeType,mixedPort,socksPort,port,redirPort,tproxyPort,mode,allowLan,logLevel,ipv6,findProcessMode,keepAliveInterval,unifiedDelay,tcpConcurrent,tun,dns,geoXUrl,geodataLoader,globalUa,externalController,const DeepCollectionEquality().hash(hosts)]);
+int get hashCode => Object.hashAll([runtimeType,mixedPort,socksPort,port,redirPort,tproxyPort,mode,allowLan,logLevel,ipv6,findProcessMode,keepAliveInterval,unifiedDelay,tcpConcurrent,tun,dns,const DeepCollectionEquality().hash(geoXUrl),geodataLoader,globalUa,externalController,const DeepCollectionEquality().hash(hosts),geoAutoUpdate,geoUpdateInterval]);
@override
String toString() {
- return 'PatchClashConfig(mixedPort: $mixedPort, socksPort: $socksPort, port: $port, redirPort: $redirPort, tproxyPort: $tproxyPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)';
+ return 'PatchClashConfig(mixedPort: $mixedPort, socksPort: $socksPort, port: $port, redirPort: $redirPort, tproxyPort: $tproxyPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, globalUa: $globalUa, externalController: $externalController, hosts: $hosts, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval)';
}
@@ -3918,11 +3646,11 @@ abstract mixin class $PatchClashConfigCopyWith<$Res> {
factory $PatchClashConfigCopyWith(PatchClashConfig value, $Res Function(PatchClashConfig) _then) = _$PatchClashConfigCopyWithImpl;
@useResult
$Res call({
-@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'socks-port') int socksPort,@JsonKey(name: 'port') int port,@JsonKey(name: 'redir-port') int redirPort,@JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode,@JsonKey(name: 'keep-alive-interval') int keepAliveInterval,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(fromJson: Tun.safeFormJson) Tun tun,@JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,@JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl geoXUrl,@JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader,@JsonKey(name: 'global-ua') String? globalUa,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts
+@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'socks-port') int socksPort,@JsonKey(name: 'port') int port,@JsonKey(name: 'redir-port') int redirPort,@JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode,@JsonKey(name: 'keep-alive-interval') int keepAliveInterval,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(fromJson: Tun.safeFormJson) Tun tun,@JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,@JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map geoXUrl,@JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader,@JsonKey(name: 'global-ua') String? globalUa,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
});
-$TunCopyWith<$Res> get tun;$DnsCopyWith<$Res> get dns;$GeoXUrlCopyWith<$Res> get geoXUrl;
+$TunCopyWith<$Res> get tun;$DnsCopyWith<$Res> get dns;
}
/// @nodoc
@@ -3935,7 +3663,7 @@ class _$PatchClashConfigCopyWithImpl<$Res>
/// Create a copy of PatchClashConfig
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? mixedPort = null,Object? socksPort = null,Object? port = null,Object? redirPort = null,Object? tproxyPort = null,Object? mode = null,Object? allowLan = null,Object? logLevel = null,Object? ipv6 = null,Object? findProcessMode = null,Object? keepAliveInterval = null,Object? unifiedDelay = null,Object? tcpConcurrent = null,Object? tun = null,Object? dns = null,Object? geoXUrl = null,Object? geodataLoader = null,Object? globalUa = freezed,Object? externalController = null,Object? hosts = null,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? mixedPort = null,Object? socksPort = null,Object? port = null,Object? redirPort = null,Object? tproxyPort = null,Object? mode = null,Object? allowLan = null,Object? logLevel = null,Object? ipv6 = null,Object? findProcessMode = null,Object? keepAliveInterval = null,Object? unifiedDelay = null,Object? tcpConcurrent = null,Object? tun = null,Object? dns = null,Object? geoXUrl = null,Object? geodataLoader = null,Object? globalUa = freezed,Object? externalController = null,Object? hosts = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
return _then(_self.copyWith(
mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
as int,socksPort: null == socksPort ? _self.socksPort : socksPort // ignore: cast_nullable_to_non_nullable
@@ -3953,11 +3681,13 @@ as bool,tcpConcurrent: null == tcpConcurrent ? _self.tcpConcurrent : tcpConcurre
as bool,tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,dns: null == dns ? _self.dns : dns // ignore: cast_nullable_to_non_nullable
as Dns,geoXUrl: null == geoXUrl ? _self.geoXUrl : geoXUrl // ignore: cast_nullable_to_non_nullable
-as GeoXUrl,geodataLoader: null == geodataLoader ? _self.geodataLoader : geodataLoader // ignore: cast_nullable_to_non_nullable
+as Map,geodataLoader: null == geodataLoader ? _self.geodataLoader : geodataLoader // ignore: cast_nullable_to_non_nullable
as GeodataLoader,globalUa: freezed == globalUa ? _self.globalUa : globalUa // ignore: cast_nullable_to_non_nullable
as String?,externalController: null == externalController ? _self.externalController : externalController // ignore: cast_nullable_to_non_nullable
as ExternalControllerStatus,hosts: null == hosts ? _self.hosts : hosts // ignore: cast_nullable_to_non_nullable
-as Map,
+as Map,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
+as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
+as int,
));
}
/// Create a copy of PatchClashConfig
@@ -3978,15 +3708,6 @@ $DnsCopyWith<$Res> get dns {
return $DnsCopyWith<$Res>(_self.dns, (value) {
return _then(_self.copyWith(dns: value));
});
-}/// Create a copy of PatchClashConfig
-/// with the given fields replaced by the non-null parameter values.
-@override
-@pragma('vm:prefer-inline')
-$GeoXUrlCopyWith<$Res> get geoXUrl {
-
- return $GeoXUrlCopyWith<$Res>(_self.geoXUrl, (value) {
- return _then(_self.copyWith(geoXUrl: value));
- });
}
}
@@ -4069,10 +3790,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PatchClashConfig() when $default != null:
-return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts);case _:
+return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return orElse();
}
@@ -4090,10 +3811,10 @@ return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that
/// }
/// ```
-@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts) $default,) {final _that = this;
+@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval) $default,) {final _that = this;
switch (_that) {
case _PatchClashConfig():
-return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts);case _:
+return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
throw StateError('Unexpected subclass');
}
@@ -4110,10 +3831,10 @@ return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'socks-port') int socksPort, @JsonKey(name: 'port') int port, @JsonKey(name: 'redir-port') int redirPort, @JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode, @JsonKey(name: 'keep-alive-interval') int keepAliveInterval, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(fromJson: Tun.safeFormJson) Tun tun, @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns, @JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map geoXUrl, @JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader, @JsonKey(name: 'global-ua') String? globalUa, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,) {final _that = this;
switch (_that) {
case _PatchClashConfig() when $default != null:
-return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts);case _:
+return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that.tproxyPort,_that.mode,_that.allowLan,_that.logLevel,_that.ipv6,_that.findProcessMode,_that.keepAliveInterval,_that.unifiedDelay,_that.tcpConcurrent,_that.tun,_that.dns,_that.geoXUrl,_that.geodataLoader,_that.globalUa,_that.externalController,_that.hosts,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return null;
}
@@ -4125,7 +3846,7 @@ return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that
@JsonSerializable()
class _PatchClashConfig implements PatchClashConfig {
- const _PatchClashConfig({@JsonKey(name: 'mixed-port') this.mixedPort = defaultMixedPort, @JsonKey(name: 'socks-port') this.socksPort = 0, @JsonKey(name: 'port') this.port = 0, @JsonKey(name: 'redir-port') this.redirPort = 0, @JsonKey(name: 'tproxy-port') this.tproxyPort = 0, this.mode = Mode.rule, @JsonKey(name: 'allow-lan') this.allowLan = false, @JsonKey(name: 'log-level') this.logLevel = LogLevel.error, this.ipv6 = false, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) this.findProcessMode = FindProcessMode.always, @JsonKey(name: 'keep-alive-interval') this.keepAliveInterval = defaultKeepAliveInterval, @JsonKey(name: 'unified-delay') this.unifiedDelay = true, @JsonKey(name: 'tcp-concurrent') this.tcpConcurrent = true, @JsonKey(fromJson: Tun.safeFormJson) this.tun = defaultTun, @JsonKey(fromJson: Dns.safeDnsFromJson) this.dns = defaultDns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) this.geoXUrl = defaultGeoXUrl, @JsonKey(name: 'geodata-loader') this.geodataLoader = GeodataLoader.memconservative, @JsonKey(name: 'global-ua') this.globalUa, @JsonKey(name: 'external-controller') this.externalController = ExternalControllerStatus.close, final Map hosts = const {}}): _hosts = hosts;
+ const _PatchClashConfig({@JsonKey(name: 'mixed-port') this.mixedPort = defaultMixedPort, @JsonKey(name: 'socks-port') this.socksPort = 0, @JsonKey(name: 'port') this.port = 0, @JsonKey(name: 'redir-port') this.redirPort = 0, @JsonKey(name: 'tproxy-port') this.tproxyPort = 0, this.mode = Mode.rule, @JsonKey(name: 'allow-lan') this.allowLan = false, @JsonKey(name: 'log-level') this.logLevel = LogLevel.error, this.ipv6 = false, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) this.findProcessMode = FindProcessMode.always, @JsonKey(name: 'keep-alive-interval') this.keepAliveInterval = defaultKeepAliveInterval, @JsonKey(name: 'unified-delay') this.unifiedDelay = true, @JsonKey(name: 'tcp-concurrent') this.tcpConcurrent = true, @JsonKey(fromJson: Tun.safeFormJson) this.tun = defaultTun, @JsonKey(fromJson: Dns.safeDnsFromJson) this.dns = defaultDns, @JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) final Map geoXUrl = defaultGeoXUrl, @JsonKey(name: 'geodata-loader') this.geodataLoader = GeodataLoader.memconservative, @JsonKey(name: 'global-ua') this.globalUa, @JsonKey(name: 'external-controller') this.externalController = ExternalControllerStatus.close, final Map hosts = const {}, @JsonKey(name: 'geo-auto-update') this.geoAutoUpdate = false, @JsonKey(name: 'geo-update-interval') this.geoUpdateInterval = 24}): _geoXUrl = geoXUrl,_hosts = hosts;
factory _PatchClashConfig.fromJson(Map json) => _$PatchClashConfigFromJson(json);
@override@JsonKey(name: 'mixed-port') final int mixedPort;
@@ -4143,7 +3864,13 @@ class _PatchClashConfig implements PatchClashConfig {
@override@JsonKey(name: 'tcp-concurrent') final bool tcpConcurrent;
@override@JsonKey(fromJson: Tun.safeFormJson) final Tun tun;
@override@JsonKey(fromJson: Dns.safeDnsFromJson) final Dns dns;
-@override@JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) final GeoXUrl geoXUrl;
+ final Map _geoXUrl;
+@override@JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map get geoXUrl {
+ if (_geoXUrl is EqualUnmodifiableMapView) return _geoXUrl;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableMapView(_geoXUrl);
+}
+
@override@JsonKey(name: 'geodata-loader') final GeodataLoader geodataLoader;
@override@JsonKey(name: 'global-ua') final String? globalUa;
@override@JsonKey(name: 'external-controller') final ExternalControllerStatus externalController;
@@ -4154,6 +3881,8 @@ class _PatchClashConfig implements PatchClashConfig {
return EqualUnmodifiableMapView(_hosts);
}
+@override@JsonKey(name: 'geo-auto-update') final bool geoAutoUpdate;
+@override@JsonKey(name: 'geo-update-interval') final int geoUpdateInterval;
/// Create a copy of PatchClashConfig
/// with the given fields replaced by the non-null parameter values.
@@ -4168,16 +3897,16 @@ Map toJson() {
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _PatchClashConfig&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.socksPort, socksPort) || other.socksPort == socksPort)&&(identical(other.port, port) || other.port == port)&&(identical(other.redirPort, redirPort) || other.redirPort == redirPort)&&(identical(other.tproxyPort, tproxyPort) || other.tproxyPort == tproxyPort)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.keepAliveInterval, keepAliveInterval) || other.keepAliveInterval == keepAliveInterval)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.dns, dns) || other.dns == dns)&&(identical(other.geoXUrl, geoXUrl) || other.geoXUrl == geoXUrl)&&(identical(other.geodataLoader, geodataLoader) || other.geodataLoader == geodataLoader)&&(identical(other.globalUa, globalUa) || other.globalUa == globalUa)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&const DeepCollectionEquality().equals(other._hosts, _hosts));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _PatchClashConfig&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.socksPort, socksPort) || other.socksPort == socksPort)&&(identical(other.port, port) || other.port == port)&&(identical(other.redirPort, redirPort) || other.redirPort == redirPort)&&(identical(other.tproxyPort, tproxyPort) || other.tproxyPort == tproxyPort)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.keepAliveInterval, keepAliveInterval) || other.keepAliveInterval == keepAliveInterval)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.dns, dns) || other.dns == dns)&&const DeepCollectionEquality().equals(other._geoXUrl, _geoXUrl)&&(identical(other.geodataLoader, geodataLoader) || other.geodataLoader == geodataLoader)&&(identical(other.globalUa, globalUa) || other.globalUa == globalUa)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&const DeepCollectionEquality().equals(other._hosts, _hosts)&&(identical(other.geoAutoUpdate, geoAutoUpdate) || other.geoAutoUpdate == geoAutoUpdate)&&(identical(other.geoUpdateInterval, geoUpdateInterval) || other.geoUpdateInterval == geoUpdateInterval));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hashAll([runtimeType,mixedPort,socksPort,port,redirPort,tproxyPort,mode,allowLan,logLevel,ipv6,findProcessMode,keepAliveInterval,unifiedDelay,tcpConcurrent,tun,dns,geoXUrl,geodataLoader,globalUa,externalController,const DeepCollectionEquality().hash(_hosts)]);
+int get hashCode => Object.hashAll([runtimeType,mixedPort,socksPort,port,redirPort,tproxyPort,mode,allowLan,logLevel,ipv6,findProcessMode,keepAliveInterval,unifiedDelay,tcpConcurrent,tun,dns,const DeepCollectionEquality().hash(_geoXUrl),geodataLoader,globalUa,externalController,const DeepCollectionEquality().hash(_hosts),geoAutoUpdate,geoUpdateInterval]);
@override
String toString() {
- return 'PatchClashConfig(mixedPort: $mixedPort, socksPort: $socksPort, port: $port, redirPort: $redirPort, tproxyPort: $tproxyPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)';
+ return 'PatchClashConfig(mixedPort: $mixedPort, socksPort: $socksPort, port: $port, redirPort: $redirPort, tproxyPort: $tproxyPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, globalUa: $globalUa, externalController: $externalController, hosts: $hosts, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval)';
}
@@ -4188,11 +3917,11 @@ abstract mixin class _$PatchClashConfigCopyWith<$Res> implements $PatchClashConf
factory _$PatchClashConfigCopyWith(_PatchClashConfig value, $Res Function(_PatchClashConfig) _then) = __$PatchClashConfigCopyWithImpl;
@override @useResult
$Res call({
-@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'socks-port') int socksPort,@JsonKey(name: 'port') int port,@JsonKey(name: 'redir-port') int redirPort,@JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode,@JsonKey(name: 'keep-alive-interval') int keepAliveInterval,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(fromJson: Tun.safeFormJson) Tun tun,@JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,@JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) GeoXUrl geoXUrl,@JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader,@JsonKey(name: 'global-ua') String? globalUa,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts
+@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'socks-port') int socksPort,@JsonKey(name: 'port') int port,@JsonKey(name: 'redir-port') int redirPort,@JsonKey(name: 'tproxy-port') int tproxyPort, Mode mode,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) FindProcessMode findProcessMode,@JsonKey(name: 'keep-alive-interval') int keepAliveInterval,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(fromJson: Tun.safeFormJson) Tun tun,@JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,@JsonKey(name: 'geox-url', fromJson: _geoXUrlFromJson, toJson: _geoXUrlToJson) Map geoXUrl,@JsonKey(name: 'geodata-loader') GeodataLoader geodataLoader,@JsonKey(name: 'global-ua') String? globalUa,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController, Map hosts,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
});
-@override $TunCopyWith<$Res> get tun;@override $DnsCopyWith<$Res> get dns;@override $GeoXUrlCopyWith<$Res> get geoXUrl;
+@override $TunCopyWith<$Res> get tun;@override $DnsCopyWith<$Res> get dns;
}
/// @nodoc
@@ -4205,7 +3934,7 @@ class __$PatchClashConfigCopyWithImpl<$Res>
/// Create a copy of PatchClashConfig
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? mixedPort = null,Object? socksPort = null,Object? port = null,Object? redirPort = null,Object? tproxyPort = null,Object? mode = null,Object? allowLan = null,Object? logLevel = null,Object? ipv6 = null,Object? findProcessMode = null,Object? keepAliveInterval = null,Object? unifiedDelay = null,Object? tcpConcurrent = null,Object? tun = null,Object? dns = null,Object? geoXUrl = null,Object? geodataLoader = null,Object? globalUa = freezed,Object? externalController = null,Object? hosts = null,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? mixedPort = null,Object? socksPort = null,Object? port = null,Object? redirPort = null,Object? tproxyPort = null,Object? mode = null,Object? allowLan = null,Object? logLevel = null,Object? ipv6 = null,Object? findProcessMode = null,Object? keepAliveInterval = null,Object? unifiedDelay = null,Object? tcpConcurrent = null,Object? tun = null,Object? dns = null,Object? geoXUrl = null,Object? geodataLoader = null,Object? globalUa = freezed,Object? externalController = null,Object? hosts = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
return _then(_PatchClashConfig(
mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
as int,socksPort: null == socksPort ? _self.socksPort : socksPort // ignore: cast_nullable_to_non_nullable
@@ -4222,12 +3951,14 @@ as int,unifiedDelay: null == unifiedDelay ? _self.unifiedDelay : unifiedDelay //
as bool,tcpConcurrent: null == tcpConcurrent ? _self.tcpConcurrent : tcpConcurrent // ignore: cast_nullable_to_non_nullable
as bool,tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,dns: null == dns ? _self.dns : dns // ignore: cast_nullable_to_non_nullable
-as Dns,geoXUrl: null == geoXUrl ? _self.geoXUrl : geoXUrl // ignore: cast_nullable_to_non_nullable
-as GeoXUrl,geodataLoader: null == geodataLoader ? _self.geodataLoader : geodataLoader // ignore: cast_nullable_to_non_nullable
+as Dns,geoXUrl: null == geoXUrl ? _self._geoXUrl : geoXUrl // ignore: cast_nullable_to_non_nullable
+as Map,geodataLoader: null == geodataLoader ? _self.geodataLoader : geodataLoader // ignore: cast_nullable_to_non_nullable
as GeodataLoader,globalUa: freezed == globalUa ? _self.globalUa : globalUa // ignore: cast_nullable_to_non_nullable
as String?,externalController: null == externalController ? _self.externalController : externalController // ignore: cast_nullable_to_non_nullable
as ExternalControllerStatus,hosts: null == hosts ? _self._hosts : hosts // ignore: cast_nullable_to_non_nullable
-as Map,
+as Map,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
+as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
+as int,
));
}
@@ -4249,15 +3980,6 @@ $DnsCopyWith<$Res> get dns {
return $DnsCopyWith<$Res>(_self.dns, (value) {
return _then(_self.copyWith(dns: value));
});
-}/// Create a copy of PatchClashConfig
-/// with the given fields replaced by the non-null parameter values.
-@override
-@pragma('vm:prefer-inline')
-$GeoXUrlCopyWith<$Res> get geoXUrl {
-
- return $GeoXUrlCopyWith<$Res>(_self.geoXUrl, (value) {
- return _then(_self.copyWith(geoXUrl: value));
- });
}
}
diff --git a/lib/models/generated/clash_config.g.dart b/lib/models/generated/clash_config.g.dart
index ed956a31..b70d7a5a 100644
--- a/lib/models/generated/clash_config.g.dart
+++ b/lib/models/generated/clash_config.g.dart
@@ -295,28 +295,6 @@ const _$DnsModeEnumMap = {
DnsMode.hosts: 'hosts',
};
-_GeoXUrl _$GeoXUrlFromJson(Map json) => _GeoXUrl(
- mmdb:
- json['mmdb'] as String? ??
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
- asn:
- json['asn'] as String? ??
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
- geoip:
- json['geoip'] as String? ??
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
- geosite:
- json['geosite'] as String? ??
- 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat',
-);
-
-Map _$GeoXUrlToJson(_GeoXUrl instance) => {
- 'mmdb': instance.mmdb,
- 'asn': instance.asn,
- 'geoip': instance.geoip,
- 'geosite': instance.geosite,
-};
-
_Rule _$RuleFromJson(Map json) => _Rule(
id: (json['id'] as num?)?.toInt() ?? -1,
ruleAction:
@@ -451,7 +429,7 @@ _PatchClashConfig _$PatchClashConfigFromJson(Map json) =>
: Dns.safeDnsFromJson(json['dns'] as Map),
geoXUrl: json['geox-url'] == null
? defaultGeoXUrl
- : GeoXUrl.safeFormJson(json['geox-url'] as Map?),
+ : _geoXUrlFromJson(json['geox-url'] as Map?),
geodataLoader:
$enumDecodeNullable(_$GeodataLoaderEnumMap, json['geodata-loader']) ??
GeodataLoader.memconservative,
@@ -467,6 +445,8 @@ _PatchClashConfig _$PatchClashConfigFromJson(Map json) =>
(k, e) => MapEntry(k, e as String),
) ??
const {},
+ geoAutoUpdate: json['geo-auto-update'] as bool? ?? false,
+ geoUpdateInterval: (json['geo-update-interval'] as num?)?.toInt() ?? 24,
);
Map _$PatchClashConfigToJson(_PatchClashConfig instance) =>
@@ -486,12 +466,14 @@ Map _$PatchClashConfigToJson(_PatchClashConfig instance) =>
'tcp-concurrent': instance.tcpConcurrent,
'tun': instance.tun,
'dns': instance.dns,
- 'geox-url': instance.geoXUrl,
+ 'geox-url': _geoXUrlToJson(instance.geoXUrl),
'geodata-loader': _$GeodataLoaderEnumMap[instance.geodataLoader]!,
'global-ua': instance.globalUa,
'external-controller':
_$ExternalControllerStatusEnumMap[instance.externalController]!,
'hosts': instance.hosts,
+ 'geo-auto-update': instance.geoAutoUpdate,
+ 'geo-update-interval': instance.geoUpdateInterval,
};
const _$ModeEnumMap = {
diff --git a/lib/models/generated/config.freezed.dart b/lib/models/generated/config.freezed.dart
index 273c294e..e776a211 100644
--- a/lib/models/generated/config.freezed.dart
+++ b/lib/models/generated/config.freezed.dart
@@ -15,7 +15,7 @@ T _$identity(T value) => value;
/// @nodoc
mixin _$AppSettingProps {
- String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get crashlyticsTip; bool get crashlytics; bool get minimizeOnExit; bool get hidden; bool get developerMode; RestoreStrategy get restoreStrategy; bool get showTrayTitle;
+ String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get crashlyticsTip; bool get crashlytics; bool get minimizeOnExit; bool get hidden; bool get developerMode; RestoreStrategy get restoreStrategy; bool get showTrayTitle; String get customUserAgent;
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $AppSettingPropsCopyWith get copyWith => _$AppSettingPropsCopyW
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)&&(identical(other.customUserAgent, customUserAgent) || other.customUserAgent == customUserAgent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle]);
+int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle,customUserAgent]);
@override
String toString() {
- return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle)';
+ return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle, customUserAgent: $customUserAgent)';
}
@@ -48,7 +48,7 @@ abstract mixin class $AppSettingPropsCopyWith<$Res> {
factory $AppSettingPropsCopyWith(AppSettingProps value, $Res Function(AppSettingProps) _then) = _$AppSettingPropsCopyWithImpl;
@useResult
$Res call({
- String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle
+ String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String customUserAgent
});
@@ -65,7 +65,7 @@ class _$AppSettingPropsCopyWithImpl<$Res>
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,Object? customUserAgent = null,}) {
return _then(_self.copyWith(
locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable
as String?,dashboardWidgets: null == dashboardWidgets ? _self.dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
@@ -87,7 +87,8 @@ as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_
as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable
as bool,restoreStrategy: null == restoreStrategy ? _self.restoreStrategy : restoreStrategy // ignore: cast_nullable_to_non_nullable
as RestoreStrategy,showTrayTitle: null == showTrayTitle ? _self.showTrayTitle : showTrayTitle // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,customUserAgent: null == customUserAgent ? _self.customUserAgent : customUserAgent // ignore: cast_nullable_to_non_nullable
+as String,
));
}
@@ -172,10 +173,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String customUserAgent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AppSettingProps() when $default != null:
-return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _:
+return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.customUserAgent);case _:
return orElse();
}
@@ -193,10 +194,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
/// }
/// ```
-@optionalTypeArgs TResult when(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle) $default,) {final _that = this;
+@optionalTypeArgs TResult when(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String customUserAgent) $default,) {final _that = this;
switch (_that) {
case _AppSettingProps():
-return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _:
+return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.customUserAgent);case _:
throw StateError('Unexpected subclass');
}
@@ -213,10 +214,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String customUserAgent)? $default,) {final _that = this;
switch (_that) {
case _AppSettingProps() when $default != null:
-return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _:
+return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.customUserAgent);case _:
return null;
}
@@ -228,7 +229,7 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
@JsonSerializable()
class _AppSettingProps implements AppSettingProps {
- const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.crashlyticsTip = false, this.crashlytics = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.restoreStrategy = RestoreStrategy.compatible, this.showTrayTitle = true}): _dashboardWidgets = dashboardWidgets;
+ const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.crashlyticsTip = false, this.crashlytics = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.restoreStrategy = RestoreStrategy.compatible, this.showTrayTitle = true, this.customUserAgent = ''}): _dashboardWidgets = dashboardWidgets;
factory _AppSettingProps.fromJson(Map json) => _$AppSettingPropsFromJson(json);
@override final String? locale;
@@ -257,6 +258,7 @@ class _AppSettingProps implements AppSettingProps {
@override@JsonKey() final bool developerMode;
@override@JsonKey() final RestoreStrategy restoreStrategy;
@override@JsonKey() final bool showTrayTitle;
+@override@JsonKey() final String customUserAgent;
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
@@ -271,16 +273,16 @@ Map toJson() {
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)&&(identical(other.customUserAgent, customUserAgent) || other.customUserAgent == customUserAgent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle]);
+int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle,customUserAgent]);
@override
String toString() {
- return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle)';
+ return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle, customUserAgent: $customUserAgent)';
}
@@ -291,7 +293,7 @@ abstract mixin class _$AppSettingPropsCopyWith<$Res> implements $AppSettingProps
factory _$AppSettingPropsCopyWith(_AppSettingProps value, $Res Function(_AppSettingProps) _then) = __$AppSettingPropsCopyWithImpl;
@override @useResult
$Res call({
- String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle
+ String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String customUserAgent
});
@@ -308,7 +310,7 @@ class __$AppSettingPropsCopyWithImpl<$Res>
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,Object? customUserAgent = null,}) {
return _then(_AppSettingProps(
locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable
as String?,dashboardWidgets: null == dashboardWidgets ? _self._dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
@@ -330,7 +332,8 @@ as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_
as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable
as bool,restoreStrategy: null == restoreStrategy ? _self.restoreStrategy : restoreStrategy // ignore: cast_nullable_to_non_nullable
as RestoreStrategy,showTrayTitle: null == showTrayTitle ? _self.showTrayTitle : showTrayTitle // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,customUserAgent: null == customUserAgent ? _self.customUserAgent : customUserAgent // ignore: cast_nullable_to_non_nullable
+as String,
));
}
diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart
index 3f54d5ae..76d3d604 100644
--- a/lib/models/generated/config.g.dart
+++ b/lib/models/generated/config.g.dart
@@ -35,6 +35,7 @@ _AppSettingProps _$AppSettingPropsFromJson(Map json) =>
) ??
RestoreStrategy.compatible,
showTrayTitle: json['showTrayTitle'] as bool? ?? true,
+ customUserAgent: json['customUserAgent'] as String? ?? '',
);
Map _$AppSettingPropsToJson(_AppSettingProps instance) =>
@@ -61,6 +62,7 @@ Map _$AppSettingPropsToJson(_AppSettingProps instance) =>
'developerMode': instance.developerMode,
'restoreStrategy': _$RestoreStrategyEnumMap[instance.restoreStrategy]!,
'showTrayTitle': instance.showTrayTitle,
+ 'customUserAgent': instance.customUserAgent,
};
const _$RestoreStrategyEnumMap = {
diff --git a/lib/models/generated/core.freezed.dart b/lib/models/generated/core.freezed.dart
index b29669e2..bc15672f 100644
--- a/lib/models/generated/core.freezed.dart
+++ b/lib/models/generated/core.freezed.dart
@@ -287,7 +287,7 @@ as String,
/// @nodoc
mixin _$UpdateParams {
- Tun get tun;@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'find-process-mode') FindProcessMode get findProcessMode; Mode get mode;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController;@JsonKey(name: 'unified-delay') bool get unifiedDelay;
+ Tun get tun;@JsonKey(name: 'mixed-port') int get mixedPort;@JsonKey(name: 'allow-lan') bool get allowLan;@JsonKey(name: 'find-process-mode') FindProcessMode get findProcessMode; Mode get mode;@JsonKey(name: 'log-level') LogLevel get logLevel; bool get ipv6;@JsonKey(name: 'tcp-concurrent') bool get tcpConcurrent;@JsonKey(name: 'external-controller') ExternalControllerStatus get externalController;@JsonKey(name: 'unified-delay') bool get unifiedDelay;@JsonKey(name: 'geo-auto-update') bool get geoAutoUpdate;@JsonKey(name: 'geo-update-interval') int get geoUpdateInterval;
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -300,16 +300,16 @@ $UpdateParamsCopyWith get copyWith => _$UpdateParamsCopyWithImpl Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay);
+int get hashCode => Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay,geoAutoUpdate,geoUpdateInterval);
@override
String toString() {
- return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay)';
+ return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval)';
}
@@ -320,7 +320,7 @@ abstract mixin class $UpdateParamsCopyWith<$Res> {
factory $UpdateParamsCopyWith(UpdateParams value, $Res Function(UpdateParams) _then) = _$UpdateParamsCopyWithImpl;
@useResult
$Res call({
- Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay
+ Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
});
@@ -337,7 +337,7 @@ class _$UpdateParamsCopyWithImpl<$Res>
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
return _then(_self.copyWith(
tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
@@ -349,7 +349,9 @@ as LogLevel,ipv6: null == ipv6 ? _self.ipv6 : ipv6 // ignore: cast_nullable_to_n
as bool,tcpConcurrent: null == tcpConcurrent ? _self.tcpConcurrent : tcpConcurrent // ignore: cast_nullable_to_non_nullable
as bool,externalController: null == externalController ? _self.externalController : externalController // ignore: cast_nullable_to_non_nullable
as ExternalControllerStatus,unifiedDelay: null == unifiedDelay ? _self.unifiedDelay : unifiedDelay // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
+as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
+as int,
));
}
/// Create a copy of UpdateParams
@@ -443,10 +445,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _UpdateParams() when $default != null:
-return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay);case _:
+return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return orElse();
}
@@ -464,10 +466,10 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
/// }
/// ```
-@optionalTypeArgs TResult when(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay) $default,) {final _that = this;
+@optionalTypeArgs TResult when(TResult Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval) $default,) {final _that = this;
switch (_that) {
case _UpdateParams():
-return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay);case _:
+return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
throw StateError('Unexpected subclass');
}
@@ -484,10 +486,10 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull(TResult? Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull(TResult? Function( Tun tun, @JsonKey(name: 'mixed-port') int mixedPort, @JsonKey(name: 'allow-lan') bool allowLan, @JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode, @JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6, @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent, @JsonKey(name: 'external-controller') ExternalControllerStatus externalController, @JsonKey(name: 'unified-delay') bool unifiedDelay, @JsonKey(name: 'geo-auto-update') bool geoAutoUpdate, @JsonKey(name: 'geo-update-interval') int geoUpdateInterval)? $default,) {final _that = this;
switch (_that) {
case _UpdateParams() when $default != null:
-return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay);case _:
+return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_that.mode,_that.logLevel,_that.ipv6,_that.tcpConcurrent,_that.externalController,_that.unifiedDelay,_that.geoAutoUpdate,_that.geoUpdateInterval);case _:
return null;
}
@@ -499,7 +501,7 @@ return $default(_that.tun,_that.mixedPort,_that.allowLan,_that.findProcessMode,_
@JsonSerializable()
class _UpdateParams implements UpdateParams {
- const _UpdateParams({required this.tun, @JsonKey(name: 'mixed-port') required this.mixedPort, @JsonKey(name: 'allow-lan') required this.allowLan, @JsonKey(name: 'find-process-mode') required this.findProcessMode, required this.mode, @JsonKey(name: 'log-level') required this.logLevel, required this.ipv6, @JsonKey(name: 'tcp-concurrent') required this.tcpConcurrent, @JsonKey(name: 'external-controller') required this.externalController, @JsonKey(name: 'unified-delay') required this.unifiedDelay});
+ const _UpdateParams({required this.tun, @JsonKey(name: 'mixed-port') required this.mixedPort, @JsonKey(name: 'allow-lan') required this.allowLan, @JsonKey(name: 'find-process-mode') required this.findProcessMode, required this.mode, @JsonKey(name: 'log-level') required this.logLevel, required this.ipv6, @JsonKey(name: 'tcp-concurrent') required this.tcpConcurrent, @JsonKey(name: 'external-controller') required this.externalController, @JsonKey(name: 'unified-delay') required this.unifiedDelay, @JsonKey(name: 'geo-auto-update') this.geoAutoUpdate = false, @JsonKey(name: 'geo-update-interval') this.geoUpdateInterval = 24});
factory _UpdateParams.fromJson(Map json) => _$UpdateParamsFromJson(json);
@override final Tun tun;
@@ -512,6 +514,8 @@ class _UpdateParams implements UpdateParams {
@override@JsonKey(name: 'tcp-concurrent') final bool tcpConcurrent;
@override@JsonKey(name: 'external-controller') final ExternalControllerStatus externalController;
@override@JsonKey(name: 'unified-delay') final bool unifiedDelay;
+@override@JsonKey(name: 'geo-auto-update') final bool geoAutoUpdate;
+@override@JsonKey(name: 'geo-update-interval') final int geoUpdateInterval;
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
@@ -526,16 +530,16 @@ Map toJson() {
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateParams&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateParams&&(identical(other.tun, tun) || other.tun == tun)&&(identical(other.mixedPort, mixedPort) || other.mixedPort == mixedPort)&&(identical(other.allowLan, allowLan) || other.allowLan == allowLan)&&(identical(other.findProcessMode, findProcessMode) || other.findProcessMode == findProcessMode)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.logLevel, logLevel) || other.logLevel == logLevel)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.tcpConcurrent, tcpConcurrent) || other.tcpConcurrent == tcpConcurrent)&&(identical(other.externalController, externalController) || other.externalController == externalController)&&(identical(other.unifiedDelay, unifiedDelay) || other.unifiedDelay == unifiedDelay)&&(identical(other.geoAutoUpdate, geoAutoUpdate) || other.geoAutoUpdate == geoAutoUpdate)&&(identical(other.geoUpdateInterval, geoUpdateInterval) || other.geoUpdateInterval == geoUpdateInterval));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay);
+int get hashCode => Object.hash(runtimeType,tun,mixedPort,allowLan,findProcessMode,mode,logLevel,ipv6,tcpConcurrent,externalController,unifiedDelay,geoAutoUpdate,geoUpdateInterval);
@override
String toString() {
- return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay)';
+ return 'UpdateParams(tun: $tun, mixedPort: $mixedPort, allowLan: $allowLan, findProcessMode: $findProcessMode, mode: $mode, logLevel: $logLevel, ipv6: $ipv6, tcpConcurrent: $tcpConcurrent, externalController: $externalController, unifiedDelay: $unifiedDelay, geoAutoUpdate: $geoAutoUpdate, geoUpdateInterval: $geoUpdateInterval)';
}
@@ -546,7 +550,7 @@ abstract mixin class _$UpdateParamsCopyWith<$Res> implements $UpdateParamsCopyWi
factory _$UpdateParamsCopyWith(_UpdateParams value, $Res Function(_UpdateParams) _then) = __$UpdateParamsCopyWithImpl;
@override @useResult
$Res call({
- Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay
+ Tun tun,@JsonKey(name: 'mixed-port') int mixedPort,@JsonKey(name: 'allow-lan') bool allowLan,@JsonKey(name: 'find-process-mode') FindProcessMode findProcessMode, Mode mode,@JsonKey(name: 'log-level') LogLevel logLevel, bool ipv6,@JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,@JsonKey(name: 'external-controller') ExternalControllerStatus externalController,@JsonKey(name: 'unified-delay') bool unifiedDelay,@JsonKey(name: 'geo-auto-update') bool geoAutoUpdate,@JsonKey(name: 'geo-update-interval') int geoUpdateInterval
});
@@ -563,7 +567,7 @@ class __$UpdateParamsCopyWithImpl<$Res>
/// Create a copy of UpdateParams
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? tun = null,Object? mixedPort = null,Object? allowLan = null,Object? findProcessMode = null,Object? mode = null,Object? logLevel = null,Object? ipv6 = null,Object? tcpConcurrent = null,Object? externalController = null,Object? unifiedDelay = null,Object? geoAutoUpdate = null,Object? geoUpdateInterval = null,}) {
return _then(_UpdateParams(
tun: null == tun ? _self.tun : tun // ignore: cast_nullable_to_non_nullable
as Tun,mixedPort: null == mixedPort ? _self.mixedPort : mixedPort // ignore: cast_nullable_to_non_nullable
@@ -575,7 +579,9 @@ as LogLevel,ipv6: null == ipv6 ? _self.ipv6 : ipv6 // ignore: cast_nullable_to_n
as bool,tcpConcurrent: null == tcpConcurrent ? _self.tcpConcurrent : tcpConcurrent // ignore: cast_nullable_to_non_nullable
as bool,externalController: null == externalController ? _self.externalController : externalController // ignore: cast_nullable_to_non_nullable
as ExternalControllerStatus,unifiedDelay: null == unifiedDelay ? _self.unifiedDelay : unifiedDelay // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,geoAutoUpdate: null == geoAutoUpdate ? _self.geoAutoUpdate : geoAutoUpdate // ignore: cast_nullable_to_non_nullable
+as bool,geoUpdateInterval: null == geoUpdateInterval ? _self.geoUpdateInterval : geoUpdateInterval // ignore: cast_nullable_to_non_nullable
+as int,
));
}
diff --git a/lib/models/generated/core.g.dart b/lib/models/generated/core.g.dart
index 1a022350..7e785dfa 100644
--- a/lib/models/generated/core.g.dart
+++ b/lib/models/generated/core.g.dart
@@ -35,6 +35,8 @@ _UpdateParams _$UpdateParamsFromJson(Map json) =>
json['external-controller'],
),
unifiedDelay: json['unified-delay'] as bool,
+ geoAutoUpdate: json['geo-auto-update'] as bool? ?? false,
+ geoUpdateInterval: (json['geo-update-interval'] as num?)?.toInt() ?? 24,
);
Map _$UpdateParamsToJson(_UpdateParams instance) =>
@@ -50,6 +52,8 @@ Map _$UpdateParamsToJson(_UpdateParams instance) =>
'external-controller':
_$ExternalControllerStatusEnumMap[instance.externalController]!,
'unified-delay': instance.unifiedDelay,
+ 'geo-auto-update': instance.geoAutoUpdate,
+ 'geo-update-interval': instance.geoUpdateInterval,
};
const _$FindProcessModeEnumMap = {
@@ -164,6 +168,7 @@ const _$CoreEventTypeEnumMap = {
CoreEventType.request: 'request',
CoreEventType.loaded: 'loaded',
CoreEventType.crash: 'crash',
+ CoreEventType.geoUpdate: 'geoUpdate',
};
_InvokeMessage _$InvokeMessageFromJson(Map json) =>
diff --git a/lib/pages/editor.dart b/lib/pages/editor.dart
index 0ac93970..9d25aeeb 100644
--- a/lib/pages/editor.dart
+++ b/lib/pages/editor.dart
@@ -139,7 +139,7 @@ class _EditorPageState extends ConsumerState {
if (file == null) {
return;
}
- final res = utf8.decode(file.bytes?.toList() ?? []);
+ final res = utf8.decode(await file.readBytes());
_controller.text = res;
}
@@ -150,6 +150,7 @@ class _EditorPageState extends ConsumerState {
title: appLocalizations.import,
value: '',
labelText: appLocalizations.url,
+ inputFormatters: TextInputLimits.limit(TextInputLimits.url),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations.emptyTip(appLocalizations.value);
@@ -442,7 +443,11 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [bar, const SizedBox(height: 12), _buildFindInput(context, value)],
+ children: [
+ bar,
+ const SizedBox(height: 12),
+ _buildFindInput(context, value),
+ ],
);
}
return bar;
@@ -499,6 +504,7 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
child: TextField(
maxLines: 1,
focusNode: focusNode,
+ inputFormatters: TextInputLimits.limit(TextInputLimits.search),
style: context.textTheme.bodyMedium,
decoration: const InputDecoration(
border: OutlineInputBorder(),
diff --git a/lib/pages/home.dart b/lib/pages/home.dart
index a891477e..c5742a87 100644
--- a/lib/pages/home.dart
+++ b/lib/pages/home.dart
@@ -6,7 +6,6 @@ import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
@@ -30,9 +29,6 @@ class HomePage extends StatelessWidget {
child: Consumer(
builder: (context, ref, child) {
final state = ref.watch(navigationStateProvider);
- final systemUiOverlayStyle = ref.read(
- systemUiOverlayStyleStateProvider,
- );
final isMobile = state.viewMode == ViewMode.mobile;
final navigationItems = state.navigationItems;
final currentIndex = state.currentIndex;
@@ -54,34 +50,28 @@ class HomePage extends StatelessWidget {
),
);
if (isMobile) {
- return AnnotatedRegion(
- value: systemUiOverlayStyle.copyWith(
- systemNavigationBarColor:
- context.colorScheme.surfaceContainer,
- ),
- child: Column(
- children: [
- Flexible(
- flex: 1,
- child: MediaQuery.removePadding(
- removeTop: false,
- removeBottom: true,
- removeLeft: true,
- removeRight: true,
- context: context,
- child: child!,
- ),
- ),
- MediaQuery.removePadding(
- removeTop: true,
- removeBottom: false,
+ return Column(
+ children: [
+ Flexible(
+ flex: 1,
+ child: MediaQuery.removePadding(
+ removeTop: false,
+ removeBottom: true,
removeLeft: true,
removeRight: true,
context: context,
- child: bottomNavigationBar,
+ child: child!,
),
- ],
- ),
+ ),
+ MediaQuery.removePadding(
+ removeTop: true,
+ removeBottom: false,
+ removeLeft: true,
+ removeRight: true,
+ context: context,
+ child: bottomNavigationBar,
+ ),
+ ],
);
} else {
return child!;
@@ -287,7 +277,7 @@ class HomeBackScopeContainer extends ConsumerWidget {
} else {
await globalState.container
.read(systemActionProvider.notifier)
- .handleBackOrExit();
+ .handleClose();
}
return false;
},
diff --git a/lib/pages/scan.dart b/lib/pages/scan.dart
index 20072deb..ef362525 100644
--- a/lib/pages/scan.dart
+++ b/lib/pages/scan.dart
@@ -61,7 +61,10 @@ class _ScanPageState extends State with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
- final double sideLength = min(400, MediaQuery.of(context).size.width * 0.67);
+ final double sideLength = min(
+ 400,
+ MediaQuery.of(context).size.width * 0.67,
+ );
final scanWindow = Rect.fromCenter(
center: MediaQuery.sizeOf(context).center(Offset.zero),
width: sideLength,
diff --git a/lib/plugins/app.dart b/lib/plugins/app.dart
index b70ed091..612b7cc4 100644
--- a/lib/plugins/app.dart
+++ b/lib/plugins/app.dart
@@ -53,9 +53,7 @@ class App {
}
Future requestNotificationsPermission() async {
- return methodChannel.invokeMethod(
- 'requestNotificationsPermission',
- );
+ return methodChannel.invokeMethod('requestNotificationsPermission');
}
Future openFile(String path) async {
@@ -74,9 +72,7 @@ class App {
}
Future tip(String? message) async {
- return methodChannel.invokeMethod('tip', {
- 'message': '$message',
- });
+ return methodChannel.invokeMethod('tip', {'message': '$message'});
}
Future initShortcuts() async {
diff --git a/lib/providers/action.dart b/lib/providers/action.dart
index 7baf8214..c444723c 100644
--- a/lib/providers/action.dart
+++ b/lib/providers/action.dart
@@ -224,6 +224,7 @@ class SetupAction extends _$SetupAction {
final message = await coreController.updateConfig(
updateParams.copyWith.tun(enable: realTunEnable),
);
+ ref.read(checkIpNumProvider.notifier).add();
if (message.isNotEmpty) throw message;
});
});
@@ -254,7 +255,6 @@ class SetupAction extends _$SetupAction {
.read(proxiesActionProvider.notifier)
.updateCurrentGroupName(GroupName.GLOBAL.name);
}
- ref.read(checkIpNumProvider.notifier).add();
}
void autoApplyProfile() {
@@ -593,9 +593,11 @@ class SystemAction extends _$SystemAction {
}
}
- Future handleBackOrExit() async {
- if (ref.read(backBlockProvider)) return;
- if (ref.read(appSettingProvider).minimizeOnExit) {
+ Future handleClose([bool exit = true]) async {
+ if (!system.isDesktop) {
+ if (ref.read(backBlockProvider)) return;
+ }
+ if (ref.read(appSettingProvider).minimizeOnExit || !exit) {
if (system.isDesktop) {
await preferences.saveConfig(ref.read(configProvider));
}
@@ -910,14 +912,14 @@ class ProfilesAction extends _$ProfilesAction {
Future addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerFile);
- final bytes = platformFile?.bytes;
- if (bytes == null) return;
+ if (platformFile == null) return;
+ final bytes = await platformFile.readBytes();
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
ref.read(currentPageLabelProvider.notifier).toProfiles();
final profile = await globalState.loadingRun(
tag: LoadingTag.profiles,
() async {
- return Profile.normal(label: platformFile?.name).saveFile(bytes);
+ return Profile.normal(label: platformFile.name).saveFile(bytes);
},
title: currentAppLocalizations.addProfile,
);
@@ -973,3 +975,22 @@ class ProfilesAction extends _$ProfilesAction {
await coreController.deleteFile(providersDirPath);
}
}
+
+@Riverpod(keepAlive: true)
+class GeoResourceAction extends _$GeoResourceAction {
+ @override
+ void build() {}
+
+ Future updateGeoResource(GeoResource geoResource) async {
+ await coreController.updateGeoData(geoResource.name);
+ }
+
+ void updateGeoResourceUrl(GeoResource geoResource, String newUrl) {
+ if (!newUrl.isUrl) {
+ throw 'Invalid url';
+ }
+ ref.read(patchClashConfigProvider.notifier).update((state) {
+ return state.copyWith(geoXUrl: {...state.geoXUrl, geoResource: newUrl});
+ });
+ }
+}
diff --git a/lib/providers/database.dart b/lib/providers/database.dart
index 4045613f..1d0d5124 100644
--- a/lib/providers/database.dart
+++ b/lib/providers/database.dart
@@ -243,14 +243,11 @@ class GlobalRules extends _$GlobalRules with AsyncNotifierMixin {
void order(int oldIndex, int newIndex) {
final previous = List.from(value);
- int insertIndex = newIndex;
- if (oldIndex < newIndex) insertIndex -= 1;
- final nextItems = List.from(previous);
- final item = nextItems.removeAt(oldIndex);
- nextItems.insert(insertIndex, item);
+ final item = previous[oldIndex];
+ final nextItems = previous.copyAndReorder(oldIndex, newIndex);
value = nextItems;
- final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
- final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
+ final preOrder = nextItems.safeGet(newIndex - 1)?.order;
+ final nextOrder = nextItems.safeGet(newIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
unawaited(
withRollback(
@@ -308,14 +305,11 @@ class ProfileAddedRules extends _$ProfileAddedRules with AsyncNotifierMixin {
void order(int oldIndex, int newIndex) {
final previous = List.from(value);
- int insertIndex = newIndex;
- if (oldIndex < newIndex) insertIndex -= 1;
- final nextItems = List.from(previous);
- final item = nextItems.removeAt(oldIndex);
- nextItems.insert(insertIndex, item);
+ final item = previous[oldIndex];
+ final nextItems = previous.copyAndReorder(oldIndex, newIndex);
value = nextItems;
- final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
- final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
+ final preOrder = nextItems.safeGet(newIndex - 1)?.order;
+ final nextOrder = nextItems.safeGet(newIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
unawaited(
withRollback(
@@ -377,14 +371,11 @@ class ProfileCustomRules extends _$ProfileCustomRules with AsyncNotifierMixin {
void order(int oldIndex, int newIndex) {
final previous = List.from(value);
- int insertIndex = newIndex;
- if (oldIndex < newIndex) insertIndex -= 1;
- final nextItems = List.from(previous);
- final item = nextItems.removeAt(oldIndex);
- nextItems.insert(insertIndex, item);
+ final item = previous[oldIndex];
+ final nextItems = previous.copyAndReorder(oldIndex, newIndex);
value = nextItems;
- final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
- final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
+ final preOrder = nextItems.safeGet(newIndex - 1)?.order;
+ final nextOrder = nextItems.safeGet(newIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
unawaited(
withRollback(
@@ -480,14 +471,11 @@ class ProxyGroups extends _$ProxyGroups with AsyncNotifierMixin {
void order(int oldIndex, int newIndex) {
final previous = List.from(value);
- int insertIndex = newIndex;
- if (oldIndex < newIndex) insertIndex -= 1;
- final nextItems = List.from(previous);
- final item = nextItems.removeAt(oldIndex);
- nextItems.insert(insertIndex, item);
+ final item = previous[oldIndex];
+ final nextItems = previous.copyAndReorder(oldIndex, newIndex);
value = nextItems;
- final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
- final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
+ final preOrder = nextItems.safeGet(newIndex - 1)?.order;
+ final nextOrder = nextItems.safeGet(newIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
unawaited(
withRollback(
diff --git a/lib/providers/generated/action.g.dart b/lib/providers/generated/action.g.dart
index 401232f5..eac384e1 100644
--- a/lib/providers/generated/action.g.dart
+++ b/lib/providers/generated/action.g.dart
@@ -91,7 +91,7 @@ final class SetupActionProvider extends $NotifierProvider {
}
}
-String _$setupActionHash() => r'5e7aff49668e1013e2024c452b08950ccf2472fe';
+String _$setupActionHash() => r'c6c7b3b5d90f5070dca9ed02e51dcc06f812a294';
abstract class _$SetupAction extends $Notifier {
void build();
@@ -244,7 +244,7 @@ final class SystemActionProvider extends $NotifierProvider {
}
}
-String _$systemActionHash() => r'579650623bcc9263528fc0ac1f91b3d83bdc45e3';
+String _$systemActionHash() => r'04f09af2702925aff276423a9c224fd8d3aca061';
abstract class _$SystemAction extends $Notifier {
void build();
@@ -450,7 +450,7 @@ final class ProfilesActionProvider
}
}
-String _$profilesActionHash() => r'5c4a52394a97ff40cb9bc7bf30d7a412e36e877a';
+String _$profilesActionHash() => r'b2457dc5b18d51204d17995949a52cffce41df38';
abstract class _$ProfilesAction extends $Notifier {
void build();
@@ -469,3 +469,55 @@ abstract class _$ProfilesAction extends $Notifier {
element.handleCreate(ref, build);
}
}
+
+@ProviderFor(GeoResourceAction)
+final geoResourceActionProvider = GeoResourceActionProvider._();
+
+final class GeoResourceActionProvider
+ extends $NotifierProvider {
+ GeoResourceActionProvider._()
+ : super(
+ from: null,
+ argument: null,
+ retry: null,
+ name: r'geoResourceActionProvider',
+ isAutoDispose: false,
+ dependencies: null,
+ $allTransitiveDependencies: null,
+ );
+
+ @override
+ String debugGetCreateSourceHash() => _$geoResourceActionHash();
+
+ @$internal
+ @override
+ GeoResourceAction create() => GeoResourceAction();
+
+ /// {@macro riverpod.override_with_value}
+ Override overrideWithValue(void value) {
+ return $ProviderOverride(
+ origin: this,
+ providerOverride: $SyncValueProvider(value),
+ );
+ }
+}
+
+String _$geoResourceActionHash() => r'980385b1cc4e685e0e2732471083c63d29b59c10';
+
+abstract class _$GeoResourceAction extends $Notifier {
+ void build();
+ @$mustCallSuper
+ @override
+ void runBuild() {
+ final ref = this.ref as $Ref;
+ final element =
+ ref.element
+ as $ClassProviderElement<
+ AnyNotifier,
+ void,
+ Object?,
+ Object?
+ >;
+ element.handleCreate(ref, build);
+ }
+}
diff --git a/lib/providers/generated/app.g.dart b/lib/providers/generated/app.g.dart
index dccb4736..213ea094 100644
--- a/lib/providers/generated/app.g.dart
+++ b/lib/providers/generated/app.g.dart
@@ -1791,7 +1791,7 @@ final class NetworkDetectionProvider
}
}
-String _$networkDetectionHash() => r'e2892c87c76992bec307699de7d838152aa272d5';
+String _$networkDetectionHash() => r'1cab20d67ec54321b4dbba9d971cd80e98542e23';
abstract class _$NetworkDetection extends $Notifier {
NetworkDetectionState build();
diff --git a/lib/providers/generated/database.g.dart b/lib/providers/generated/database.g.dart
index c2301f6e..a1eea995 100644
--- a/lib/providers/generated/database.g.dart
+++ b/lib/providers/generated/database.g.dart
@@ -292,7 +292,7 @@ final class ProfilesProvider
}
}
-String _$profilesHash() => r'a37c94a2b4f8c9aabb25e1c90b22a760507611f8';
+String _$profilesHash() => r'a977548501ae750bc4fcc0f59dc0a4994ced7c91';
abstract class _$Profiles extends $Notifier> {
List build();
@@ -336,7 +336,7 @@ final class ScriptsProvider
Scripts create() => Scripts();
}
-String _$scriptsHash() => r'c5c3c1a4529be6e13a9516ca0b6f98a2e0e127b4';
+String _$scriptsHash() => r'363611e5787ec107459446f305e35dada3e07cad';
abstract class _$Scripts extends $StreamNotifier> {
Stream> build();
@@ -449,7 +449,7 @@ final class GlobalRulesProvider
GlobalRules create() => GlobalRules();
}
-String _$globalRulesHash() => r'fcded4e1dc862ac0ddd3223df9b723072d6f2fd5';
+String _$globalRulesHash() => r'209223171050c7d26de48537f8ff4f14d6fe5b1d';
abstract class _$GlobalRules extends $StreamNotifier> {
Stream