Repository navigation
Routing: built-in rules, App-based routing section, region chip - #2365
Merged
Merged
Conversation
Update the hiddify-core submodule to db74dfc (main HEAD), which moves hiddify-sing-box onto the testing-merge-base line (AWG/amnezia option API, public RedirectStderr, removed broken client submodules). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return a CupertinoPage from customTransition() on iOS so pushed pages get the native interactive edge-swipe (pop) gesture, which CustomTransitionPage does not support. Other platforms keep the existing slide/fade transitions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the success heavy-impact from the Connected() transition to the moment the tunnel has a valid url-test ping, so it fires when the connection actually works. Add a medium then heavy buzz when a live connection times out, and a heavy buzz when it recovers. Extract the delay-validity threshold into ConnectionConst.maxDelay / isValidDelay as the single source of truth, and route the connection button, system tray, proxy tile, and delay indicator through it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the leading action icon with a live status: a spinning indicator while updating, then a transient success/failure icon (cross-faded). The result is held for 2s inside UpdateProfileNotifier so it survives list rebuilds/reordering. Also tidy ProfileTile (extract body/title/tap helpers, drop commented-out code and the unused color param) and remove the never-used ProfileTileMain widget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route bulk updates through UpdateProfileNotifier.updateProfile so each tile shows its status, and report one aggregate toast (N success, M failed with names) instead of one per profile. Single user-initiated updates no longer toast on success. Toast titles now wrap to multiple lines and error toasts stay longer to fit the failed names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Key profile tiles by id so their update indicator and animation aren't lost when the list reorders (it sorts by last-update, so an updated profile jumps position). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix swapped sort labels, replace the ambiguous up/down arrow with explicit direction labels (A -> Z / Z -> A, Newest/Oldest first) shown as a subtitle, and default to the natural direction per sort type. Persist the sort choice via a new general preference so it survives app restarts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add translations for bulkSuccess, bulkPartial and the sort direction labels (newestFirst/oldestFirst) across all locales, matching the keys added to en. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove pages.profiles.msg.update.successNamed (single updates no longer toast on success) and components.subscriptionInfo.{remainingTime, remainingDurationNew, remainingUsage, profileSite, profileSupport} (only used by the deleted New*SubscriptionInfo widgets) from en and all locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
English base-locale strings for the profile pin/unpin action. Kept as its own commit (source locale); other languages follow separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a pinned column (DB schema v7 + migration; drift steps/schema/test helpers regenerated) plus a README documenting the migration workflow. The profiles list now orders active -> pinned -> sorted; Pin/Unpin is the first profile-menu item and pinned list tiles show a pin badge. Pin state persists and a subscription refresh no longer clobbers it (toUpdateEntry omits pinned). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Translate common.pin / common.unpin into all non-English locales. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the aider-based translation helper and migrate its workflow into Claude Code project instructions. - delete .aider.conf.yml and SLANG.md - remove aider history/cache entries from .gitignore - add CLAUDE.md documenting the slang translation workflow - git-ignore slang analyze reports (_missing/_unused_translations*.json) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `?url=` deep-link form documented as `hiddify://install-config?url=<uri>#<name>` never picked up the name: the parser only read a `name=` query parameter and ignored the fragment. Prefer an explicit `name=` and fall back to the fragment. Adds tests covering both deep-link forms and the other panel schemes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a `showLeading` flag to LanSharingPreferenceWidget and turns it off in the quick settings modal, where the leading icon is redundant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switches the profiles modal from a Column to a Stack so the buttons float above the list instead of taking their own row. Bottom list padding is increased to keep the last profile reachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Strings for the profile web page / support chips and for the warning shown when a subscription-provided link is not in the trusted list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`profile-web-page-url` and `support-url` were stored on SubscriptionInfo, so they were only kept when the `subscription-userinfo` header was also present. They are independent headers, so they move onto RemoteProfileEntity and are now parsed unconditionally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scammers publish look-alike channels and sites (e.g. t.me/Hiddiify vs the real t.me/hiddify) inside fake subscriptions to collect donations meant for real providers. This adds the list used to tell a verified provider link from an unverified one. Matching is exact on the whole url, after normalising the scheme, a leading `www.`, host case, a trailing slash and any fragment — so a look-alike handle never matches. The list is managed by the project manager and holds Hiddify only for now; there is no public request flow to be added to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reworks the unverified-link dialog so it informs rather than alarms: an unverified link is not necessarily malicious, so it drops the icon and the error styling and states plainly that the link cannot be verified. The Open button stays disabled for 10 seconds with a visible countdown, the same friction used for sensitive permissions, so the note is actually read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renders the provider's web page and support links as a single chip below the main profile card. With both present the chip cycles every 10s: it slides up behind the card, then the next one slides down into place. The card's bottom corners square off so the chip reads as attached to it. A tapped link is checked against the trusted list first: a trusted link opens after a plain confirmation, anything else goes through the countdown warning. Hidden behind ProfileTileConst.showProviderLinks while the design is decided. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The header was split on `;` and every segment was assumed to be a `key=value` pair, reading index 1 unconditionally. A trailing `;`, a stray `;;` or any malformed token therefore threw — and because parse() is wrapped in a single try/catch, the whole profile failed to load: no name, no traffic, nothing. A trailing semicolon is common in the wild. Segments without a `key=value` pair are now skipped instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The interval was read with int.parse, so a decimal or any non-numeric value threw and — like the subscription-userinfo case — failed the entire profile parse rather than just the auto-update setting. The convention is a positive integer number of hours, so anything else (garbage, a decimal, zero or negative) is now ignored and the profile keeps its default update behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two gaps in the name hierarchy: content-disposition was matched with `filename="..."` only, so the RFC 5987 extended form `filename*=UTF-8''<percent-encoded>` — how servers send a non-ASCII name — was skipped and the provider's real name was lost. The extended form is now preferred and decoded, falling back to the quoted form. The steps also mixed `isEmpty` with `isBlank`: a whitespace-only result was treated as a name by the later steps but as blank by the final fallback, so a real url fragment or filename could be dropped in favour of "Remote Profile". Every step now treats whitespace as blank. Also documents that the url-based steps are remote-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eader Replaces the `enable-fragment` boolean with `fragment`, which carries the same value as a proxy link's own `fragment` parameter, so a provider can set the size and sleep instead of only switching fragmentation on. The value is read exactly like a proxy link: a full `size,sleep,method` triplet, or `tlshello,size,sleep`. Anything shorter, or a size or sleep that isn't a valid range, is incomplete and leaves fragmentation off rather than enabling it with half the values. The user's own setting still wins, and enables fragmentation with the size and sleep from their settings — it carries no values of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Warp and psiphon are mutually exclusive — the core runs one chain mode at a time — so the two boolean headers become a single `extra-security` header holding the mode. UserOverride follows, replacing `enableWarp`/`enablePsiphon` with `extraSecurity`, bumped to v2 with a read-time migration; psiphon wins when both were set, matching v1 where it overrode warp. The value is matched strictly against ChainMode: it must equal a documented mode key exactly or it is ignored, so no case folding and no lists. `profile` is never accepted from a subscription, since chaining to another profile is the user's choice. ChainMode and ChainStatus also replace the hardcoded strings. The raw header is consumed rather than passed through — `extra-security` is on the output allow-list, so an unrecognised value would otherwise reach the built config as a string where the core expects an object. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tile no longer shows or edits a password, so the title itself has to tell the user the shared link is protected. Drop the two keys the old password UI needed: - lanSharingPassword (input dialog title) - lanSharingPasswordNotSet (subtitle placeholder) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirror the English change in the other ten locales: lanSharingPassword and lanSharingPasswordNotSet no longer exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The user had to invent and maintain the LAN sharing password, and an empty one silently shared the VPN with no authentication at all. Generate a 16-character password with Random.secure the first time sharing is turned on, store it, and reuse it from then on. Sharing that was already enabled without a password gets one as soon as the tile is shown. Visually ambiguous characters are left out of the alphabet so the password survives being typed by hand from the QR code. With the password out of the user's hands, the edit dialog, the subtitle and the no-password link branch are gone, and the tile is a plain SwitchListTile again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The protobuf/gRPC output under lib/hiddifycore/generated/ is produced by protoc and json_editor.dart is a vendored copy of json_editor_flutter. Neither is ever hand-edited, yet together they accounted for 148 of the 243 analyzer diagnostics, burying the findings in our own code. Compilation still covers both; only the analyzer stops reporting them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
While auto selection is on and the list differs from the region's list, a bar counts the changes. Share opens a sheet, shown like the app's other sheets, with exactly what gets sent and where before it opens GitHub. The share item leaves the menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The expandable button offered a new rule and the predefined rules, which the built-in rules have replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The region leaves the bottom panel for a chip beside the menu, with its flag and the word Region. Changing it still resets the direct DNS server, whose default follows the region. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel, its Routing tab and the setting that kept it open are gone. Its options move to the top of the rules list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built-in rules keep the list from being empty, so its empty text goes and the menu always offers every item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Balancer, Resolve destination and IPv6 sit above the rules and can't be moved. Each value sits at the end of its row and opens its menu there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Android, App-based routing heads the rules list, since it applies before every rule. Its switch turns it on and off, and its two modes sit under its name, the current one in a tinted pill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While it's on, a row shows up to ten app logos, the user's own picks first, and opens the app list. It keeps its height while loading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Outside region Other, a row turns auto selection on and off, updates it now, and shows when it was updated or why its list is missing. While it loads, a bar takes the switch's place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The region or the mode can change while a list loads, and that change already settled auto selection. A list that arrives after it is dropped instead of bringing back the old region's or mode's picks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mode change cleared only the old mode's auto picks, so when the new mode's list failed to load, auto selection still showed on with no picks, and Share counted the user's own apps as added. It now turns auto selection off first, and a list that loads turns it back on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing null to that preference fails and only logs a warning, so the date was never cleared. Nothing reads it while auto selection is off, and turning it on writes a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AppList copied its family argument into a late final field in build, so a second build threw, and its exports watched the translations, which rebuilds it on a language change. It now uses the generated mode, and the exports read the translations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its one caller always passes the mode and the region, so they are required, and the unused include/exclude getters, result helpers and preference fallbacks are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The region listener needs no checks: it only fires on a change. The list page restores the removed auto picks through the data source it already uses for Undo, so AppBasedRouting no longer passes it along. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mode change can start a run while another still loads. The first one to end hid the bar and brought back the switch while the other was still loading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flags were cleared through a cascade, which kept the column as it was, so an import merged into the current lists although its dialog says it replaces them. Auto picks stay, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an empty Proxy only list, Android lets every app into the VPN, but the package picker marked every app as outside it, and every picked one as a rule that won't apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every change renumbers the list or keeps each rule in its place, so the list is always in order. The sort also changed the state in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The allowed built-ins, the ones found and the checks between them become one ordered set: a built-in seen for the first time is crossed off, and what's left goes first. Same behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The intro sets the region after the rules load with region Other, so the region rule was added on top, above Block ads, while Reset puts it last. A list that was never saved is the default list, so it is now rebuilt in the default order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chip read the DNS setting after waiting for the region to save, when the page may already be gone. It now takes it before the wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The days between two midnights were counted in whole days, so a 23-hour day at a clock change turned yesterday into today. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The logos on the routing page and in the share sheet were decoded for a 2x screen, so most phones scaled them up and blurred them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The menu no longer has a short form for an empty list, so its items are built in its itemBuilder instead of on every rules change, and the two export items use plain tear-offs like the others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They were pinned to the bottom left, which in Persian and Arabic is the end of the line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dynamic_color 1.7 leaves surfaceContainerHighest unset, so it falls back to the page color and the chips disappear. Until the Flutter upgrade, they use surfaceVariant, the same tone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old region picker never reset it: its handler never ran. So the chip doesn't either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - .gitignore: this branch drops the aider entries; upstream adds the local signing material. Both kept. - analysis_options.yaml: both sides' analyzer excludes kept. - Makefile: upstream's portable zip (7z, or Compress-Archive) from hiddify#2359, which fixes the same thing as this branch's tar.exe line. - hiddify-core: upstream's e5b8ef2, which already contains db74dfc. - profile_parser_test.dart: both new test groups kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworks Settings → Routing: built-in rules, an App-based routing section (formerly Per-app proxy, Android only), and the general options moved out of the bottom panel.
Depends on #2333. This branch is built on it. Its own commits start after
8aaeb368; the diff shrinks to them once #2333 is merged.mainis merged in, so it has no conflicts.Rules
App-based routing (Android)
per_app_proxy_enabled). Preferences migration v2 moves the oldoffvalue. Android reads the new key, and falls back to the old rule when the Quick Settings tile or a reboot starts the VPN before the migration has run.General options
Toasts
Notes
{region}, which the core still has to replace.Testing
Android emulator, API 36, upgraded over a saved
excludemode:After merging
main:dart analyzeis clean, andflutter testpasses (150 tests).🤖 Generated with Claude Code