diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 0c907565f1ce..857f82fa02ae 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.1.5 +* Added support for Right-to-left (RTL) directionality. * Fixes stale ignore: prefer_const_constructors. * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md index 612e62c4bc92..e9324afbf1b1 100644 --- a/packages/flutter_adaptive_scaffold/README.md +++ b/packages/flutter_adaptive_scaffold/README.md @@ -137,110 +137,108 @@ displayed and the entrance animation and exit animation. ```dart - // AdaptiveLayout has a number of slots that take SlotLayouts and these - // SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs. - return AdaptiveLayout( - // Primary navigation config has nothing from 0 to 600 dp screen width, - // then an unextended NavigationRail with no labels and just icons then an - // extended NavigationRail with both icons and labels. - primaryNavigation: SlotLayout( - config: { - Breakpoints.medium: SlotLayout.from( - inAnimation: AdaptiveScaffold.leftOutIn, - key: const Key('Primary Navigation Medium'), - builder: (_) => AdaptiveScaffold.standardNavigationRail( - selectedIndex: selectedNavigation, - onDestinationSelected: (int newIndex) { - setState(() { - selectedNavigation = newIndex; - }); - }, - leading: const Icon(Icons.menu), - destinations: destinations - .map((_) => AdaptiveScaffold.toRailDestination(_)) - .toList(), - backgroundColor: navRailTheme.backgroundColor, - selectedIconTheme: navRailTheme.selectedIconTheme, - unselectedIconTheme: navRailTheme.unselectedIconTheme, - selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, - unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, - ), - ), - Breakpoints.large: SlotLayout.from( - key: const Key('Primary Navigation Large'), - inAnimation: AdaptiveScaffold.leftOutIn, - builder: (_) => AdaptiveScaffold.standardNavigationRail( - selectedIndex: selectedNavigation, - onDestinationSelected: (int newIndex) { - setState(() { - selectedNavigation = newIndex; - }); - }, - extended: true, - leading: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: const [ - Text( - 'REPLY', - style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)), - ), - Icon(Icons.menu_open) - ], - ), - destinations: destinations - .map((_) => AdaptiveScaffold.toRailDestination(_)) - .toList(), - trailing: trailingNavRail, - backgroundColor: navRailTheme.backgroundColor, - selectedIconTheme: navRailTheme.selectedIconTheme, - unselectedIconTheme: navRailTheme.unselectedIconTheme, - selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, - unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, - ), - ), - }, +// AdaptiveLayout has a number of slots that take SlotLayouts and these +// SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs. +return AdaptiveLayout( + // Primary navigation config has nothing from 0 to 600 dp screen width, + // then an unextended NavigationRail with no labels and just icons then an + // extended NavigationRail with both icons and labels. + primaryNavigation: SlotLayout( + config: { + Breakpoints.medium: SlotLayout.from( + inAnimation: AdaptiveScaffold.leftOutIn, + key: const Key('Primary Navigation Medium'), + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + leading: const Icon(Icons.menu), + destinations: destinations + .map((_) => AdaptiveScaffold.toRailDestination(_)) + .toList(), + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), ), - // Body switches between a ListView and a GridView from small to medium - // breakpoints and onwards. - body: SlotLayout( - config: { - Breakpoints.small: SlotLayout.from( - key: const Key('Body Small'), - builder: (_) => ListView.builder( - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - ), + Breakpoints.large: SlotLayout.from( + key: const Key('Primary Navigation Large'), + inAnimation: AdaptiveScaffold.leftOutIn, + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + extended: true, + leading: const Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'REPLY', + style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)), + ), + Icon(Icons.menu_open) + ], ), - Breakpoints.mediumAndUp: SlotLayout.from( - key: const Key('Body Medium'), - builder: (_) => - GridView.count(crossAxisCount: 2, children: children), - ) - }, + destinations: destinations + .map((_) => AdaptiveScaffold.toRailDestination(_)) + .toList(), + trailing: trailingNavRail, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), ), - // BottomNavigation is only active in small views defined as under 600 dp - // width. - bottomNavigation: SlotLayout( - config: { - Breakpoints.small: SlotLayout.from( - key: const Key('Bottom Navigation Small'), - inAnimation: AdaptiveScaffold.bottomToTop, - outAnimation: AdaptiveScaffold.topToBottom, - builder: (_) => AdaptiveScaffold.standardBottomNavigationBar( - destinations: destinations, - currentIndex: selectedNavigation, - onDestinationSelected: (int newIndex) { - setState(() { - selectedNavigation = newIndex; - }); - }, - ), - ) - }, + }, + ), + // Body switches between a ListView and a GridView from small to medium + // breakpoints and onwards. + body: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('Body Small'), + builder: (_) => ListView.builder( + itemCount: children.length, + itemBuilder: (BuildContext context, int index) => children[index], + ), ), - ); - } -} + Breakpoints.mediumAndUp: SlotLayout.from( + key: const Key('Body Medium'), + builder: (_) => + GridView.count(crossAxisCount: 2, children: children), + ) + }, + ), + // BottomNavigation is only active in small views defined as under 600 dp + // width. + bottomNavigation: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('Bottom Navigation Small'), + inAnimation: AdaptiveScaffold.bottomToTop, + outAnimation: AdaptiveScaffold.topToBottom, + builder: (_) => AdaptiveScaffold.standardBottomNavigationBar( + destinations: destinations, + currentIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + ), + ) + }, + ), +); ``` Both of the examples shown here produce the same output: diff --git a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart index eb9522041fce..77b5cd78d9fe 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(goderbauer): Remove this ignore when this package requires Flutter 3.8 or later. -// ignore_for_file: prefer_const_constructors - import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; @@ -58,8 +55,8 @@ class _MyHomePageState extends State { children: [ const Divider(color: Colors.black), const SizedBox(height: 10), - Row( - children: const [ + const Row( + children: [ SizedBox(width: 27), Text('Folders', style: TextStyle(fontSize: 16)), ], @@ -74,8 +71,8 @@ class _MyHomePageState extends State { iconSize: 21, ), const SizedBox(width: 21), - Flexible( - child: const Text( + const Flexible( + child: Text( 'Freelance', overflow: TextOverflow.ellipsis, ), @@ -92,8 +89,8 @@ class _MyHomePageState extends State { iconSize: 21, ), const SizedBox(width: 21), - Flexible( - child: const Text( + const Flexible( + child: Text( 'Mortgage', overflow: TextOverflow.ellipsis, ), @@ -198,9 +195,9 @@ class _MyHomePageState extends State { }); }, extended: true, - leading: Row( + leading: const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: const [ + children: [ Text( 'REPLY', style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)), @@ -260,6 +257,6 @@ class _MyHomePageState extends State { }, ), ); - // #enddocregion + // #enddocregion Example } } diff --git a/packages/flutter_adaptive_scaffold/example/lib/main.dart b/packages/flutter_adaptive_scaffold/example/lib/main.dart index 5e97f06859a9..4aac90d8bc93 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/main.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/main.dart @@ -52,6 +52,9 @@ class _MyHomePageState extends State // the navigation elements. ValueNotifier showGridView = ValueNotifier(false); + // Override the application's directionality. + TextDirection directionalityOverride = TextDirection.ltr; + // The index of the selected mail card. int? selected; @@ -118,70 +121,96 @@ class _MyHomePageState extends State @override Widget build(BuildContext context) { - final Widget trailingNavRail = Column( - children: [ - const Divider(color: Colors.white, thickness: 1.5), - const SizedBox(height: 10), - Row(children: [ - const SizedBox(width: 22), - Text('Folders', - style: TextStyle(fontSize: 13, color: Colors.grey[700])) - ]), - const SizedBox(height: 22), - Row( - children: [ - const SizedBox(width: 16), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.folder_copy_outlined), - iconSize: 21, - ), - const SizedBox(width: 21), - const Text('Freelance', overflow: TextOverflow.ellipsis), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - const SizedBox(width: 16), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.folder_copy_outlined), - iconSize: 21, - ), - const SizedBox(width: 21), - const Text('Mortgage', overflow: TextOverflow.ellipsis), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - const SizedBox(width: 16), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.folder_copy_outlined), - iconSize: 21, - ), - const SizedBox(width: 21), - const Flexible( - child: Text('Taxes', overflow: TextOverflow.ellipsis)) - ], - ), - const SizedBox(height: 16), - Row( - children: [ - const SizedBox(width: 16), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.folder_copy_outlined), - iconSize: 21, + final Widget trailingNavRail = Expanded( + child: Column( + children: [ + const Divider(color: Colors.white, thickness: 1.5), + const SizedBox(height: 10), + Row(children: [ + const SizedBox(width: 22), + Text('Folders', + style: TextStyle(fontSize: 13, color: Colors.grey[700])) + ]), + const SizedBox(height: 22), + Row( + children: [ + const SizedBox(width: 16), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.folder_copy_outlined), + iconSize: 21, + ), + const SizedBox(width: 21), + const Text('Freelance', overflow: TextOverflow.ellipsis), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + const SizedBox(width: 16), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.folder_copy_outlined), + iconSize: 21, + ), + const SizedBox(width: 21), + const Text('Mortgage', overflow: TextOverflow.ellipsis), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + const SizedBox(width: 16), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.folder_copy_outlined), + iconSize: 21, + ), + const SizedBox(width: 21), + const Flexible( + child: Text('Taxes', overflow: TextOverflow.ellipsis)) + ], + ), + const SizedBox(height: 16), + Row( + children: [ + const SizedBox(width: 16), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.folder_copy_outlined), + iconSize: 21, + ), + const SizedBox(width: 21), + const Flexible( + child: Text('Receipts', overflow: TextOverflow.ellipsis)) + ], + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: SwitchListTile.adaptive( + title: const Text( + 'Directionality', + style: TextStyle(fontSize: 12), + ), + subtitle: Text( + directionalityOverride == TextDirection.ltr ? 'LTR' : 'RTL', + ), + value: directionalityOverride == TextDirection.ltr, + onChanged: (bool value) { + setState(() { + if (value) { + directionalityOverride = TextDirection.ltr; + } else { + directionalityOverride = TextDirection.rtl; + } + }); + }, + ), ), - const SizedBox(width: 21), - const Flexible( - child: Text('Receipts', overflow: TextOverflow.ellipsis)) - ], - ), - ], + ), + ], + ), ); // These are the destinations used within the AdaptiveScaffold navigation @@ -208,134 +237,137 @@ class _MyHomePageState extends State // Updating the listener value. showGridView.value = Breakpoints.mediumAndUp.isActive(context); - return Scaffold( - backgroundColor: const Color.fromARGB(255, 234, 227, 241), - // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes - // LayoutSlots for its variety of screen slots. - body: AdaptiveLayout( - // Each SlotLayout has a config which maps Breakpoints to - // SlotLayoutConfigs. - primaryNavigation: SlotLayout( - config: { - // The breakpoint used here is from the Breakpoints class but custom - // Breakpoints can be defined by extending the Breakpoint class - Breakpoints.medium: SlotLayout.from( - // Every SlotLayoutConfig takes a key and a builder. The builder - // is to save memory that would be spent on initialization. - key: const Key('primaryNavigation'), - builder: (_) { - return AdaptiveScaffold.standardNavigationRail( - // Usually it would be easier to use a builder from - // AdaptiveScaffold for these types of navigation but this - // navigation has custom staggered item animations. + return Directionality( + textDirection: directionalityOverride, + child: Scaffold( + backgroundColor: const Color.fromARGB(255, 234, 227, 241), + // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes + // LayoutSlots for its variety of screen slots. + body: AdaptiveLayout( + // Each SlotLayout has a config which maps Breakpoints to + // SlotLayoutConfigs. + primaryNavigation: SlotLayout( + config: { + // The breakpoint used here is from the Breakpoints class but custom + // Breakpoints can be defined by extending the Breakpoint class + Breakpoints.medium: SlotLayout.from( + // Every SlotLayoutConfig takes a key and a builder. The builder + // is to save memory that would be spent on initialization. + key: const Key('primaryNavigation'), + builder: (_) { + return AdaptiveScaffold.standardNavigationRail( + // Usually it would be easier to use a builder from + // AdaptiveScaffold for these types of navigation but this + // navigation has custom staggered item animations. + onDestinationSelected: (int index) { + setState(() { + _navigationIndex = index; + }); + }, + selectedIndex: _navigationIndex, + leading: ScaleTransition( + scale: _articleIconSlideController, + child: const _MediumComposeIcon(), + ), + backgroundColor: const Color.fromARGB(0, 255, 255, 255), + destinations: [ + slideInNavigationItem( + begin: -1, + controller: _inboxIconSlideController, + icon: Icons.inbox, + label: 'Inbox', + ), + slideInNavigationItem( + begin: -2, + controller: _articleIconSlideController, + icon: Icons.article_outlined, + label: 'Articles', + ), + slideInNavigationItem( + begin: -3, + controller: _chatIconSlideController, + icon: Icons.chat_bubble_outline, + label: 'Chat', + ), + slideInNavigationItem( + begin: -4, + controller: _videoIconSlideController, + icon: Icons.video_call_outlined, + label: 'Video', + ) + ], + ); + }, + ), + Breakpoints.large: SlotLayout.from( + key: const Key('Large primaryNavigation'), + // The AdaptiveScaffold builder here greatly simplifies + // navigational elements. + builder: (_) => AdaptiveScaffold.standardNavigationRail( + leading: const _LargeComposeIcon(), onDestinationSelected: (int index) { setState(() { _navigationIndex = index; }); }, selectedIndex: _navigationIndex, - leading: ScaleTransition( - scale: _articleIconSlideController, - child: const _MediumComposeIcon(), - ), - backgroundColor: const Color.fromARGB(0, 255, 255, 255), - destinations: [ - slideInNavigationItem( - begin: -1, - controller: _inboxIconSlideController, - icon: Icons.inbox, - label: 'Inbox', - ), - slideInNavigationItem( - begin: -2, - controller: _articleIconSlideController, - icon: Icons.article_outlined, - label: 'Articles', - ), - slideInNavigationItem( - begin: -3, - controller: _chatIconSlideController, - icon: Icons.chat_bubble_outline, - label: 'Chat', - ), - slideInNavigationItem( - begin: -4, - controller: _videoIconSlideController, - icon: Icons.video_call_outlined, - label: 'Video', - ) - ], - ); - }, - ), - Breakpoints.large: SlotLayout.from( - key: const Key('Large primaryNavigation'), - // The AdaptiveScaffold builder here greatly simplifies - // navigational elements. - builder: (_) => AdaptiveScaffold.standardNavigationRail( - leading: const _LargeComposeIcon(), - onDestinationSelected: (int index) { - setState(() { - _navigationIndex = index; - }); - }, - selectedIndex: _navigationIndex, - trailing: trailingNavRail, - extended: true, - destinations: destinations.map((_) { - return AdaptiveScaffold.toRailDestination(_); - }).toList(), + trailing: trailingNavRail, + extended: true, + destinations: destinations.map((_) { + return AdaptiveScaffold.toRailDestination(_); + }).toList(), + ), ), - ), - }, - ), - body: SlotLayout( - config: { - Breakpoints.standard: SlotLayout.from( - key: const Key('body'), - // The conditional here is for navigation screens. The first - // screen shows the main screen and every other screen shows - // ExamplePage. - builder: (_) => (_navigationIndex == 0) - ? Padding( - padding: const EdgeInsets.fromLTRB(0, 32, 0, 0), - child: _ItemList( - selected: selected, - items: _allItems, - selectCard: selectCard, + }, + ), + body: SlotLayout( + config: { + Breakpoints.standard: SlotLayout.from( + key: const Key('body'), + // The conditional here is for navigation screens. The first + // screen shows the main screen and every other screen shows + // ExamplePage. + builder: (_) => (_navigationIndex == 0) + ? Padding( + padding: const EdgeInsets.fromLTRB(0, 32, 0, 0), + child: _ItemList( + selected: selected, + items: _allItems, + selectCard: selectCard, + ), + ) + : const _ExamplePage(), + ), + }, + ), + secondaryBody: _navigationIndex == 0 + ? SlotLayout( + config: { + Breakpoints.mediumAndUp: SlotLayout.from( + // This overrides the default behavior of the secondaryBody + // disappearing as it is animating out. + outAnimation: AdaptiveScaffold.stayOnScreen, + key: const Key('Secondary Body'), + builder: (_) => SafeArea( + child: _DetailTile(item: _allItems[selected ?? 0]), ), ) - : const _ExamplePage(), - ), - }, - ), - secondaryBody: _navigationIndex == 0 - ? SlotLayout( - config: { - Breakpoints.mediumAndUp: SlotLayout.from( - // This overrides the default behavior of the secondaryBody - // disappearing as it is animating out. - outAnimation: AdaptiveScaffold.stayOnScreen, - key: const Key('Secondary Body'), - builder: (_) => SafeArea( - child: _DetailTile(item: _allItems[selected ?? 0]), - ), - ) - }, + }, + ) + : null, + bottomNavigation: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('bottomNavigation'), + // You can define inAnimations or outAnimations to override the + // default offset transition. + outAnimation: AdaptiveScaffold.topToBottom, + builder: (_) => AdaptiveScaffold.standardBottomNavigationBar( + destinations: destinations, + ), ) - : null, - bottomNavigation: SlotLayout( - config: { - Breakpoints.small: SlotLayout.from( - key: const Key('bottomNavigation'), - // You can define inAnimations or outAnimations to override the - // default offset transition. - outAnimation: AdaptiveScaffold.topToBottom, - builder: (_) => AdaptiveScaffold.standardBottomNavigationBar( - destinations: destinations, - ), - ) - }, + }, + ), ), ), ); diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart index 028f47e7cad7..f3485ae370bf 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart @@ -488,165 +488,160 @@ class _AdaptiveScaffoldState extends State { final NavigationRailThemeData navRailTheme = Theme.of(context).navigationRailTheme; - return Directionality( - textDirection: TextDirection.ltr, - child: Scaffold( - appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer - ? widget.appBar ?? AppBar() - : null, - drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer - ? Drawer( - child: NavigationRail( - extended: true, - leading: widget.leadingExtendedNavRail, - trailing: widget.trailingNavRail, - selectedIndex: widget.selectedIndex, - destinations: widget.destinations - .map((_) => AdaptiveScaffold.toRailDestination(_)) - .toList(), - onDestinationSelected: widget.onSelectedIndexChange, - ), - ) - : null, - body: AdaptiveLayout( - bodyOrientation: widget.bodyOrientation, - bodyRatio: widget.bodyRatio, - internalAnimations: widget.internalAnimations, - primaryNavigation: SlotLayout( - config: { - widget.mediumBreakpoint: SlotLayout.from( - key: const Key('primaryNavigation'), - builder: (_) => AdaptiveScaffold.standardNavigationRail( - width: widget.navigationRailWidth, - leading: widget.leadingUnextendedNavRail, - trailing: widget.trailingNavRail, - selectedIndex: widget.selectedIndex, - destinations: widget.destinations - .map((_) => AdaptiveScaffold.toRailDestination(_)) - .toList(), - onDestinationSelected: widget.onSelectedIndexChange, - backgroundColor: navRailTheme.backgroundColor, - selectedIconTheme: navRailTheme.selectedIconTheme, - unselectedIconTheme: navRailTheme.unselectedIconTheme, - selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, - unSelectedLabelTextStyle: - navRailTheme.unselectedLabelTextStyle, - ), + return Scaffold( + appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer + ? widget.appBar ?? AppBar() + : null, + drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer + ? Drawer( + child: NavigationRail( + extended: true, + leading: widget.leadingExtendedNavRail, + trailing: widget.trailingNavRail, + selectedIndex: widget.selectedIndex, + destinations: widget.destinations + .map((_) => AdaptiveScaffold.toRailDestination(_)) + .toList(), + onDestinationSelected: widget.onSelectedIndexChange, ), - widget.largeBreakpoint: SlotLayout.from( - key: const Key('primaryNavigation1'), - builder: (_) => AdaptiveScaffold.standardNavigationRail( - width: widget.extendedNavigationRailWidth, - extended: true, - leading: widget.leadingExtendedNavRail, - trailing: widget.trailingNavRail, - selectedIndex: widget.selectedIndex, - destinations: widget.destinations - .map((_) => AdaptiveScaffold.toRailDestination(_)) - .toList(), - onDestinationSelected: widget.onSelectedIndexChange, - backgroundColor: navRailTheme.backgroundColor, - selectedIconTheme: navRailTheme.selectedIconTheme, - unselectedIconTheme: navRailTheme.unselectedIconTheme, - selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, - unSelectedLabelTextStyle: - navRailTheme.unselectedLabelTextStyle, - ), + ) + : null, + body: AdaptiveLayout( + bodyOrientation: widget.bodyOrientation, + bodyRatio: widget.bodyRatio, + internalAnimations: widget.internalAnimations, + primaryNavigation: SlotLayout( + config: { + widget.mediumBreakpoint: SlotLayout.from( + key: const Key('primaryNavigation'), + builder: (_) => AdaptiveScaffold.standardNavigationRail( + width: widget.navigationRailWidth, + leading: widget.leadingUnextendedNavRail, + trailing: widget.trailingNavRail, + selectedIndex: widget.selectedIndex, + destinations: widget.destinations + .map((_) => AdaptiveScaffold.toRailDestination(_)) + .toList(), + onDestinationSelected: widget.onSelectedIndexChange, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, ), - }, - ), - bottomNavigation: - !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer - ? SlotLayout( - config: { - widget.smallBreakpoint: SlotLayout.from( - key: const Key('bottomNavigation'), - builder: (_) => - AdaptiveScaffold.standardBottomNavigationBar( - currentIndex: widget.selectedIndex, - destinations: widget.destinations, - onDestinationSelected: widget.onSelectedIndexChange, - ), - ), - }, - ) - : null, - body: SlotLayout( - config: { - Breakpoints.standard: SlotLayout.from( - key: const Key('body'), - inAnimation: AdaptiveScaffold.fadeIn, - outAnimation: AdaptiveScaffold.fadeOut, - builder: widget.body, - ), - if (widget.smallBody != null) - widget.smallBreakpoint: - (widget.smallBody != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('smallBody'), - inAnimation: AdaptiveScaffold.fadeIn, - outAnimation: AdaptiveScaffold.fadeOut, - builder: widget.smallBody, - ) - : null, - if (widget.body != null) - widget.mediumBreakpoint: - (widget.body != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('body'), - inAnimation: AdaptiveScaffold.fadeIn, - outAnimation: AdaptiveScaffold.fadeOut, - builder: widget.body, - ) - : null, - if (widget.largeBody != null) - widget.largeBreakpoint: - (widget.largeBody != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('largeBody'), - inAnimation: AdaptiveScaffold.fadeIn, - outAnimation: AdaptiveScaffold.fadeOut, - builder: widget.largeBody, - ) - : null, - }, - ), - secondaryBody: SlotLayout( - config: { - Breakpoints.standard: SlotLayout.from( - key: const Key('sBody'), - outAnimation: AdaptiveScaffold.stayOnScreen, - builder: widget.secondaryBody, + ), + widget.largeBreakpoint: SlotLayout.from( + key: const Key('primaryNavigation1'), + builder: (_) => AdaptiveScaffold.standardNavigationRail( + width: widget.extendedNavigationRailWidth, + extended: true, + leading: widget.leadingExtendedNavRail, + trailing: widget.trailingNavRail, + selectedIndex: widget.selectedIndex, + destinations: widget.destinations + .map((_) => AdaptiveScaffold.toRailDestination(_)) + .toList(), + onDestinationSelected: widget.onSelectedIndexChange, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, ), - if (widget.smallSecondaryBody != null) - widget.smallBreakpoint: - (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('smallSBody'), - outAnimation: AdaptiveScaffold.stayOnScreen, - builder: widget.smallSecondaryBody, - ) - : null, - if (widget.secondaryBody != null) - widget.mediumBreakpoint: - (widget.secondaryBody != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('sBody'), - outAnimation: AdaptiveScaffold.stayOnScreen, - builder: widget.secondaryBody, - ) - : null, - if (widget.largeSecondaryBody != null) - widget.largeBreakpoint: - (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder) - ? SlotLayout.from( - key: const Key('largeSBody'), - outAnimation: AdaptiveScaffold.stayOnScreen, - builder: widget.largeSecondaryBody, - ) - : null, - }, - ), + ), + }, + ), + bottomNavigation: + !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer + ? SlotLayout( + config: { + widget.smallBreakpoint: SlotLayout.from( + key: const Key('bottomNavigation'), + builder: (_) => + AdaptiveScaffold.standardBottomNavigationBar( + currentIndex: widget.selectedIndex, + destinations: widget.destinations, + onDestinationSelected: widget.onSelectedIndexChange, + ), + ), + }, + ) + : null, + body: SlotLayout( + config: { + Breakpoints.standard: SlotLayout.from( + key: const Key('body'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.body, + ), + if (widget.smallBody != null) + widget.smallBreakpoint: + (widget.smallBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('smallBody'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.smallBody, + ) + : null, + if (widget.body != null) + widget.mediumBreakpoint: + (widget.body != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('body'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.body, + ) + : null, + if (widget.largeBody != null) + widget.largeBreakpoint: + (widget.largeBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('largeBody'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.largeBody, + ) + : null, + }, + ), + secondaryBody: SlotLayout( + config: { + Breakpoints.standard: SlotLayout.from( + key: const Key('sBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.secondaryBody, + ), + if (widget.smallSecondaryBody != null) + widget.smallBreakpoint: + (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('smallSBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.smallSecondaryBody, + ) + : null, + if (widget.secondaryBody != null) + widget.mediumBreakpoint: + (widget.secondaryBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('sBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.secondaryBody, + ) + : null, + if (widget.largeSecondaryBody != null) + widget.largeBreakpoint: + (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('largeSBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.largeSecondaryBody, + ) + : null, + }, ), ), ); diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index 4e3cc5c57d42..155e08665b41 100644 --- a/packages/flutter_adaptive_scaffold/pubspec.yaml +++ b/packages/flutter_adaptive_scaffold/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_adaptive_scaffold description: Widgets to easily build adaptive layouts, including navigation elements. -version: 0.1.4 +version: 0.1.5 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22 repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart index 90fb25bffdb0..5a84f4476e41 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart @@ -170,9 +170,7 @@ void main() { expect(begin, findsOneWidget); expect(end, findsOneWidget); } - // TODO(gspencergoog): Remove skip when AnimatedSwitcher fix rolls into stable. - // https://github.com/flutter/flutter/pull/107476 - }, skip: true); + }); testWidgets('slot layout can tolerate rapid changes in breakpoints', (WidgetTester tester) async { @@ -191,9 +189,7 @@ void main() { await tester.pumpAndSettle(); expect(begin, findsOneWidget); expect(end, findsNothing); - // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable. - // https://github.com/flutter/flutter/pull/107476 - }, skip: true); + }); // This test reflects the behavior of the internal animations of both the body // and secondary body and also the navigational items. This is reflected in @@ -248,9 +244,7 @@ void main() { expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10)); expect( tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); - // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable. - // https://github.com/flutter/flutter/pull/107476 - }, skip: true); + }); testWidgets('adaptive layout does not animate when animations off', (WidgetTester tester) async { @@ -269,9 +263,7 @@ void main() { expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10)); expect( tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); - // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable. - // https://github.com/flutter/flutter/pull/107476 - }, skip: true); + }); } class TestBreakpoint0 extends Breakpoint { diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart index 325628645522..463dd84a0b3b 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart @@ -543,6 +543,43 @@ void main() { tester.widget(find.byType(NavigationRail)); expect(rail.groupAlignment, equals(groupAlignment)); }); + + testWidgets( + "doesn't override Directionality", + (WidgetTester tester) async { + const List destinations = [ + NavigationDestination( + icon: Icon(Icons.home), + label: 'Home', + ), + NavigationDestination( + icon: Icon(Icons.account_circle), + label: 'Profile', + ), + ]; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Directionality( + textDirection: TextDirection.rtl, + child: AdaptiveScaffold( + destinations: destinations, + body: (BuildContext context) { + return const SizedBox.shrink(); + }, + ), + ), + ), + ), + ); + + final Finder body = find.byKey(const Key('body')); + expect(body, findsOneWidget); + final TextDirection dir = Directionality.of(body.evaluate().first); + expect(dir, TextDirection.rtl); + }, + ); } /// An empty widget that implements [PreferredSizeWidget] to ensure that