adaptive_nav 0.14.0
adaptive_nav: ^0.14.0 copied to clipboard
A Navigator 2.0 router that renders one navigation state as a stack or as master-detail panes and keeps screen state across resizes and folds. Ready for iPhone Duo.
0.14.0 #
Features #
AdaptiveNav: the router as one object, aRouterConfigforMaterialApp.router(routerConfig: nav)that carries the navigation API. It is not aListenable, so it fits a Riverpod provider underriverpod_lint.AdaptiveNavowns everything that notifies, the immersive flag and pane split included (immersive,resizablePanes,paneFractions,onPaneCommit). The config fields stay for a hand-builtRouter.
Changed #
- A deep link replaces only the target branch: its root and any screen already
in the stack are kept, and the other tabs keep their stacks. A deep-linked
screen gets its branch's
transition. redirectsees the start exactly once, with or without a codec.statebefore the first frame is the initial state as given.goBranchwith an index outside the branches throws anArgumentError.mountedBranchesis read-only and@visibleForTesting.- The router asserts that the config has branches, and the stock
NavigationBarthat it has at least two in the chrome. - Gated mutations with no guard involved notify listeners in the same call,
as
pushdoes. Calling one frombuildnow fails an assert. - Disposing the delegate completes every pending
pushwithnull;pushafter dispose asserts.
Fixes #
- System back at a guarded root with nowhere to switch to leaves the app once
the guard agrees. The app's own
maybePopthere still does nothing. - Two panes over a bottom bar no longer get the bottom inset twice.
- Exit guards no longer race:
pop,replace,resetTo,popToRootandresetDetailToremove only what they asked about and keep anything pushed meanwhile, in any branch. - A screen removed by any route completes its
pushfuture withnull. - An
onExitguard that throws counts as a refusal and is reported; one that answers after dispose changes nothing. - Right-to-left locales no longer mirror the wide layout.
- A rail column too short for two destinations puts all of them behind the overflow menu.
- With
immersiveconfigured, the rail sits where it does without it. - Dragging the pane boundary, the detail reveal and the immersive slide no longer rebuild every mounted screen each frame.
MasterDetailConfig.fitsAroundtakes the pane gap into account.PaneSplitController: a drag that ends where it began commits nothing, a drag writes only to its own branch, andrestoreleaves it alone. A handle that leaves mid-drag ends the drag.- The empty detail's
detailPlaceholderis no longer rebuilt every frame of a drag or the detail reveal.
0.13.0 #
Breaking #
- Below 600 across, without the side insets, a window is always a single
screen, whatever
MasterDetailConfig.fitssays (kCompactWidthBreakpoint,PaneMetrics.allowsTwoPanes). A phone no longer splits over its bar, and a window narrowed through 600 no longer goes back to two panes when the rail gives way to the bar. iPhone Duo's inner display in portrait, 669 across, keeps its two panes.
Features #
AdaptiveShellConfig.liftHeadersIntoStatusRow(experimental, iOS): on iPhone Duo's inner display in portrait the panes' headers move up beside the clock, which takes only the top right corner. The pane under the glyphs reports their width throughStatusRowScope.trailingReserveOf(context)for the end of itsAppBaractions. Off by default.MasterDetailConfig.portraitPaneRatio: the master's share in a portrait window, ahead ofalignToWindowCenterandpaneRatio. The user's drag, a fold and the pane minimums still win.AdaptiveShellConfig.actionsInStatusColumn(experimental, iOS): on iPhone Duo's inner display in landscape the empty part of the status column, below the clock, goes to the pane against it (StatusColumnScope.of(context)).StatusColumnActionsmoves a screen's header actions there and back with a short fade. Off by default.
Fixes #
- On Android the rail no longer sits under the navigation buttons or a camera
cutout on its edge in landscape: its background reaches the edge, its
destinations start after the inset (
PaneMetrics.railCutoutInset).
0.12.0 #
Features #
AdaptiveShellConfig.backToBranch: system back at the root of any other branch switches to that branch instead of closing the app, the way most Android apps return to their start tab. Dialogs, the drawer, pushed screens and a detail are popped first, and the root'sonExitis asked as on a tab tap.null, the default, keeps the old behaviour.MasterDetailConfig.alignToWindowCenterputs the boundary between the panes on the window's horizontal centre instead of atpaneRatio. The user's drag and an active fold still win, and the pane minimums still apply.
Fixes #
- On Android with predictive back, system back could close the app instead of
going back — after a push into a branch without master-detail, or after
switching to a tab with screens above its root. Every navigator of the shell
reported to the system and the last one won. The shell now sends one answer
for all of them. Apps that worked around this with their own
onNavigationNotificationinMaterialApp.routercan remove it. - System back closes a dialog on the root navigator (the default for
showDialog) instead of the screen under it or the app. - System back closes an open shell drawer, with predictive back too. A drawer
or
showBottomSheetinside a screen still is not reported to the system; see "System back" in the README. - A screen's own
PopScope(canPop: false)now holds system back for an entry pushed withoutonExit; the screen used to be removed anyway.
0.11.0 #
Breaking #
- In the rail layout the rail and the panes now extend under the status bar,
as the panes above a bottom bar already did. Before, the whole layout started
below it and the status bar showed one colour (the canvas or the
Scaffoldbackground) across all three. Now it shows the rail's colour over the rail and each pane's colour over that pane. The rail reserves the inset itself; a pane passespadding.top − margin.topto its screen. A screen with anAppBarneeds no change. A screen without one has to handleMediaQuery.padding.topitself, as it does on a phone.
Fixes #
- On iPhone Duo's outer display a flush rail left an unpainted gap between
itself and the window edge. The gap is now painted in the rail's colour.
New
RailDecoration.isFlushreports whether the rail has no margin and no corner radius.
Example #
- Panes are flush, in three shades, with the drag marker on the boundary.
lib/playground.dart: the example in a desktop window, a phone, a tablet, iPhone Duo and Galaxy Z Fold8, with rotate and fold buttons. Built for the web and published from.github/workflows/pages.yml.
0.10.0 #
iPhone Duo. The navigation follows the system's own bars on both displays and in every orientation, the pane arithmetic counts the safe area, and the screens keep their state through every fold. Measured on the iPhone Duo simulator under Xcode 27.1; build against the iOS 27.1 SDK.
Breaking #
AdaptiveShellConfig.showsRailis nowchromeLayout, and returns aChromePlacement—bottom,leftorright— instead of a bool.ChromePredicateanddefaultShowsRailare gone; the replacements areChromeLayoutanddefaultChromeLayout.- An empty detail pane with no
BranchConfig.detailPlaceholderis now blank, where it used to read "Select an item". The package has no screens of its own and could not localise that string; a caption belongs to the app.
iPhone Duo #
- The rail can sit on the right, and on the outer display it joins the system's vertical bar instead of taking a second column: it is centred on the same axis as the clock and the camera, and moves to the other edge as the phone turns.
AdaptiveShellConfig.systemBar, aSystemBarMetrics, keeps the camera's end of that column free — 150 points upright, 80 in landscape, where the system hides the clock. The camera is in a corner of the glass, so turned one way it ends up at the bottom of the column;SystemBarMetrics.endfollows it there.defaultChromeLayoutreads the posture offMediaQuery.paddingalone: a vertical inset of 60 points or more on exactly one side is the system bar. The rail follows it only on a display compact enough to need it — the outer one. On the inner display in landscape the rail leads, the far pane bleeds under the bar, andSystemBarMetrics.cornerClearancekeeps the first destination out of the rounded corner.- A bottom bar with two panes above it, for the inner display in portrait: 669 points across, with horizontal bars. The pane cards run up under the status bar and the top inset is reserved once for the whole layout, so its 82 points cost the panes nothing but the margin.
- The rail no longer overflows a short column. Destinations it cannot hold
go behind a menu button at its end; on a folded Duo six fit, against a
desktop's dozen.
AdaptiveShellConfig.railOverflowturns it off,railOverflowBuilderdraws the button in place of the Material one, andrailDestinationExtentoverrides the height the count is based on. That height is otherwise worked out per destination from its label, with the rail theme's styles, so one long label does not cost the others a line. RailDecoration.backgroundColor. The rail column is painted as one piece, including the strip under the system bar, where the canvas used to show through.
Foldables in general #
MasterDetailConfig.alignToFold(defaulttrue),FoldMetricsandPaneFold. While a device is half open the pane boundary goes on the crease, the panes part by its width, and the divider stops being draggable until the device is flat again. Read fromMediaQuery.displayFeatures, which the framework fills on Android and not yet on iOS.AdaptiveShellConfig.foldLocatorandFoldMetrics.windowCentre. An app can say where the crease is on a device whose platform does not — iPhone Duo today — and a fold the platform does report still wins.
Pane arithmetic #
- Fixed: the horizontal safe-area insets are now part of it. They were
ignored, so
fits,paneRatioanddetailMinWidthall counted width that lay under a system inset. On an iPhone Duo's inner display in landscape a detail dragged to its stop was 276 points wide whiledetailMinWidthsaid- The panes still reach the window edge; only the width they may count on changed.
PaneMetricsandPaneUnderlapare exported, withsystemBarInsetandrailColumnWidth, so an app can arrive at the same numbers the shell does.- A pane's
MediaQuerynow carries the part of the inset it actually reaches under, rather than all of it or none: with aPaneDecoration.marginthe card is already held off the edge, and the screen inside it was insetting for the margin a second time.
Example and docs #
example/uses master-detail minimums of 300 and 330, which fit the inner display in portrait and keep the outer display a single stack. The package defaults are unchanged.- The README documents the five iPhone Duo postures and the iOS 27.1 SDK requirement.
Changes to expect on devices that are not foldables #
- A notched phone in landscape loses the trailing inset from its pane
area: the detail bleeds under it, so it was never width the screen could
use.
MasterDetailConfig.fitstherefore sees ~60 points less than before, which can turn two panes into one at the boundary. - A bottom bar no longer implies a single pane. The width still decides, and a phone is far under the package's default minimums — but an app that lowered them now gets the split it asked for where it used to get a stack.
- An Android foldable aligns its split to a reported fold while half open.
MasterDetailConfig.alignToFold: falsekeeps the old behaviour.
other_devices_test.dart pins the first two.
0.9.0 #
First public release. The API is still settling — pre-1.0, so breaking changes come in minor versions.
NavState/BranchStack/NavEntry: immutable navigation state with value equality.AdaptiveRouterDelegate:push,pop,popWithResult,replace,resetTo,resetDetailTo,popToRoot,goBranch, system back handling and a single exit-guard gate across every way out of a screen.- Three layouts from one state: compact (navigation bar, detail over the bar), medium (rail, one pane) and expanded (rail, master-detail panes), with the screens' element tree preserved across every transition.
AdaptiveRouteInformationParserplus an app-suppliedRouteCodecfor deep links and cold start.- Configurable chrome: custom bar and rail builders, a drawer, hidden branches, a chromeless mode and a redirect hook.
- Pane decoration (margins, gap, radius, canvas, borders), a draggable pane
divider through
PaneSplitController, and an immersive mode that slides the rail away.
