Repository navigation
iPhone Duo adaptation — fold foundation + home two-pane alignment (slices A, B/C) - #687
Merged
Merged
Conversation
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
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
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.
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.displayFeaturesempty → nothing engages).Slice A — window size-class + fold foundation
Pure, tested
lib/src/core/window_class.dart:WindowWidthClass/WindowHeightClass(a test pinswidthClassOftowideLayoutAt),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.displayFeaturespath Android already uses, so no screen needs an iOS branch:core/duo_fold.dart— parse a native fold message → synthesize afoldfeature (only when the crease actively divides), merge without doubling a platform-reported fold, plussimulatedBookFold/simulatedTabletopFold. Pure + tested.widgets/duo_fold_scope.dart— injects the native (EventChannel get_ride/duo_fold) or simulated crease intoMediaQuery; no-op when none; native channel absent off iOS.app.dart— wraps the router inDuoFoldScope.docs/iphone_duo_native_plugin.md— the iOS Swift plugin (UIHingeInteraction+reservedRegions, from pvinis/duo-demo'sWindowProbe) 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-widthpostureHalfOpenedwhile the hinge is partially open, logical-point bounds. So once the app's Flutter includes that PR, the native plugin can be deleted andmergeFoldFeaturesalready yields to the engine's feature (no doubling). Android (Fold/Flip/Pixel Fold/Surface Duo) needs no plugin — Flutter already fillsdisplayFeaturesfrom WindowManager.Validation
dart analyzeclean;flutter testgreen (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