Skip to content

iPhone Duo adaptation — fold foundation + home two-pane alignment (slices A, B/C) - #687

Merged
getgroupmy merged 4 commits into
mainfrom
claude/iphone-duo-adaptation
Nov 13, 2026
Merged

getgroupmy merged 4 commits into
mainfrom
claude/iphone-duo-adaptation

Conversation

@getgroupmy

@getgroupmy getgroupmy commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Adapting the UI to a foldable (outer / inner displays) without breaking standard iPhones. Every change is gated on a reported separating fold, so it is a no-op on all current iPhones (MediaQuery.displayFeatures empty → nothing engages).

Slice A — window size-class + fold foundation

Pure, tested lib/src/core/window_class.dart: WindowWidthClass/WindowHeightClass (a test pins widthClassOf to wideLayoutAt), foldOf/postureOf, hitsFold, panesAround, verticalSplitFor, windowMetricsFor.

Slice B/C — home two-pane aligned to the fold

The home screen's existing map + panel two-column layout now lines its divider up with a vertical book-fold instead of a fixed 420px, reusing the wide layout (no map rebuild). Falls back to the fixed width when there's no fold or the panes would be too narrow.

Slice D — adaptive audit

No code changes needed: the app already decides layout from window size (not orientation or device idiom), uses no hardcoded device-size tables, and keeps trip state across resize by construction. Documented.

Slice E — fold bridge + simulation harness

Brings an iPhone Duo's crease into the app through the same MediaQuery.displayFeatures path Android already uses, so no screen needs an iOS branch:

  • core/duo_fold.dart — parse a native fold message → synthesize a fold feature (only when the crease actively divides), merge without doubling a platform-reported fold, plus simulatedBookFold/simulatedTabletopFold. Pure + tested.
  • widgets/duo_fold_scope.dart — injects the native (EventChannel get_ride/duo_fold) or simulated crease into MediaQuery; no-op when none; native channel absent off iOS.
  • app.dart — wraps the router in DuoFoldScope.
  • docs/iphone_duo_native_plugin.md — the iOS Swift plugin (UIHingeInteraction + reservedRegions, from pvinis/duo-demo's WindowProbe) to build on Xcode 27.1.

Validated against the official Flutter engine PR

flutter/flutter#193025 (open) adds the same iOS display-feature population to the engine. Its mapping matches ours exactly — division → DisplayFeatureType.fold, zero-width postureHalfOpened while the hinge is partially open, logical-point bounds. So once the app's Flutter includes that PR, the native plugin can be deleted and mergeFoldFeatures already yields to the engine's feature (no doubling). Android (Fold/Flip/Pixel Fold/Surface Duo) needs no plugin — Flutter already fills displayFeatures from WindowManager.

Validation

dart analyze clean; flutter test green (window_class + duo_fold + duo_fold_scope + home suites, 120 in the combined run). The simulation harness (duoFoldSimulation) exercises the whole layout path on an ordinary phone, as the widget tests do.

Deferred — needs Xcode 27.1 + Duo simulator

Compile/verify the Swift plugin; phone-layout sheet on a fold, tabletop (horizontal) folds, pins near the seam, half-open ergonomics — all need visual verification on the simulator.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NDfeQQMcJvAnDpYN9Mwyb6

First slice of adapting the UI to a foldable (outer/inner displays) without
breaking standard iPhones. A pure, tested foundation that reads the actual
window geometry and reported display features — never a device name or a
fixed size table:

- WindowWidthClass / WindowHeightClass from the window size, on the app's
  existing wide-layout thresholds (a test pins widthClassOf to wideLayoutAt
  so they can never drift).
- foldOf / postureOf: a fold or hinge from MediaQuery.displayFeatures, with
  whether it separates the window (a hinge always; a fold only half-opened —
  a flat fold is a seamless display) and which way the seam runs.
- hitsFold: keep a button/card clear of the seam. panesAround: split an area
  into two panes around the seam.
- windowMetricsFor(context): the live reader, rebuilding on fold/resize.

No-op on every current iPhone (no fold reported). Nothing consumes it yet;
later slices route the home map, confirm actions/SOS and call screen around
the fold and add the two-pane inner-display layout.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NDfeQQMcJvAnDpYN9Mwyb6
… B/C)

The home screen already lays out map + booking/trip panel as two columns at
regular width. On a foldable held open as a book, line that divide up with
the fold so neither the panel nor the map crosses the crease, instead of a
fixed 420px panel that could straddle it.

- window_class: verticalSplitFor(metrics) returns the panel width and seam
  gap from a separating vertical fold, or null when there is none or the two
  panes it would make are too narrow to use (minLeft/minRight guards). Pure +
  tested.
- home_screen wide branch: panel width and the divider come from that split
  when present, else the existing fixed width + hairline divider.

No-op on every non-folding device: displayFeatures is empty, so the split is
null and the layout is byte-for-byte the current one. The folded/half-open
phone-layout sheet and horizontal (tabletop) folds are deferred to the
on-simulator pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NDfeQQMcJvAnDpYN9Mwyb6
@getgroupmy getgroupmy changed the title iPhone Duo adaptation — window size-class + fold foundation (slice A) iPhone Duo adaptation — fold foundation + home two-pane alignment (slices A, B/C) Nov 13, 2026
Brings the Duo's crease into the app through the same MediaQuery.displayFeatures
path an Android foldable already uses, so no screen needs an iOS branch.

- core/duo_fold.dart: DuoFoldSignal (hinge pose + crease rect + occlusions),
  duoFoldFromNative (parse the channel message), displayFeatureFor (crease ->
  a half-opened fold feature only when it is actively dividing), mergeFoldFeatures
  (add the synthesized feature only when the platform reports none, so Android is
  never doubled), and simulatedBookFold / simulatedTabletopFold. Pure + tested.
- widgets/duo_fold_scope.dart: injects the native (EventChannel get_ride/duo_fold)
  or simulated crease into MediaQuery.displayFeatures. No-op when none; the native
  channel is absent off iOS and its first listen error is swallowed.
- app.dart: wraps the router content in DuoFoldScope.
- docs/iphone_duo_native_plugin.md: the iOS Swift plugin (UIHingeInteraction +
  UIView.reservedRegions, modelled on pvinis/duo-demo's WindowProbe) to build and
  verify on Xcode 27.1 — written from reference code, not compiled here. Android
  needs no plugin (Flutter already fills displayFeatures from WindowManager).

The simulation harness (duoFoldSimulation) exercises the whole layout path on an
ordinary phone, as the widget tests do.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NDfeQQMcJvAnDpYN9Mwyb6
…tures)

Record that Flutter itself is adding iOS display-feature population
(flutter/flutter#193025), which validates our mapping (division ->
DisplayFeatureType.fold, zero-width postureHalfOpened while the hinge is
partially open, logical-point bounds) and reframes the native plugin as a
stopgap for Flutter versions before that PR lands — mergeFoldFeatures already
yields to the engine's feature once it does. Also capture the reserved-region
timing lag (read in viewDidLayoutSubviews; no change notification).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NDfeQQMcJvAnDpYN9Mwyb6
@getgroupmy
getgroupmy merged commit 1204eb9 into main Nov 13, 2026
23 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.

2 participants