duo_fold library

Fold-aware layout for Apple's iPhone Duo, extracted from Sensecast.

See README.md for the design notes and the simulator-only caveat.

Classes

DuoBookSplitNotifier
DuoFoldBridge
Thin wrapper over the foldable plugin so tests can substitute fakes. The machine-id channel is how the provider seeds a Duo on first launch: the app answers hw.machine (e.g. iPhone19,4) over a method channel. The default channel name is the one Sensecast's AppDelegate answers; pass your own name if your native side registers a different channel, or override machineIdentifier outright.
DuoFoldLayoutSignature
The fields of a DuoFoldState that a layout decision can read.
DuoFoldState
Immutable snapshot of Duo fold state. Everything downstream keys off this; on non-Duo devices it is a constant no-op value.
DuoGlanceEnabledNotifier
EdgeRailGeometry
FoldConfinedRoute<T>
A pushed route whose content confines itself to the fold's secondary (detail) pane while a vertical split is in effect. Readable content like search results must not straddle the physical crease. The exposed primary pane keeps the previous route visible under a dismissible barrier, mirroring the split-Settings detail idiom.
FoldLayout
Pure decisions about fold geometry. Nothing here touches the platform. Every input arrives via DuoFoldState and the caller's constraints.
FoldSplitView
FoldStartupGate

Enums

DuoHingeStatus
Hinge posture as reported by the foldable bridge.
DuoPose
What the device is physically doing, derived from posture + regions.
FoldMode
The resolved layout treatment for the current fold state.

Constants

foldableMachineIds → const Set<String>
Machine ids that are certainly foldables, so a first launch can seed isDuo before the hinge interaction answers. iPhone Duo reports iPhone19,4 (AppleDB). A stored verdict always wins over this map: a device that was settled non-foldable once is never re-seeded by it.

Properties

duoBookSplitProvider → NotifierProvider<DuoBookSplitNotifier, bool>
final
duoFoldBridgeProvider → Provider<DuoFoldBridge>
final
duoFoldProvider → StreamProvider<DuoFoldState>
Latest fold state. The native changes stream is open-ended, so consumers must read .value. duoFoldProvider.future waits for the stream's last event and would never resolve while the device lives.
final
duoGlanceEnabledProvider → NotifierProvider<DuoGlanceEnabledNotifier, bool>
final

Functions

duoFoldForLayout(WidgetRef ref) → DuoFoldState
Watches the fold state at layout granularity: the calling widget rebuilds when any field a layout decision can read changes (pose, division/occlusion bands, size classes, hinge status) but not on raw hinge-angle ticks inside one pose, which a physical sweep emits continuously (pose carries every angle-derived layout rule). Listeners that need each event keep ref.listen(duoFoldProvider, …).
duoStateFromFoldable(FoldableData data) → DuoFoldState
Maps a plugin snapshot into our model. A verdict is settled when the bridge has stopped answering unknown: that is the signal the startup gate waits for, and the only verdict the cache persists. FoldableData.unsupported is the settled "not a foldable" answer, not the unsettled one (that const carries supportLevel available).
foldAwareDialogBuilder(BuildContext context, WidgetBuilder builder) → WidgetBuilder
Wraps a dialog's builder content so it centers inside the primary pane instead of straddling the fold. Pass-through on non-split modes. The pane anchor is re-evaluated on every fold event, so a dialog already on screen follows the device through book, flat, and table poses.
showFoldAwareDialog<T>({required BuildContext context, required WidgetBuilder builder, bool barrierDismissible = true}) → Future<T?>
showDialog that anchors the dialog inside the active fold pane. Same fold rules as showFoldAwareSheet; outside an active fold it calls showDialog untouched.
showFoldAwareSheet<T>({required BuildContext context, required WidgetBuilder builder, bool isScrollControlled = false, Color? backgroundColor, ShapeBorder? shape, bool showDragHandle = false}) → Future<T?>
Like showModalBottomSheet, but on a folded Duo the sheet is confined to a pane: the primary pane for a book fold (left in LTR, right in RTL), the bottom pane for a table fold. The confinement is re-evaluated while the sheet is open: a mid-presentation fold transition moves the sheet to the matching pane instead of leaving it across the crease.