Skip to content

Collapse the per-app controls into rows that open on demand - #733

Merged
kasnder merged 2 commits into
claude/per-app-remote-vpn-routingfrom
claude/details-protection-sheet
Aug 20, 2026
Merged

Collapse the per-app controls into rows that open on demand#733
kasnder merged 2 commits into
claude/per-app-remote-vpn-routingfrom
claude/details-protection-sheet

Conversation

@kasnder

@kasnder kasnder commented Aug 19, 2026

Copy link
Copy Markdown
Member

Protection and Remote VPN each showed every option's full explanation at all
times: eight paragraphs of copy between the tab bar and the first tracker, so
the list the screen exists for started two and a half screens down. The
explanations are worth their length — "Trackers allowed" versus "Bypass
TrackerControl" is not a choice anyone should make from the label alone — but
paying for them on every visit is what pushed the trackers off the screen.

Each control is now a row carrying its current value ("Protection —
Protected"), and the radio list with the full explanations lives in a bottom
sheet behind it. Comparing the options before choosing still works, because the
sheet shows all of them together with their copy intact; it just no longer
happens by default. The tracker categories are back on the first screen.

The remote-routing row states its own unavailability instead of hiding: when
there is no remote VPN, the app bypasses TrackerControl, or the tunnel carries
only part of the traffic, the reason sits where the value would be and the row
does not open.

Nothing about the underlying state changes. resolve/applyState, the reload
gating, and the per-app wg_route write are the same calls in the same order —
the diff is the view layer, and AppProtectionStateTest and
RemoteRoutingLogicTest still cover the logic unchanged. No new strings, so no
locale file is touched, and no new JVM test: this adds no logic class, only
binding plus a stateLabelRes switch mirroring the existing radioIdFor.

Two details the radio groups did not have to handle. The sheet dismisses before
the state is applied, because applyState triggers a rebind and the old
isPressed() guard against programmatic re-checks has no equivalent here. And
the sheet is dropped in onDetachedFromRecyclerView, so a details screen torn
down with one open does not leak its window.

Verified on an emulator (fdroid debug): rows render with their current values,
the sheet opens with the right option checked, selecting one applies it and
updates the row, and the Remote VPN row shows app_route_unavailable_no_vpn
with no chevron when nothing is configured. :app:testGithubDebugUnitTest and
:app:lintGithubDebug pass.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

🤖 Generated with Claude Code

@kasnder
kasnder force-pushed the claude/details-protection-sheet branch from 30587c1 to ae52266 Compare August 19, 2026 21:23
@kasnder
kasnder marked this pull request as ready for review August 20, 2026 10:28
kasnder and others added 2 commits August 20, 2026 12:52
Protection and Remote VPN each showed every option's full explanation at
all times: eight paragraphs of copy between the tab bar and the first
tracker, so the list the screen exists for started two and a half screens
down. The explanations are worth their length — "Trackers allowed" versus
"Bypass TrackerControl" is not a choice anyone should make from the label
alone — but paying for them on every visit is what pushed the trackers off
the screen.

Each control is now a row carrying its current value ("Protection —
Protected"), and the radio list with the full explanations lives in a
bottom sheet behind it. Comparing the options before choosing still works,
because the sheet shows all of them together with their copy intact; it
just no longer happens by default. The tracker categories are back on the
first screen.

The remote-routing row states its own unavailability instead of hiding:
when there is no remote VPN, the app bypasses TrackerControl, or the
tunnel carries only part of the traffic, the reason sits where the value
would be and the row does not open.

Nothing about the underlying state changes. resolve/applyState, the
reload gating, and the per-app wg_route write are the same calls in the
same order — the diff is the view layer, and AppProtectionStateTest and
RemoteRoutingLogicTest still cover the logic unchanged. No new strings,
so no locale file is touched.

Two details the radio groups did not have to handle. The sheet dismisses
before the state is applied, because applyState triggers a rebind and the
old isPressed() guard against programmatic re-checks has no equivalent
here. And the sheet is dropped in onDetachedFromRecyclerView, so a
details screen torn down with one open does not leak its window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kasnder
kasnder force-pushed the claude/details-protection-sheet branch from ae52266 to aac7675 Compare August 20, 2026 10:56
@kasnder
kasnder merged commit b8cec6c into master Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant