adaptive_shell 2.0.0
adaptive_shell: ^2.0.0 copied to clipboard
Adaptive master-detail layout for Flutter — automatically switches between compact and two-pane layouts across mobile, tablet, and desktop.
2.0.0 #
All changes are additive and backward compatible. Existing
AdaptiveDestination(icon: Icons.people, label: '...')code compiles and runs with zero changes.
✨ New Features #
AdaptiveShellTheme — full navigation theming 🎨
A single const-able class that controls every visual aspect of the
navigation chrome. Pass it to AdaptiveShell.theme:
AdaptiveShell(
theme: const AdaptiveShellTheme(
// Rail
railMinExtendedWidth: 180, // tighter default (was Flutter's 256)
railBackgroundColor: Color(0xFFF8F9FA),
railIndicatorColor: Color(0xFFD0BCFF),
railSelectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),
railDecoration: BoxDecoration(
border: Border(right: BorderSide(color: Color(0xFFE0E0E0))),
),
// Nav bar
navBarIndicatorColor: Color(0xFFD0BCFF),
navBarLabelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
navBarHeight: 72,
),
)
Supports copyWith and lerp.
AdaptiveShellController — programmatic rail control 🎛️
Collapse/expand the navigation rail from anywhere without prop-drilling:
final _nav = AdaptiveShellController();
AdaptiveShell(controller: _nav, ...)
// From an AppBar action or FAB:
IconButton(onPressed: _nav.toggleRail, icon: const Icon(Icons.menu))
Methods: collapseRail(), expandRail(), toggleRail().
Property: isRailCollapsed.
Widget icons for AdaptiveDestination — SVG, PNG, custom 🖼️
icon is now optional (IconData?). Use iconWidget, selectedIconWidget,
or iconBuilder for full widget-based icons:
// Classic IconData — unchanged, still works
const AdaptiveDestination(icon: Icons.home, label: 'Home')
// SVG / PNG widget pair
AdaptiveDestination(
iconWidget: SvgPicture.asset('assets/home.svg'),
selectedIconWidget: SvgPicture.asset('assets/home_filled.svg'),
label: 'Home',
)
// Builder — recommended for theme-driven SVG colors
AdaptiveDestination(
iconBuilder: (context, isSelected) => SvgPicture.asset(
'assets/home.svg',
colorFilter: ColorFilter.mode(
isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
BlendMode.srcIn,
),
),
label: 'Home',
)
Custom nav builders 🏗️
Replace the entire NavigationBar or NavigationRail with your own widget:
AdaptiveShell(
navigationBarBuilder: (context, destinations, index, onSelected) =>
MyCustomBottomBar(...),
navigationRailBuilder: (context, destinations, index, onSelected, isExtended) =>
MyCustomSidebar(...),
)
More AdaptiveDestination fields
| Field | Description |
|---|---|
iconSize: double? |
Wraps the resolved icon in a SizedBox for consistent SVG/PNG sizing |
tooltip: String? |
Custom tooltip (defaults to label) |
enabled: bool |
Disable individual destinations (grayed out, non-tappable) |
badgeLabel: String? |
Flexible badge: "" = dot, "NEW", "99+" |
🐛 Bug Fix #
- Navigation rail on desktop mode now defaults to
minExtendedWidth: 160(previously fell back to Flutter's built-in256default, which consumed too much horizontal space on typical laptop windows).
1.1.0 #
✨ New Features #
AdaptiveBuilder 🏗️
A standalone responsive builder widget that works without an AdaptiveShell ancestor — it uses LayoutBuilder and AdaptiveBreakpoints directly:
AdaptiveBuilder(
compact: (context) => MobileWidget(),
medium: (context) => TabletWidget(), // optional, falls back to compact
expanded: (context) => DesktopWidget(), // optional, falls back to medium
breakpoints: AdaptiveBreakpoints.tabletFirst, // customisable
)
Keyboard Shortcuts ⌨️
Map any ShortcutActivator (e.g. SingleActivator, LogicalKeySet) to a navigation destination index. Only active on medium/expanded layouts (tablet / desktop):
AdaptiveShell(
keyboardShortcuts: {
SingleActivator(LogicalKeyboardKey.digit1, control: true): 0,
SingleActivator(LogicalKeyboardKey.digit2, control: true): 1,
},
// ...
)
Collapsible Navigation Rail 📍
A chevron toggle button above the rail destinations lets users collapse the rail to icon-only mode. railCollapseOnMedium auto-collapses when the layout enters medium mode:
AdaptiveShell(
railCollapsible: true, // shows toggle button
railCollapseOnMedium: true, // auto-collapses on tablet breakpoint
// ...
)
Custom Pane Divider ➗
Replace the hardcoded VerticalDivider between the master and detail panes with any widget:
AdaptiveShell(
paneDivider: VerticalDivider(width: 2, color: Colors.blue),
// ...
)
Context Extensions 🔧
Quick access to layout information anywhere in your widget tree:
context.screenType/context.layoutMode— get currentLayoutMode(usable inswitch)context.isCompact,context.isMedium,context.isExpanded— boolean checkscontext.isMobile,context.isTablet,context.isDesktop— semantic aliasescontext.isTwoPane— true when two panes are visiblecontext.adaptiveWidth(base)/context.adaptiveHeight(base)— scale dimensionscontext.adaptivePadding()— responsiveEdgeInsets(16 / 24 / 32)context.adaptiveFontSize(base)/context.adaptiveSpacing(base)— scale text & gapscontext.adaptiveColumns— grid column count: 1 / 2 / 3 (compact / medium / expanded)context.adaptiveValue<T>(compact:, medium:, expanded:)— any typed value per breakpoint
// Boolean checks
if (context.isCompact) { /* mobile */ }
if (context.isTablet) { /* medium or expanded */ }
// Typed per-breakpoint value
final padding = context.adaptiveValue(compact: 8.0, medium: 16.0, expanded: 24.0);
// Grid columns
GridView.count(crossAxisCount: context.adaptiveColumns, ...)
// Layout mode in switch
switch (context.layoutMode) {
case LayoutMode.compact: return MobileNav();
case LayoutMode.medium: return TabletNav();
case LayoutMode.expanded: return DesktopNav();
}
Layout Change Callback
React to layout mode transitions for analytics or state management:
AdaptiveShell(
onLayoutModeChanged: (oldMode, newMode) {
print('Layout: $oldMode → $newMode');
},
// ...
)
Debug Overlay
Visual indicator showing current layout mode and breakpoints during development:
AdaptiveShell(
debugShowLayoutMode: true, // Shows overlay in top-right corner
// ...
)
AutoScale 📏
Proportionally scales your layout to fill any screen size — the same technique that made responsive_framework's AutoScale popular:
AdaptiveShell(
autoScale: true, // render at 360 dp design canvas, scale to screen
scaleFactor: 1.2, // optional: 20% boost on top of auto scale
autoScaleDesignWidth: 390, // optional: override canvas (e.g. iPhone 14 = 390 dp)
// ...
)
Default autoScaleDesignWidth is 360 dp — a typical phone canvas that ensures a compact (single-pane, bottom-nav) layout by default. The debug overlay gains a live ⚖️ Scale ×N.NN line when autoScale is enabled.
State Persistence 💾
Preserves scroll positions and widget state when the device rotates, the window resizes, or the layout mode changes:
AdaptiveShell(
persistState: true,
stateKey: 'my_shell', // optional namespace (default: 'adaptive_shell')
// ...
)
Uses stable GlobalKeys on child1/child2 so element subtrees survive compact ↔ wide transitions, plus a keyed PageStorage bucket for scroll restoration.
Animated Transitions ✨
Fine-tune detail-pane animations:
AdaptiveShell(
transitionCurve: Curves.easeInOutCubic, // custom curve (default: easeInOut)
enableHeroAnimations: true, // slide + fade instead of cross-fade
// ...
)
🔗 All new params available on AdaptiveMasterDetail #
autoScale, scaleFactor, autoScaleDesignWidth, persistState, stateKey, transitionCurve, enableHeroAnimations, paneDivider, railCollapsible, railCollapseOnMedium, keyboardShortcuts are all forwarded to the internal AdaptiveShell.
🐛 Bug Fixes & Internal Improvements #
AdaptiveShellrefactored fromStatelessWidgettoStatefulWidget— fully backward-compatible.onLayoutModeChangednow reliably fires across hot-reload and test pumps.- Fixed identical debug-overlay emoji for compact/medium modes.
AdaptiveMasterDetail._computeModerespectsautoScale— navigation decisions (push vs in-place) now correctly match the visually rendered compact layout.- Fixed
NavigationRailoverflow whenrailCollapsible: true— the toggle button (~36 px) consumed height from theColumnthat also holds theNavigationRail, causing aRenderFlex overflowed by N pixels on the bottomassertion on short screens with many destinations. The rail is now wrapped inLayoutBuilder → SingleChildScrollView + ConstrainedBox(minHeight) + IntrinsicHeightso destinations scroll gracefully when they exceed the available height, while still centering correctly on taller screens. The wrapper is applied only when the toggle is present; the default (railCollapsible: false) path is unchanged.
✅ Tests #
- 37 new tests for AutoScale, State Persistence, Animated Transitions, new context extensions, and
AdaptiveMasterDetailpass-through. - 29 new tests for AdaptiveBuilder, keyboard shortcuts, collapsible rail, and custom
paneDivider. - 3 new regression tests for the
railCollapsibleoverflow fix (expanded + medium layouts, and default path). - Total: 139 tests, all passing.
⚠️ Breaking Changes #
- None. All
1.0.xcode compiles and runs unchanged. - New
BuildContextextensions (layoutMode,adaptiveColumns,adaptiveValue, and all v1.1.0 additions) may conflict if you already define identically-named extensions. Resolve with a hide import:import 'package:adaptive_shell/adaptive_shell.dart' hide AdaptiveContextExtensions;
1.0.0 #
- Initial release with two APIs:
AdaptiveShell— flexible wrapper withchild1/child2for full manual control over both panes, navigation, and state.AdaptiveMasterDetail<T>— zero-boilerplate generic widget withitemBuilder/detailBuilder. Handles selection state, navigation (push on mobile, side-pane on tablet), and responsive layout switching automatically.
- Automatic
NavigationBar(compact) /NavigationRail(medium/expanded) switching. AdaptiveShell.of(context)for descendant layout-mode awareness.AdaptiveShell.isTwoPane(context)convenience helper.AdaptiveBreakpointswith Material 3 window size class defaults +tabletFirstpreset.AdaptiveDestinationwith badge support.emptyDetailPlaceholderfor large screens with no selection.masterHeaderandmasterBuilderfor custom master pane layouts.compactDetailScaffoldBuilderfor custom mobile detail screen wrapping.- Animated pane transitions with configurable duration.
- Full support for Android, iOS, web, macOS, Windows, Linux.
