diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 85913500819..5d65f72a5ef 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.2.0 + +* Add breakpoints for mediumLarge and extraLarge. +* Add height and orientation based breakpoint checks. +* **BREAKING CHANGES**: + * Removes `WidthPlatformBreakpoint` + * Breakpoints can now be constructed directly with `Breakpoint` + * Checks for `andUp` or `platform` can be done as parameter: `Breakpoint.small(andUp: true, platform: Breakpoint.mobile)` + ## 0.1.12 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md index 3dda0975353..77b0a11c480 100644 --- a/packages/flutter_adaptive_scaffold/README.md +++ b/packages/flutter_adaptive_scaffold/README.md @@ -17,19 +17,19 @@ flutter run --release ## AdaptiveScaffold -AdaptiveScaffold implements the basic visual layout structure for Material +`AdaptiveScaffold` implements the basic visual layout structure for Material Design 3 that adapts to a variety of screens. It provides a preset of layout, including positions and animations, by handling macro changes in navigational elements and bodies based on the current features of the screen, namely screen width and platform. For example, the navigational elements would be a -BottomNavigationBar on a small mobile device and a NavigationRail on larger +`BottomNavigationBar` on a small mobile device and a `NavigationRail` on larger devices. The body is the primary screen that takes up the space left by the navigational elements. The secondaryBody acts as an option to split the space between two panes for purposes such as having a detail view. There is some automatic functionality with foldables to handle the split between panels -properly. AdaptiveScaffold is much simpler to use but is not the best if you +properly. `AdaptiveScaffold` is much simpler to use but is not the best if you would like high customizability. Apps that would like more refined layout and/or -animation should use AdaptiveLayout. +animation should use `AdaptiveLayout`. ### Example Usage @@ -52,10 +52,12 @@ Widget build(BuildContext context) { // An option to override the default transition duration. transitionDuration: Duration(milliseconds: _transitionDuration), // An option to override the default breakpoints used for small, medium, - // and large. - smallBreakpoint: const WidthPlatformBreakpoint(end: 700), - mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000), - largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000), + // mediumLarge, large, and extraLarge. + smallBreakpoint: const Breakpoint(endWidth: 700), + mediumBreakpoint: const Breakpoint(beginWidth: 700, endWidth: 1000), + mediumLargeBreakpoint: const Breakpoint(beginWidth: 1000, endWidth: 1200), + largeBreakpoint: const Breakpoint(beginWidth: 1200, endWidth: 1600), + extraLargeBreakpoint: const Breakpoint(beginWidth: 1600), useDrawer: false, selectedIndex: _selectedTab, onSelectedIndexChange: (int index) { @@ -90,19 +92,33 @@ Widget build(BuildContext context) { label: 'Inbox', ), ], - body: (_) => GridView.count(crossAxisCount: 2, children: children), smallBody: (_) => ListView.builder( itemCount: children.length, itemBuilder: (_, int idx) => children[idx], ), + body: (_) => GridView.count(crossAxisCount: 2, children: children), + mediumLargeBody: (_) => + GridView.count(crossAxisCount: 3, children: children), + largeBody: (_) => GridView.count(crossAxisCount: 4, children: children), + extraLargeBody: (_) => + GridView.count(crossAxisCount: 5, children: children), // Define a default secondaryBody. - secondaryBody: (_) => Container( - color: const Color.fromARGB(255, 234, 158, 192), - ), // Override the default secondaryBody during the smallBreakpoint to be // empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly // overridden. smallSecondaryBody: AdaptiveScaffold.emptyBuilder, + secondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + mediumLargeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + largeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + extraLargeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), ); } ``` @@ -115,16 +131,16 @@ customizability at a cost of more lines of code. ### AdaptiveLayout !["AdaptiveLayout's Assigned Slots Displayed on Screen"](example/demo_files/screenSlots.png) -AdaptiveLayout is the top-level widget class that arranges the layout of the +`AdaptiveLayout` is the top-level widget class that arranges the layout of the slots and their animation, similar to Scaffold. It takes in several LayoutSlots -and returns an appropriate layout based on the diagram above. AdaptiveScaffold -is built upon AdaptiveLayout internally but abstracts some of the complexity +and returns an appropriate layout based on the diagram above. `AdaptiveScaffold` +is built upon `AdaptiveLayout` internally but abstracts some of the complexity with presets based on the Material 3 Design specification. ### SlotLayout -SlotLayout handles the adaptivity or the changes between widgets at certain -Breakpoints. It also holds the logic for animating between breakpoints. It takes +`SlotLayout` handles the adaptivity or the changes between widgets at certain +`Breakpoints`. It also holds the logic for animating between breakpoints. It takes SlotLayoutConfigs mapped to Breakpoints in a config and displays a widget based on that information. @@ -169,6 +185,39 @@ return AdaptiveLayout( unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, ), ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('Primary Navigation MediumLarge'), + inAnimation: AdaptiveScaffold.leftOutIn, + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + extended: true, + leading: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'REPLY', + style: headerColor, + ), + const Icon(Icons.menu_open) + ], + ), + destinations: destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + trailing: trailingNavRail, + 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, @@ -180,14 +229,47 @@ return AdaptiveLayout( }); }, extended: true, - leading: const Row( + leading: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'REPLY', + style: headerColor, + ), + const Icon(Icons.menu_open) + ], + ), + destinations: destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + trailing: trailingNavRail, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), + ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('Primary Navigation ExtraLarge'), + inAnimation: AdaptiveScaffold.leftOutIn, + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + extended: true, + leading: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( 'REPLY', - style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)), + style: headerColor, ), - Icon(Icons.menu_open) + const Icon(Icons.menu_open) ], ), destinations: destinations @@ -215,11 +297,26 @@ return AdaptiveLayout( itemBuilder: (BuildContext context, int index) => children[index], ), ), - Breakpoints.mediumAndUp: SlotLayout.from( + Breakpoints.medium: SlotLayout.from( key: const Key('Body Medium'), builder: (_) => GridView.count(crossAxisCount: 2, children: children), - ) + ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('Body MediumLarge'), + builder: (_) => + GridView.count(crossAxisCount: 3, children: children), + ), + Breakpoints.large: SlotLayout.from( + key: const Key('Body Large'), + builder: (_) => + GridView.count(crossAxisCount: 4, children: children), + ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('Body ExtraLarge'), + builder: (_) => + GridView.count(crossAxisCount: 5, children: children), + ), }, ), // BottomNavigation is only active in small views defined as under 600 dp 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 a99ffb0a233..451c99d8c08 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart @@ -48,6 +48,9 @@ class _MyHomePageState extends State { }); } + final TextStyle headerColor = + const TextStyle(color: Color.fromARGB(255, 255, 201, 197)); + @override Widget build(BuildContext context) { final NavigationRailThemeData navRailTheme = @@ -200,6 +203,39 @@ class _MyHomePageState extends State { unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, ), ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('Primary Navigation MediumLarge'), + inAnimation: AdaptiveScaffold.leftOutIn, + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + extended: true, + leading: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'REPLY', + style: headerColor, + ), + const Icon(Icons.menu_open) + ], + ), + destinations: destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + trailing: trailingNavRail, + 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, @@ -211,14 +247,47 @@ class _MyHomePageState extends State { }); }, extended: true, - leading: const Row( + leading: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( 'REPLY', - style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)), + style: headerColor, ), - Icon(Icons.menu_open) + const Icon(Icons.menu_open) + ], + ), + destinations: destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + trailing: trailingNavRail, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), + ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('Primary Navigation ExtraLarge'), + inAnimation: AdaptiveScaffold.leftOutIn, + builder: (_) => AdaptiveScaffold.standardNavigationRail( + selectedIndex: selectedNavigation, + onDestinationSelected: (int newIndex) { + setState(() { + selectedNavigation = newIndex; + }); + }, + extended: true, + leading: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'REPLY', + style: headerColor, + ), + const Icon(Icons.menu_open) ], ), destinations: destinations @@ -246,11 +315,26 @@ class _MyHomePageState extends State { itemBuilder: (BuildContext context, int index) => children[index], ), ), - Breakpoints.mediumAndUp: SlotLayout.from( + Breakpoints.medium: SlotLayout.from( key: const Key('Body Medium'), builder: (_) => GridView.count(crossAxisCount: 2, children: children), - ) + ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('Body MediumLarge'), + builder: (_) => + GridView.count(crossAxisCount: 3, children: children), + ), + Breakpoints.large: SlotLayout.from( + key: const Key('Body Large'), + builder: (_) => + GridView.count(crossAxisCount: 4, children: children), + ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('Body ExtraLarge'), + builder: (_) => + GridView.count(crossAxisCount: 5, children: children), + ), }, ), // BottomNavigation is only active in small views defined as under 600 dp diff --git a/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart b/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart index ff7e25683b4..600037955a6 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart @@ -85,10 +85,12 @@ class _MyHomePageState extends State { // An option to override the default transition duration. transitionDuration: Duration(milliseconds: _transitionDuration), // An option to override the default breakpoints used for small, medium, - // and large. - smallBreakpoint: const WidthPlatformBreakpoint(end: 700), - mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000), - largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000), + // mediumLarge, large, and extraLarge. + smallBreakpoint: const Breakpoint(endWidth: 700), + mediumBreakpoint: const Breakpoint(beginWidth: 700, endWidth: 1000), + mediumLargeBreakpoint: const Breakpoint(beginWidth: 1000, endWidth: 1200), + largeBreakpoint: const Breakpoint(beginWidth: 1200, endWidth: 1600), + extraLargeBreakpoint: const Breakpoint(beginWidth: 1600), useDrawer: false, selectedIndex: _selectedTab, onSelectedIndexChange: (int index) { @@ -123,19 +125,33 @@ class _MyHomePageState extends State { label: 'Inbox', ), ], - body: (_) => GridView.count(crossAxisCount: 2, children: children), smallBody: (_) => ListView.builder( itemCount: children.length, itemBuilder: (_, int idx) => children[idx], ), + body: (_) => GridView.count(crossAxisCount: 2, children: children), + mediumLargeBody: (_) => + GridView.count(crossAxisCount: 3, children: children), + largeBody: (_) => GridView.count(crossAxisCount: 4, children: children), + extraLargeBody: (_) => + GridView.count(crossAxisCount: 5, children: children), // Define a default secondaryBody. - secondaryBody: (_) => Container( - color: const Color.fromARGB(255, 234, 158, 192), - ), // Override the default secondaryBody during the smallBreakpoint to be // empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly // overridden. smallSecondaryBody: AdaptiveScaffold.emptyBuilder, + secondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + mediumLargeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + largeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + extraLargeSecondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), ); } // #enddocregion Example diff --git a/packages/flutter_adaptive_scaffold/example/lib/main.dart b/packages/flutter_adaptive_scaffold/example/lib/main.dart index 13a6418973d..2afffb773b7 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/main.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/main.dart @@ -299,6 +299,26 @@ class _MyHomePageState extends State ); }, ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('MediumLarge 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((NavigationDestination destination) { + return AdaptiveScaffold.toRailDestination(destination); + }).toList(), + ), + ), Breakpoints.large: SlotLayout.from( key: const Key('Large primaryNavigation'), // The AdaptiveScaffold builder here greatly simplifies @@ -319,6 +339,26 @@ class _MyHomePageState extends State }).toList(), ), ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('ExtraLarge 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((NavigationDestination destination) { + return AdaptiveScaffold.toRailDestination(destination); + }).toList(), + ), + ), }, ), body: SlotLayout( diff --git a/packages/flutter_adaptive_scaffold/example/test/adaptive_layout_demo_test.dart b/packages/flutter_adaptive_scaffold/example/test/adaptive_layout_demo_test.dart index 77d0e0b759e..13ce917738a 100644 --- a/packages/flutter_adaptive_scaffold/example/test/adaptive_layout_demo_test.dart +++ b/packages/flutter_adaptive_scaffold/example/test/adaptive_layout_demo_test.dart @@ -24,7 +24,7 @@ void main() { ); Future updateScreen(double width, WidgetTester tester) async { - await tester.binding.setSurfaceSize(Size(width, 800)); + await tester.binding.setSurfaceSize(Size(width, 2000)); await tester.pumpWidget( MaterialApp( theme: ThemeData.light().copyWith( @@ -35,7 +35,7 @@ void main() { ), ), home: MediaQuery( - data: MediaQueryData(size: Size(width, 800)), + data: MediaQueryData(size: Size(width, 2000)), child: const example.MyHomePage(), ), ), @@ -51,15 +51,62 @@ void main() { expect(find.byKey(const Key('Primary Navigation Medium')), findsNothing); expect(find.byKey(const Key('Bottom Navigation Small')), findsOneWidget); expect(find.byKey(const Key('Body Medium')), findsNothing); + expect(find.byKey(const Key('Primary Navigation MediumLarge')), + findsNothing); expect(find.byKey(const Key('Primary Navigation Large')), findsNothing); + expect( + find.byKey(const Key('Primary Navigation ExtraLarge')), findsNothing); await updateScreen(700, tester); - expect(find.byKey(const Key('Body')), findsNothing); + expect(find.byKey(const Key('Body Small')), findsNothing); expect(find.byKey(const Key('Bottom Navigation Small')), findsNothing); expect(find.byKey(const Key('Body Medium')), findsOneWidget); expect( find.byKey(const Key('Primary Navigation Medium')), findsOneWidget); + expect(find.byKey(const Key('Primary Navigation MediumLarge')), + findsNothing); + expect(find.byKey(const Key('Primary Navigation Large')), findsNothing); + expect( + find.byKey(const Key('Primary Navigation ExtraLarge')), findsNothing); + + await updateScreen(860, tester); + expect(find.byKey(const Key('Body Small')), findsNothing); + expect(find.byKey(const Key('Bottom Navigation Small')), findsNothing); + expect(find.byKey(const Key('Body Medium')), findsNothing); + expect(find.byKey(const Key('Body MediumLarge')), findsOneWidget); + expect(find.byKey(const Key('Primary Navigation Medium')), findsNothing); + expect(find.byKey(const Key('Primary Navigation MediumLarge')), + findsOneWidget); + expect(find.byKey(const Key('Primary Navigation Large')), findsNothing); + expect( + find.byKey(const Key('Primary Navigation ExtraLarge')), findsNothing); + + await updateScreen(1200, tester); + expect(find.byKey(const Key('Body Small')), findsNothing); + expect(find.byKey(const Key('Bottom Navigation Small')), findsNothing); + expect(find.byKey(const Key('Body Medium')), findsNothing); + expect(find.byKey(const Key('Body MediumLarge')), findsNothing); + expect(find.byKey(const Key('Body Large')), findsOneWidget); + expect(find.byKey(const Key('Primary Navigation Medium')), findsNothing); + expect(find.byKey(const Key('Primary Navigation MediumLarge')), + findsNothing); + expect(find.byKey(const Key('Primary Navigation Large')), findsOneWidget); + expect( + find.byKey(const Key('Primary Navigation ExtraLarge')), findsNothing); + + await updateScreen(1600, tester); + expect(find.byKey(const Key('Body Small')), findsNothing); + expect(find.byKey(const Key('Bottom Navigation Small')), findsNothing); + expect(find.byKey(const Key('Body Medium')), findsNothing); + expect(find.byKey(const Key('Body MediumLarge')), findsNothing); + expect(find.byKey(const Key('Body Large')), findsNothing); + expect(find.byKey(const Key('Body ExtraLarge')), findsOneWidget); + expect(find.byKey(const Key('Primary Navigation Medium')), findsNothing); + expect(find.byKey(const Key('Primary Navigation MediumLarge')), + findsNothing); expect(find.byKey(const Key('Primary Navigation Large')), findsNothing); + expect(find.byKey(const Key('Primary Navigation ExtraLarge')), + findsOneWidget); }, ); @@ -105,8 +152,8 @@ void main() { 'adaptive layout displays children in correct places', (WidgetTester tester) async { await updateScreen(400, tester); - expect(tester.getBottomLeft(bottomNavigation), const Offset(0, 800)); - expect(tester.getBottomRight(bottomNavigation), const Offset(400, 800)); + expect(tester.getBottomLeft(bottomNavigation), const Offset(0, 2000)); + expect(tester.getBottomRight(bottomNavigation), const Offset(400, 2000)); expect(tester.getTopRight(body), const Offset(400, 0)); expect(tester.getTopLeft(body), Offset.zero); }, @@ -119,7 +166,7 @@ void main() { await updateScreen(690, tester); expect(tester.getTopLeft(bodyMedium), const Offset(88, 0)); - expect(tester.getBottomRight(bodyMedium), const Offset(690, 800)); + expect(tester.getBottomRight(bodyMedium), const Offset(690, 2000)); }, ); @@ -165,13 +212,55 @@ void main() { }, ); + testWidgets( + 'when view in medium large screen, navigation rail must be visible as per theme data values.', + (WidgetTester tester) async { + final Finder primaryNavigationMediumLarge = find.byKey( + const Key('Primary Navigation MediumLarge'), + ); + await updateScreen(860, tester); + expect(primaryNavigationMediumLarge, findsOneWidget); + + final Finder navigationRailFinder = find.descendant( + of: primaryNavigationMediumLarge, + matching: find.byType(NavigationRail), + ); + expect(navigationRailFinder, findsOneWidget); + + final NavigationRail navigationRailView = tester.firstWidget( + navigationRailFinder, + ); + expect(navigationRailView, isNotNull); + expect( + navigationRailView.backgroundColor, + navigationRailThemeBgColor, + ); + expect( + navigationRailView.selectedIconTheme?.size, + selectedIconThemeData.size, + ); + expect( + navigationRailView.selectedIconTheme?.color, + selectedIconThemeData.color, + ); + expect( + navigationRailView.unselectedIconTheme?.size, + unSelectedIconThemeData.size, + ); + expect( + navigationRailView.unselectedIconTheme?.color, + unSelectedIconThemeData.color, + ); + }, + ); + testWidgets( 'when view in large screen, navigation rail must be visible as per theme data values.', (WidgetTester tester) async { final Finder primaryNavigationLarge = find.byKey( const Key('Primary Navigation Large'), ); - await updateScreen(860, tester); + await updateScreen(1200, tester); expect(primaryNavigationLarge, findsOneWidget); final Finder navigationRailFinder = find.descendant( @@ -206,4 +295,46 @@ void main() { ); }, ); + + testWidgets( + 'when view in extraLarge screen, navigation rail must be visible as per theme data values.', + (WidgetTester tester) async { + final Finder primaryNavigationExtraLarge = find.byKey( + const Key('Primary Navigation ExtraLarge'), + ); + await updateScreen(1600, tester); + expect(primaryNavigationExtraLarge, findsOneWidget); + + final Finder navigationRailFinder = find.descendant( + of: primaryNavigationExtraLarge, + matching: find.byType(NavigationRail), + ); + expect(navigationRailFinder, findsOneWidget); + + final NavigationRail navigationRailView = tester.firstWidget( + navigationRailFinder, + ); + expect(navigationRailView, isNotNull); + expect( + navigationRailView.backgroundColor, + navigationRailThemeBgColor, + ); + expect( + navigationRailView.selectedIconTheme?.size, + selectedIconThemeData.size, + ); + expect( + navigationRailView.selectedIconTheme?.color, + selectedIconThemeData.color, + ); + expect( + navigationRailView.unselectedIconTheme?.size, + unSelectedIconThemeData.size, + ); + expect( + navigationRailView.unselectedIconTheme?.color, + unSelectedIconThemeData.color, + ); + }, + ); } diff --git a/packages/flutter_adaptive_scaffold/example/test/adaptive_scaffold_demo_test.dart b/packages/flutter_adaptive_scaffold/example/test/adaptive_scaffold_demo_test.dart index d08a34c2c96..7631d36e35b 100644 --- a/packages/flutter_adaptive_scaffold/example/test/adaptive_scaffold_demo_test.dart +++ b/packages/flutter_adaptive_scaffold/example/test/adaptive_scaffold_demo_test.dart @@ -10,18 +10,22 @@ import 'package:flutter_test/flutter_test.dart'; void main() { final Finder smallBody = find.byKey(const Key('smallBody')); final Finder body = find.byKey(const Key('body')); + final Finder mediumLargeBody = find.byKey(const Key('mediumLargeBody')); final Finder largeBody = find.byKey(const Key('largeBody')); + final Finder extraLargeBody = find.byKey(const Key('extraLargeBody')); final Finder bnav = find.byKey(const Key('bottomNavigation')); final Finder pnav = find.byKey(const Key('primaryNavigation')); final Finder pnav1 = find.byKey(const Key('primaryNavigation1')); + final Finder pnav2 = find.byKey(const Key('primaryNavigation2')); + final Finder pnav3 = find.byKey(const Key('primaryNavigation3')); Future updateScreen(double width, WidgetTester tester, {int transitionDuration = 1000}) async { - await tester.binding.setSurfaceSize(Size(width, 800)); + await tester.binding.setSurfaceSize(Size(width, 2000)); await tester.pumpWidget( MaterialApp( home: MediaQuery( - data: MediaQueryData(size: Size(width, 800)), + data: MediaQueryData(size: Size(width, 2000)), child: example.MyHomePage( transitionDuration: transitionDuration, )), @@ -29,38 +33,88 @@ void main() { ); } - testWidgets('dislays correct item of config based on screen width', + testWidgets('displays correct item of config based on screen width', (WidgetTester tester) async { + // Small await updateScreen(300, tester); await tester.pumpAndSettle(); expect(smallBody, findsOneWidget); expect(bnav, findsOneWidget); expect(tester.getTopLeft(smallBody), Offset.zero); - expect(tester.getTopLeft(bnav), const Offset(0, 720)); + expect(tester.getTopLeft(bnav), const Offset(0, 1920)); expect(body, findsNothing); + expect(mediumLargeBody, findsNothing); expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); expect(pnav, findsNothing); expect(pnav1, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); + // Medium await updateScreen(800, tester); await tester.pumpAndSettle(); expect(body, findsOneWidget); - expect(tester.getTopLeft(body), const Offset(88, 0)); - expect(body, findsOneWidget); - expect(bnav, findsNothing); - expect(largeBody, findsNothing); expect(pnav, findsOneWidget); + expect(tester.getTopLeft(body), const Offset(88, 0)); expect(tester.getTopLeft(pnav), Offset.zero); - expect(tester.getBottomRight(pnav), const Offset(88, 800)); + expect(smallBody, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(bnav, findsNothing); expect(pnav1, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); + // Medium Large await updateScreen(1100, tester); await tester.pumpAndSettle(); - expect(body, findsOneWidget); - expect(pnav, findsNothing); + expect(mediumLargeBody, findsOneWidget); expect(pnav1, findsOneWidget); + expect(tester.getTopLeft(mediumLargeBody), const Offset(208, 0)); expect(tester.getTopLeft(pnav1), Offset.zero); - expect(tester.getBottomRight(pnav1), const Offset(208, 800)); + expect(smallBody, findsNothing); + expect(body, findsNothing); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(bnav, findsNothing); + expect(pnav, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); + + // Large + await updateScreen(1400, tester); + await tester.pumpAndSettle(); + expect(largeBody, findsOneWidget); + expect(mediumLargeBody, findsNothing); + expect(pnav2, findsOneWidget); + expect(tester.getTopLeft(largeBody), const Offset(208, 0)); + expect(tester.getTopLeft(pnav2), Offset.zero); + expect(smallBody, findsNothing); + expect(body, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(bnav, findsNothing); + expect(pnav, findsNothing); + expect(pnav1, findsNothing); + expect(pnav3, findsNothing); + + // Extra Large + await updateScreen(1700, tester); + await tester.pumpAndSettle(); + expect(extraLargeBody, findsOneWidget); + expect(pnav3, findsOneWidget); + expect(tester.getTopLeft(extraLargeBody), const Offset(208, 0)); + expect(tester.getTopLeft(pnav3), Offset.zero); + expect(smallBody, findsNothing); + expect(body, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsNothing); + expect(bnav, findsNothing); + expect(pnav, findsNothing); + expect(pnav1, findsNothing); + expect(pnav2, findsNothing); }); testWidgets('adaptive scaffold animations work correctly', @@ -76,30 +130,30 @@ void main() { expect(tester.getTopLeft(b), const Offset(17.6, 0)); expect(tester.getBottomRight(b), - offsetMoreOrLessEquals(const Offset(778.2, 736), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(778.2, 1936), epsilon: 1.0)); expect(tester.getTopLeft(sBody), offsetMoreOrLessEquals(const Offset(778.2, 0), epsilon: 1.0)); expect(tester.getBottomRight(sBody), - offsetMoreOrLessEquals(const Offset(1178.2, 736), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(1178.2, 1936), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 600)); expect(tester.getTopLeft(b), const Offset(70.4, 0)); expect(tester.getBottomRight(b), - offsetMoreOrLessEquals(const Offset(416.0, 784), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(416.0, 1984), epsilon: 1.0)); expect(tester.getTopLeft(sBody), offsetMoreOrLessEquals(const Offset(416, 0), epsilon: 1.0)); expect(tester.getBottomRight(sBody), - offsetMoreOrLessEquals(const Offset(816, 784), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(816, 1984), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(b), const Offset(88, 0)); - expect(tester.getBottomRight(b), const Offset(400, 800)); + expect(tester.getBottomRight(b), const Offset(400, 2000)); expect(tester.getTopLeft(sBody), const Offset(400, 0)); - expect(tester.getBottomRight(sBody), const Offset(800, 800)); + expect(tester.getBottomRight(sBody), const Offset(800, 2000)); }); testWidgets('animation plays correctly in declared duration', @@ -114,8 +168,8 @@ void main() { await tester.pump(const Duration(milliseconds: 500)); expect(tester.getTopLeft(b), const Offset(88, 0)); - expect(tester.getBottomRight(b), const Offset(400, 800)); + expect(tester.getBottomRight(b), const Offset(400, 2000)); expect(tester.getTopLeft(sBody), const Offset(400, 0)); - expect(tester.getBottomRight(sBody), const Offset(800, 800)); + expect(tester.getBottomRight(sBody), const Offset(800, 2000)); }); } diff --git a/packages/flutter_adaptive_scaffold/example/test/main_test.dart b/packages/flutter_adaptive_scaffold/example/test/main_test.dart index 13e703105b6..e7b11ec00fb 100644 --- a/packages/flutter_adaptive_scaffold/example/test/main_test.dart +++ b/packages/flutter_adaptive_scaffold/example/test/main_test.dart @@ -8,18 +8,30 @@ import 'package:flutter_adaptive_scaffold_example/adaptive_scaffold_demo.dart' import 'package:flutter_test/flutter_test.dart'; void main() { + final Finder smallBody = find.byKey(const Key('smallBody')); final Finder body = find.byKey(const Key('body')); + final Finder mediumLargeBody = find.byKey(const Key('mediumLargeBody')); + final Finder largeBody = find.byKey(const Key('largeBody')); + final Finder extraLargeBody = find.byKey(const Key('extraLargeBody')); + + final Finder smallSBody = find.byKey(const Key('smallSBody')); final Finder sBody = find.byKey(const Key('sBody')); + final Finder mediumLargeSBody = find.byKey(const Key('mediumLargeSBody')); + final Finder largeSBody = find.byKey(const Key('largeSBody')); + final Finder extraLargeSBody = find.byKey(const Key('extraLargeSBody')); + final Finder bnav = find.byKey(const Key('bottomNavigation')); final Finder pnav = find.byKey(const Key('primaryNavigation')); final Finder pnav1 = find.byKey(const Key('primaryNavigation1')); + final Finder pnav2 = find.byKey(const Key('primaryNavigation2')); + final Finder pnav3 = find.byKey(const Key('primaryNavigation3')); Future updateScreen(double width, WidgetTester tester) async { - await tester.binding.setSurfaceSize(Size(width, 800)); + await tester.binding.setSurfaceSize(Size(width, 2000)); await tester.pumpWidget( MaterialApp( home: MediaQuery( - data: MediaQueryData(size: Size(width, 800)), + data: MediaQueryData(size: Size(width, 2000)), child: const example.MyHomePage()), ), ); @@ -29,23 +41,93 @@ void main() { testWidgets('dislays correct item of config based on screen width', (WidgetTester tester) async { await updateScreen(300, tester); + expect(smallBody, findsOneWidget); expect(sBody, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(smallSBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeSBody, findsNothing); + expect(largeSBody, findsNothing); + expect(extraLargeSBody, findsNothing); expect(bnav, findsOneWidget); expect(body, findsNothing); expect(pnav, findsNothing); expect(pnav1, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); await updateScreen(800, tester); - expect(body, findsOneWidget); - expect(body, findsOneWidget); - expect(bnav, findsNothing); + expect(smallBody, findsNothing); + expect(sBody, findsOneWidget); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(smallSBody, findsNothing); expect(sBody, findsOneWidget); + expect(mediumLargeSBody, findsNothing); + expect(largeSBody, findsNothing); + expect(extraLargeSBody, findsNothing); + expect(bnav, findsNothing); + expect(body, findsOneWidget); expect(pnav, findsOneWidget); expect(pnav1, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); await updateScreen(1100, tester); - expect(body, findsOneWidget); + expect(smallBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeBody, findsOneWidget); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsNothing); + expect(smallSBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeSBody, findsOneWidget); + expect(largeSBody, findsNothing); + expect(extraLargeSBody, findsNothing); + expect(bnav, findsNothing); + expect(body, findsNothing); expect(pnav, findsNothing); expect(pnav1, findsOneWidget); + expect(pnav2, findsNothing); + expect(pnav3, findsNothing); + + await updateScreen(1400, tester); + expect(smallBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsOneWidget); + expect(extraLargeBody, findsNothing); + expect(smallSBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeSBody, findsNothing); + expect(largeSBody, findsOneWidget); + expect(extraLargeSBody, findsNothing); + expect(bnav, findsNothing); + expect(body, findsNothing); + expect(pnav, findsNothing); + expect(pnav1, findsNothing); + expect(pnav2, findsOneWidget); + expect(pnav3, findsNothing); + + await updateScreen(1800, tester); + expect(smallBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsNothing); + expect(extraLargeBody, findsOneWidget); + expect(smallSBody, findsNothing); + expect(sBody, findsNothing); + expect(mediumLargeSBody, findsNothing); + expect(largeSBody, findsNothing); + expect(extraLargeSBody, findsOneWidget); + expect(bnav, findsNothing); + expect(body, findsNothing); + expect(pnav, findsNothing); + expect(pnav1, findsNothing); + expect(pnav2, findsNothing); + expect(pnav3, findsOneWidget); }); } diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_layout.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_layout.dart index 9f6cc78a96d..f32a8e759bb 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_layout.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_layout.dart @@ -58,8 +58,8 @@ enum _SlotIds { /// key: const Key('Primary Navigation Medium'), /// builder: (_) => AdaptiveScaffold.toNavigationRail(destinations: destinations), /// ), -/// Breakpoints.large: SlotLayout.from( -/// key: const Key('Primary Navigation Large'), +/// Breakpoints.mediumLarge: SlotLayout.from( +/// key: const Key('Primary Navigation MediumLarge'), /// inAnimation: leftOutIn, /// builder: (_) => AdaptiveScaffold.toNavigationRail(extended: true, 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 638dcf99234..fc8db67bb04 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart @@ -92,14 +92,20 @@ class AdaptiveScaffold extends StatefulWidget { this.trailingNavRail, this.smallBody, this.body, + this.mediumLargeBody, this.largeBody, + this.extraLargeBody, this.smallSecondaryBody, this.secondaryBody, + this.mediumLargeSecondaryBody, this.largeSecondaryBody, + this.extraLargeSecondaryBody, this.bodyRatio, this.smallBreakpoint = Breakpoints.small, this.mediumBreakpoint = Breakpoints.medium, + this.mediumLargeBreakpoint = Breakpoints.mediumLarge, this.largeBreakpoint = Breakpoints.large, + this.extraLargeBreakpoint = Breakpoints.extraLarge, this.drawerBreakpoint = Breakpoints.smallDesktop, this.internalAnimations = true, this.transitionDuration = const Duration(seconds: 1), @@ -148,19 +154,33 @@ class AdaptiveScaffold extends StatefulWidget { /// empty. final WidgetBuilder? smallBody; - /// Widget to be displayed in the body slot at the middle breakpoint. + /// Widget to be displayed in the body slot at the medium breakpoint. /// /// The default displayed body. final WidgetBuilder? body; - /// Widget to be displayed in the body slot at the largest breakpoint. + /// Widget to be displayed in the body slot at the mediumLarge breakpoint. + /// + /// If nothing is entered for this property, then the default [body] is + /// displayed in the slot. If null is entered for this slot, the slot stays + /// empty. + final WidgetBuilder? mediumLargeBody; + + /// Widget to be displayed in the body slot at the large breakpoint. /// /// If nothing is entered for this property, then the default [body] is /// displayed in the slot. If null is entered for this slot, the slot stays /// empty. final WidgetBuilder? largeBody; - /// Widget to be displayed in the secondaryBody slot at the smallest + /// Widget to be displayed in the body slot at the extraLarge breakpoint. + /// + /// If nothing is entered for this property, then the default [body] is + /// displayed in the slot. If null is entered for this slot, the slot stays + /// empty. + final WidgetBuilder? extraLargeBody; + + /// Widget to be displayed in the secondaryBody slot at the compact /// breakpoint. /// /// If nothing is entered for this property, then the default [secondaryBody] @@ -168,12 +188,20 @@ class AdaptiveScaffold extends StatefulWidget { /// empty. final WidgetBuilder? smallSecondaryBody; - /// Widget to be displayed in the secondaryBody slot at the middle breakpoint. + /// Widget to be displayed in the secondaryBody slot at the medium breakpoint. /// /// The default displayed secondaryBody. final WidgetBuilder? secondaryBody; - /// Widget to be displayed in the secondaryBody slot at the largest + /// Widget to be displayed in the secondaryBody slot at the mediumLarge + /// breakpoint. + /// + /// If nothing is entered for this property, then the default [secondaryBody] + /// is displayed in the slot. If null is entered for this slot, the slot stays + /// empty. + final WidgetBuilder? mediumLargeSecondaryBody; + + /// Widget to be displayed in the secondaryBody slot at the large /// breakpoint. /// /// If nothing is entered for this property, then the default [secondaryBody] @@ -181,6 +209,14 @@ class AdaptiveScaffold extends StatefulWidget { /// empty. final WidgetBuilder? largeSecondaryBody; + /// Widget to be displayed in the secondaryBody slot at the extraLarge + /// breakpoint. + /// + /// If nothing is entered for this property, then the default [secondaryBody] + /// is displayed in the slot. If null is entered for this slot, the slot stays + /// empty. + final WidgetBuilder? extraLargeSecondaryBody; + /// Defines the fractional ratio of body to the secondaryBody. /// /// For example 0.3 would mean body takes up 30% of the available space and @@ -190,7 +226,7 @@ class AdaptiveScaffold extends StatefulWidget { /// the center of the screen. final double? bodyRatio; - /// The breakpoint defined for the small size, associated with mobile-like + /// The breakpoint defined for the compact size, associated with mobile-like /// features. /// /// Defaults to [Breakpoints.small]. @@ -199,15 +235,27 @@ class AdaptiveScaffold extends StatefulWidget { /// The breakpoint defined for the medium size, associated with tablet-like /// features. /// - /// Defaults to [Breakpoints.mediumBreakpoint]. + /// Defaults to [Breakpoints.medium]. final Breakpoint mediumBreakpoint; + /// The breakpoint defined for the mediumLarge size, associated with desktop-like + /// features. + /// + /// Defaults to [Breakpoints.mediumLarge]. + final Breakpoint mediumLargeBreakpoint; + /// The breakpoint defined for the large size, associated with desktop-like /// features. /// - /// Defaults to [Breakpoints.largeBreakpoint]. + /// Defaults to [Breakpoints.large]. final Breakpoint largeBreakpoint; + /// The breakpoint defined for the extraLarge size, associated with ultra-wide + /// features. + /// + /// Defaults to [Breakpoints.extraLarge]. + final Breakpoint extraLargeBreakpoint; + /// Whether or not the developer wants the smooth entering slide transition on /// secondaryBody. /// @@ -385,7 +433,9 @@ class AdaptiveScaffold extends StatefulWidget { List breakpoints = const [ Breakpoints.small, Breakpoints.medium, + Breakpoints.mediumLarge, Breakpoints.large, + Breakpoints.extraLarge, ], double margin = 8, int itemColumns = 1, @@ -408,7 +458,7 @@ class AdaptiveScaffold extends StatefulWidget { if (thisMargin < kMaterialMediumMinMargin) { thisMargin = kMaterialMediumMinMargin; } - } else if (currentBreakpoint == Breakpoints.large) { + } else if (currentBreakpoint == Breakpoints.mediumLarge) { if (thisMargin < kMaterialExpandedMinMargin) { thisMargin = kMaterialExpandedMinMargin; } @@ -588,7 +638,7 @@ class _AdaptiveScaffoldState extends State { groupAlignment: widget.groupAlignment, ), ), - widget.largeBreakpoint: SlotLayout.from( + widget.mediumLargeBreakpoint: SlotLayout.from( key: const Key('primaryNavigation1'), builder: (_) => AdaptiveScaffold.standardNavigationRail( width: widget.extendedNavigationRailWidth, @@ -607,6 +657,46 @@ class _AdaptiveScaffoldState extends State { groupAlignment: widget.groupAlignment, ), ), + widget.largeBreakpoint: SlotLayout.from( + key: const Key('primaryNavigation2'), + builder: (_) => AdaptiveScaffold.standardNavigationRail( + width: widget.extendedNavigationRailWidth, + extended: true, + leading: widget.leadingExtendedNavRail, + trailing: widget.trailingNavRail, + selectedIndex: widget.selectedIndex, + destinations: widget.destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + onDestinationSelected: widget.onSelectedIndexChange, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), + ), + widget.extraLargeBreakpoint: SlotLayout.from( + key: const Key('primaryNavigation3'), + builder: (_) => AdaptiveScaffold.standardNavigationRail( + width: widget.extendedNavigationRailWidth, + extended: true, + leading: widget.leadingExtendedNavRail, + trailing: widget.trailingNavRail, + selectedIndex: widget.selectedIndex, + destinations: widget.destinations + .map((NavigationDestination destination) => + AdaptiveScaffold.toRailDestination(destination)) + .toList(), + onDestinationSelected: widget.onSelectedIndexChange, + backgroundColor: navRailTheme.backgroundColor, + selectedIconTheme: navRailTheme.selectedIconTheme, + unselectedIconTheme: navRailTheme.unselectedIconTheme, + selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle, + unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle, + ), + ), }, ), bottomNavigation: @@ -653,6 +743,16 @@ class _AdaptiveScaffoldState extends State { builder: widget.body, ) : null, + if (widget.mediumLargeBody != null) + widget.mediumLargeBreakpoint: + (widget.mediumLargeBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('mediumLargeBody'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.mediumLargeBody, + ) + : null, if (widget.largeBody != null) widget.largeBreakpoint: (widget.largeBody != AdaptiveScaffold.emptyBuilder) @@ -663,6 +763,16 @@ class _AdaptiveScaffoldState extends State { builder: widget.largeBody, ) : null, + if (widget.extraLargeBody != null) + widget.extraLargeBreakpoint: + (widget.extraLargeBody != AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('extraLargeBody'), + inAnimation: AdaptiveScaffold.fadeIn, + outAnimation: AdaptiveScaffold.fadeOut, + builder: widget.extraLargeBody, + ) + : null, }, ), secondaryBody: SlotLayout( @@ -690,6 +800,15 @@ class _AdaptiveScaffoldState extends State { builder: widget.secondaryBody, ) : null, + if (widget.mediumLargeSecondaryBody != null) + widget.mediumLargeBreakpoint: (widget.mediumLargeSecondaryBody != + AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('mediumLargeSBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.mediumLargeSecondaryBody, + ) + : null, if (widget.largeSecondaryBody != null) widget.largeBreakpoint: (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder) @@ -699,6 +818,15 @@ class _AdaptiveScaffoldState extends State { builder: widget.largeSecondaryBody, ) : null, + if (widget.extraLargeSecondaryBody != null) + widget.extraLargeBreakpoint: (widget.extraLargeSecondaryBody != + AdaptiveScaffold.emptyBuilder) + ? SlotLayout.from( + key: const Key('extraLargeSBody'), + outAnimation: AdaptiveScaffold.stayOnScreen, + builder: widget.extraLargeSecondaryBody, + ) + : null, }, ), ), diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 5a7bcfe579b..aa8e744b344 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -4,18 +4,6 @@ import 'package:flutter/material.dart'; -const Set _desktop = { - TargetPlatform.linux, - TargetPlatform.macOS, - TargetPlatform.windows -}; - -const Set _mobile = { - TargetPlatform.android, - TargetPlatform.fuchsia, - TargetPlatform.iOS, -}; - /// A group of standard breakpoints built according to the material /// specifications for screen width size. /// @@ -27,83 +15,78 @@ class Breakpoints { /// case that no other breakpoint is active. /// /// It is active from a width of -1 dp to infinity. - static const Breakpoint standard = WidthPlatformBreakpoint(begin: -1); + static const Breakpoint standard = Breakpoint(beginWidth: -1); /// A window whose width is less than 600 dp and greater than 0 dp. - static const Breakpoint small = WidthPlatformBreakpoint(begin: 0, end: 600); + static const Breakpoint small = Breakpoint.small(); /// A window whose width is greater than 0 dp. - static const Breakpoint smallAndUp = WidthPlatformBreakpoint(begin: 0); + static const Breakpoint smallAndUp = Breakpoint.small(andUp: true); /// A desktop screen whose width is less than 600 dp and greater than 0 dp. static const Breakpoint smallDesktop = - WidthPlatformBreakpoint(begin: 0, end: 600, platform: _desktop); + Breakpoint.small(platform: Breakpoint.desktop); /// A mobile screen whose width is less than 600 dp and greater than 0 dp. static const Breakpoint smallMobile = - WidthPlatformBreakpoint(begin: 0, end: 600, platform: _mobile); + Breakpoint.small(platform: Breakpoint.mobile); /// A window whose width is between 600 dp and 840 dp. - static const Breakpoint medium = - WidthPlatformBreakpoint(begin: 600, end: 840); + static const Breakpoint medium = Breakpoint.medium(); /// A window whose width is greater than 600 dp. - static const Breakpoint mediumAndUp = WidthPlatformBreakpoint(begin: 600); + static const Breakpoint mediumAndUp = Breakpoint.medium(andUp: true); /// A desktop window whose width is between 600 dp and 840 dp. static const Breakpoint mediumDesktop = - WidthPlatformBreakpoint(begin: 600, end: 840, platform: _desktop); + Breakpoint.medium(platform: Breakpoint.desktop); /// A mobile window whose width is between 600 dp and 840 dp. static const Breakpoint mediumMobile = - WidthPlatformBreakpoint(begin: 600, end: 840, platform: _mobile); + Breakpoint.medium(platform: Breakpoint.mobile); + + /// A window whose width is between 840 dp and 1200 dp. + static const Breakpoint mediumLarge = Breakpoint.mediumLarge(); /// A window whose width is greater than 840 dp. - static const Breakpoint large = WidthPlatformBreakpoint(begin: 840); + static const Breakpoint mediumLargeAndUp = + Breakpoint.mediumLarge(andUp: true); - /// A desktop window whose width is greater than 840 dp. - static const Breakpoint largeDesktop = - WidthPlatformBreakpoint(begin: 840, platform: _desktop); + /// A desktop window whose width is between 840 dp and 1200 dp. + static const Breakpoint mediumLargeDesktop = + Breakpoint.mediumLarge(platform: Breakpoint.desktop); - /// A mobile window whose width is greater than 840 dp. - static const Breakpoint largeMobile = - WidthPlatformBreakpoint(begin: 840, platform: _mobile); -} + /// A mobile window whose width is between 840 dp and 1200 dp. + static const Breakpoint mediumLargeMobile = + Breakpoint.mediumLarge(platform: Breakpoint.mobile); -/// A class that can be used to quickly generate [Breakpoint]s that depend on -/// the screen width and the platform. -class WidthPlatformBreakpoint extends Breakpoint { - /// Returns a const [Breakpoint] with the given constraints. - const WidthPlatformBreakpoint({this.begin, this.end, this.platform}); + /// A window whose width is between 1200 dp and 1600 dp. + static const Breakpoint large = Breakpoint.large(); - /// The beginning width dp value. If left null then the [Breakpoint] will have - /// no lower bound. - final double? begin; + /// A window whose width is greater than 1200 dp. + static const Breakpoint largeAndUp = Breakpoint.large(andUp: true); - /// The end width dp value. If left null then the [Breakpoint] will have no - /// upper bound. - final double? end; + /// A desktop window whose width is between 1200 dp and 1600 dp. + static const Breakpoint largeDesktop = + Breakpoint.large(platform: Breakpoint.desktop); - /// A Set of [TargetPlatform]s that the [Breakpoint] will be active on. If - /// left null then it will be active on all platforms. - final Set? platform; + /// A mobile window whose width is between 1200 dp and 1600 dp. + static const Breakpoint largeMobile = + Breakpoint.large(platform: Breakpoint.mobile); - @override - bool isActive(BuildContext context) { - final TargetPlatform host = Theme.of(context).platform; - final bool isRightPlatform = platform?.contains(host) ?? true; + /// A window whose width is greater than 1600 dp. + static const Breakpoint extraLarge = Breakpoint.extraLarge(); - // Null boundaries are unbounded, assign the max/min of their associated - // direction on a number line. - final double width = MediaQuery.sizeOf(context).width; - final double lowerBound = begin ?? double.negativeInfinity; - final double upperBound = end ?? double.infinity; + /// A desktop window whose width is greater than 1600 dp. + static const Breakpoint extraLargeDesktop = + Breakpoint.extraLarge(platform: Breakpoint.desktop); - return width >= lowerBound && width < upperBound && isRightPlatform; - } + /// A mobile window whose width is greater than 1600 dp. + static const Breakpoint extraLargeMobile = + Breakpoint.extraLarge(platform: Breakpoint.mobile); } -/// An interface to define the conditions that distinguish between types of +/// A class to define the conditions that distinguish between types of /// screens. /// /// Adaptive apps usually display differently depending on the screen type: a @@ -121,11 +104,115 @@ class WidthPlatformBreakpoint extends Breakpoint { /// /// * [SlotLayout.config], which uses breakpoints to dictate the layout of the /// screen. -abstract class Breakpoint { - /// Returns a const [Breakpoint]. - const Breakpoint(); +class Breakpoint { + /// Returns a const [Breakpoint] with the given constraints. + const Breakpoint({ + this.beginWidth, + this.endWidth, + this.beginHeight, + this.endHeight, + this.platform, + this.andUp = false, + }); + + /// Returns a [Breakpoint] with the given constraints for a small screen. + const Breakpoint.small({this.andUp = false, this.platform}) + : beginWidth = 0, + endWidth = 600, + beginHeight = null, + endHeight = 480; + + /// Returns a [Breakpoint] with the given constraints for a medium screen. + const Breakpoint.medium({this.andUp = false, this.platform}) + : beginWidth = 600, + endWidth = 840, + beginHeight = 480, + endHeight = 900; + + /// Returns a [Breakpoint] with the given constraints for a mediumLarge screen. + const Breakpoint.mediumLarge({this.andUp = false, this.platform}) + : beginWidth = 840, + endWidth = 1200, + beginHeight = 900, + endHeight = null; + + /// Returns a [Breakpoint] with the given constraints for a large screen. + const Breakpoint.large({this.andUp = false, this.platform}) + : beginWidth = 1200, + endWidth = 1600, + beginHeight = 900, + endHeight = null; + + /// Returns a [Breakpoint] with the given constraints for an extraLarge screen. + const Breakpoint.extraLarge({this.andUp = false, this.platform}) + : beginWidth = 1600, + endWidth = null, + beginHeight = 900, + endHeight = null; + + /// A set of [TargetPlatform]s that the [Breakpoint] will be active on desktop. + static const Set desktop = { + TargetPlatform.linux, + TargetPlatform.macOS, + TargetPlatform.windows + }; + + /// A set of [TargetPlatform]s that the [Breakpoint] will be active on mobile. + static const Set mobile = { + TargetPlatform.android, + TargetPlatform.fuchsia, + TargetPlatform.iOS, + }; + + /// When set to true, it will include any size above the set width. + final bool andUp; + + /// The beginning width dp value. If left null then the [Breakpoint] will have + /// no lower bound. + final double? beginWidth; + + /// The end width dp value. If left null then the [Breakpoint] will have no + /// upper bound. + final double? endWidth; + + /// The beginning height dp value. If left null then the [Breakpoint] will have + /// no lower bound. + final double? beginHeight; + + /// The end height dp value. If left null then the [Breakpoint] will have no + /// upper bound. + final double? endHeight; + + /// A Set of [TargetPlatform]s that the [Breakpoint] will be active on. If + /// left null then it will be active on all platforms. + final Set? platform; /// A method that returns true based on conditions related to the context of - /// the screen such as MediaQuery.sizeOf(context).width. - bool isActive(BuildContext context); + /// the screen such as MediaQuery.sizeOf(context).width, MediaQuery.sizeOf(context).height + /// and MediaQuery.orientationOf(context). + bool isActive(BuildContext context) { + final TargetPlatform host = Theme.of(context).platform; + final bool isRightPlatform = platform?.contains(host) ?? true; + + final double width = MediaQuery.sizeOf(context).width; + final double height = MediaQuery.sizeOf(context).height; + final Orientation orientation = MediaQuery.orientationOf(context); + + final double lowerBoundWidth = beginWidth ?? double.negativeInfinity; + final double upperBoundWidth = endWidth ?? double.infinity; + + final double lowerBoundHeight = beginHeight ?? double.negativeInfinity; + final double upperBoundHeight = endHeight ?? double.infinity; + + final bool isWidthActive = andUp + ? width >= lowerBoundWidth + : width >= lowerBoundWidth && width < upperBoundWidth; + + final bool isHeightActive = (orientation == Orientation.landscape && + height >= lowerBoundHeight && + height < upperBoundHeight) || + orientation == Orientation.portrait; + + return isWidthActive && isHeightActive && isRightPlatform; + } } diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index 93f7d409e1a..504f5257c9a 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.12 +version: 0.2.0 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 3166e5b382d..7a7ab6f1300 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart @@ -15,7 +15,7 @@ void main() { MediaQuery slot(double width) { return MediaQuery( data: MediaQueryData.fromView(tester.view) - .copyWith(size: Size(width, 800)), + .copyWith(size: Size(width, 2000)), child: Directionality( textDirection: TextDirection.ltr, child: SlotLayout( @@ -26,6 +26,10 @@ void main() { key: const Key('400'), builder: (_) => const SizedBox()), TestBreakpoint800(): SlotLayout.from( key: const Key('800'), builder: (_) => const SizedBox()), + TestBreakpoint1200(): SlotLayout.from( + key: const Key('1200'), builder: (_) => const SizedBox()), + TestBreakpoint1600(): SlotLayout.from( + key: const Key('1600'), builder: (_) => const SizedBox()), }, ), ), @@ -36,16 +40,36 @@ void main() { expect(find.byKey(const Key('0')), findsOneWidget); expect(find.byKey(const Key('400')), findsNothing); expect(find.byKey(const Key('800')), findsNothing); + expect(find.byKey(const Key('1200')), findsNothing); + expect(find.byKey(const Key('1600')), findsNothing); await tester.pumpWidget(slot(500)); expect(find.byKey(const Key('0')), findsNothing); expect(find.byKey(const Key('400')), findsOneWidget); expect(find.byKey(const Key('800')), findsNothing); + expect(find.byKey(const Key('1200')), findsNothing); + expect(find.byKey(const Key('1600')), findsNothing); await tester.pumpWidget(slot(1000)); expect(find.byKey(const Key('0')), findsNothing); expect(find.byKey(const Key('400')), findsNothing); expect(find.byKey(const Key('800')), findsOneWidget); + expect(find.byKey(const Key('1200')), findsNothing); + expect(find.byKey(const Key('1600')), findsNothing); + + await tester.pumpWidget(slot(1400)); + expect(find.byKey(const Key('0')), findsNothing); + expect(find.byKey(const Key('400')), findsNothing); + expect(find.byKey(const Key('800')), findsNothing); + expect(find.byKey(const Key('1200')), findsOneWidget); + expect(find.byKey(const Key('1600')), findsNothing); + + await tester.pumpWidget(slot(1800)); + expect(find.byKey(const Key('0')), findsNothing); + expect(find.byKey(const Key('400')), findsNothing); + expect(find.byKey(const Key('800')), findsNothing); + expect(find.byKey(const Key('1200')), findsNothing); + expect(find.byKey(const Key('1600')), findsOneWidget); }); testWidgets('adaptive layout displays children in correct places', @@ -55,12 +79,12 @@ void main() { expect(tester.getTopLeft(topNavigation), Offset.zero); expect(tester.getTopLeft(secondaryNavigation), const Offset(390, 10)); expect(tester.getTopLeft(primaryNavigation), const Offset(0, 10)); - expect(tester.getTopLeft(bottomNavigation), const Offset(0, 790)); + expect(tester.getTopLeft(bottomNavigation), const Offset(0, 1990)); expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); - expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790)); + expect(tester.getBottomRight(testBreakpoint), const Offset(200, 1990)); expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); + expect(tester.getBottomRight(secondaryTestBreakpoint), + const Offset(390, 1990)); }); testWidgets('adaptive layout correct layout when body vertical', @@ -71,12 +95,12 @@ void main() { expect(tester.getTopLeft(topNavigation), Offset.zero); expect(tester.getTopLeft(secondaryNavigation), const Offset(390, 10)); expect(tester.getTopLeft(primaryNavigation), const Offset(0, 10)); - expect(tester.getTopLeft(bottomNavigation), const Offset(0, 790)); + expect(tester.getTopLeft(bottomNavigation), const Offset(0, 1990)); expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); - expect(tester.getBottomRight(testBreakpoint), const Offset(390, 400)); - expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(10, 400)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); + expect(tester.getBottomRight(testBreakpoint), const Offset(390, 1000)); + expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(10, 1000)); + expect(tester.getBottomRight(secondaryTestBreakpoint), + const Offset(390, 1990)); }); testWidgets('adaptive layout correct layout when rtl', @@ -87,12 +111,12 @@ void main() { expect(tester.getTopLeft(topNavigation), Offset.zero); expect(tester.getTopLeft(secondaryNavigation), const Offset(0, 10)); expect(tester.getTopLeft(primaryNavigation), const Offset(390, 10)); - expect(tester.getTopLeft(bottomNavigation), const Offset(0, 790)); + expect(tester.getTopLeft(bottomNavigation), const Offset(0, 1990)); expect(tester.getTopLeft(testBreakpoint), const Offset(200, 10)); - expect(tester.getBottomRight(testBreakpoint), const Offset(390, 790)); + expect(tester.getBottomRight(testBreakpoint), const Offset(390, 1990)); expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(10, 10)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(200, 790)); + expect(tester.getBottomRight(secondaryTestBreakpoint), + const Offset(200, 1990)); }); testWidgets('adaptive layout correct layout when body ratio not default', @@ -103,20 +127,24 @@ void main() { expect(tester.getTopLeft(topNavigation), Offset.zero); expect(tester.getTopLeft(secondaryNavigation), const Offset(390, 10)); expect(tester.getTopLeft(primaryNavigation), const Offset(0, 10)); - expect(tester.getTopLeft(bottomNavigation), const Offset(0, 790)); + expect(tester.getTopLeft(bottomNavigation), const Offset(0, 1990)); expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); expect(tester.getBottomRight(testBreakpoint), - offsetMoreOrLessEquals(const Offset(136.7, 790), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(136.7, 1990), epsilon: 1.0)); expect(tester.getTopLeft(secondaryTestBreakpoint), offsetMoreOrLessEquals(const Offset(136.7, 10), epsilon: 1.0)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); + expect(tester.getBottomRight(secondaryTestBreakpoint), + const Offset(390, 1990)); }); final Finder begin = find.byKey(const Key('0')); final Finder end = find.byKey(const Key('400')); + final Finder large = find.byKey(const Key('1200')); + final Finder extraLarge = find.byKey(const Key('1600')); + Finder slideIn(String key) => find.byKey(Key('in-${Key(key)}')); Finder slideOut(String key) => find.byKey(Key('out-${Key(key)}')); + testWidgets( 'slot layout properly switches between items with the appropriate animation', (WidgetTester tester) async { @@ -143,6 +171,44 @@ void main() { await tester.pumpAndSettle(); expect(begin, findsNothing); expect(end, findsOneWidget); + + await tester + .pumpWidget(slot(1300, const Duration(milliseconds: 1000), tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.widget(slideOut('400')).position.value, + const Offset(-0.5, 0)); + expect(tester.widget(slideIn('1200')).position.value, + const Offset(-0.5, 0)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.widget(slideOut('400')).position.value, + const Offset(-1.0, 0)); + expect(tester.widget(slideIn('1200')).position.value, + Offset.zero); + + await tester.pumpAndSettle(); + expect(end, findsNothing); + expect(large, findsOneWidget); + + await tester + .pumpWidget(slot(1700, const Duration(milliseconds: 1000), tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.widget(slideOut('1200')).position.value, + const Offset(-0.5, 0)); + expect(tester.widget(slideIn('1600')).position.value, + const Offset(-0.5, 0)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.widget(slideOut('1200')).position.value, + const Offset(-1.0, 0)); + expect(tester.widget(slideIn('1600')).position.value, + Offset.zero); + + await tester.pumpAndSettle(); + expect(large, findsNothing); + expect(extraLarge, findsOneWidget); }); testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize', @@ -207,42 +273,42 @@ void main() { expect(tester.getTopLeft(testBreakpoint), const Offset(1, 1)); expect(tester.getBottomRight(testBreakpoint), - offsetMoreOrLessEquals(const Offset(395.8, 799), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(395.8, 1999), epsilon: 1.0)); expect(tester.getTopLeft(secondaryTestBreakpoint), offsetMoreOrLessEquals(const Offset(395.8, 1.0), epsilon: 1.0)); expect(tester.getBottomRight(secondaryTestBreakpoint), - offsetMoreOrLessEquals(const Offset(594.8, 799.0), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(594.8, 1999.0), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); expect(tester.getTopLeft(testBreakpoint), const Offset(5, 5)); expect(tester.getBottomRight(testBreakpoint), - offsetMoreOrLessEquals(const Offset(294.2, 795), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(294.2, 1995), epsilon: 1.0)); expect(tester.getTopLeft(secondaryTestBreakpoint), offsetMoreOrLessEquals(const Offset(294.2, 5.0), epsilon: 1.0)); expect(tester.getBottomRight(secondaryTestBreakpoint), - offsetMoreOrLessEquals(const Offset(489.2, 795.0), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(489.2, 1995.0), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); expect(tester.getTopLeft(testBreakpoint), const Offset(9, 9)); expect(tester.getBottomRight(testBreakpoint), - offsetMoreOrLessEquals(const Offset(201.7, 791), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(201.7, 1991), epsilon: 1.0)); expect(tester.getTopLeft(secondaryTestBreakpoint), offsetMoreOrLessEquals(const Offset(201.7, 9.0), epsilon: 1.0)); expect(tester.getBottomRight(secondaryTestBreakpoint), - offsetMoreOrLessEquals(const Offset(392.7, 791), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(392.7, 1991), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); - expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790)); + expect(tester.getBottomRight(testBreakpoint), const Offset(200, 1990)); expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); + expect(tester.getBottomRight(secondaryTestBreakpoint), + const Offset(390, 1990)); }); testWidgets('adaptive layout can adjust animation duration', @@ -282,7 +348,95 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); - expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790)); + expect(tester.getBottomRight(testBreakpoint), const Offset(200, 1990)); + + await tester.pumpWidget( + await layout(width: 800, tester: tester, animations: false)); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getTopLeft(testBreakpoint400), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint400), const Offset(400, 1990)); + + await tester.pumpWidget( + await layout(width: 1000, tester: tester, animations: false)); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getTopLeft(testBreakpoint800), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint800), const Offset(500, 1990)); + + await tester.pumpWidget( + await layout(width: 1300, tester: tester, animations: false)); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getTopLeft(testBreakpoint1200), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint1200), const Offset(650, 1990)); + + await tester.pumpWidget( + await layout(width: 1700, tester: tester, animations: false)); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getTopLeft(testBreakpoint1600), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint1600), const Offset(850, 1990)); + }); + + testWidgets( + 'adaptive layout handles internal animations correctly for additional breakpoints', + (WidgetTester tester) async { + await tester.pumpWidget(await layout(width: 800, tester: tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getTopLeft(testBreakpoint400), const Offset(1, 1)); + expect(tester.getBottomRight(testBreakpoint400), + offsetMoreOrLessEquals(const Offset(792.6, 1999), epsilon: 1.0)); + expect(tester.getTopLeft(secondaryTestBreakpoint400), + offsetMoreOrLessEquals(const Offset(792.6, 1.0), epsilon: 1.0)); + expect(tester.getBottomRight(secondaryTestBreakpoint400), + offsetMoreOrLessEquals(const Offset(1191.6, 1999.0), epsilon: 1.0)); + + await tester.pumpWidget(await layout(width: 1000, tester: tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(tester.getTopLeft(testBreakpoint800), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint800), + offsetMoreOrLessEquals(const Offset(855.3, 1990.0), epsilon: 1.0)); + expect(tester.getTopLeft(secondaryTestBreakpoint800), + offsetMoreOrLessEquals(const Offset(855.3, 10.0), epsilon: 1.0)); + expect(tester.getBottomRight(secondaryTestBreakpoint800), + offsetMoreOrLessEquals(const Offset(1345.3, 1990.0), epsilon: 1.0)); + + await tester.pumpWidget(await layout(width: 1300, tester: tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(tester.getTopLeft(testBreakpoint1200), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint1200), + offsetMoreOrLessEquals(const Offset(1114.0, 1990.0), epsilon: 1.0)); + expect(tester.getTopLeft(secondaryTestBreakpoint1200), + offsetMoreOrLessEquals(const Offset(1114.0, 10.0), epsilon: 1.0)); + expect(tester.getBottomRight(secondaryTestBreakpoint1200), + offsetMoreOrLessEquals(const Offset(1754.0, 1990.0), epsilon: 1.0)); + + await tester.pumpWidget(await layout(width: 1700, tester: tester)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(tester.getTopLeft(testBreakpoint1600), const Offset(10, 10)); + expect(tester.getBottomRight(testBreakpoint1600), + offsetMoreOrLessEquals(const Offset(1459.1, 1990.0), epsilon: 1.0)); + expect(tester.getTopLeft(secondaryTestBreakpoint1600), + offsetMoreOrLessEquals(const Offset(1459.1, 10.0), epsilon: 1.0)); + expect(tester.getBottomRight(secondaryTestBreakpoint1600), + offsetMoreOrLessEquals(const Offset(2299.1, 1990.0), epsilon: 1.0)); }); } @@ -307,6 +461,20 @@ class TestBreakpoint800 extends Breakpoint { } } +class TestBreakpoint1200 extends Breakpoint { + @override + bool isActive(BuildContext context) { + return MediaQuery.of(context).size.width > 1200; + } +} + +class TestBreakpoint1600 extends Breakpoint { + @override + bool isActive(BuildContext context) { + return MediaQuery.of(context).size.width > 1600; + } +} + final Finder topNavigation = find.byKey(const Key('Top Navigation')); final Finder secondaryNavigation = find.byKey(const Key('Secondary Navigation Small')); @@ -315,8 +483,21 @@ final Finder primaryNavigation = final Finder bottomNavigation = find.byKey(const Key('Bottom Navigation Small')); final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint')); +final Finder testBreakpoint400 = find.byKey(const Key('Test Breakpoint 400')); +final Finder testBreakpoint800 = find.byKey(const Key('Test Breakpoint 800')); +final Finder testBreakpoint1200 = find.byKey(const Key('Test Breakpoint 1200')); +final Finder testBreakpoint1600 = find.byKey(const Key('Test Breakpoint 1600')); + final Finder secondaryTestBreakpoint = find.byKey(const Key('Secondary Test Breakpoint')); +final Finder secondaryTestBreakpoint400 = + find.byKey(const Key('Secondary Test Breakpoint 400')); +final Finder secondaryTestBreakpoint800 = + find.byKey(const Key('Secondary Test Breakpoint 800')); +final Finder secondaryTestBreakpoint1200 = + find.byKey(const Key('Secondary Test Breakpoint 1200')); +final Finder secondaryTestBreakpoint1600 = + find.byKey(const Key('Secondary Test Breakpoint 1600')); Widget on(BuildContext _) { return const SizedBox(width: 10, height: 10); @@ -331,9 +512,9 @@ Future layout({ bool animations = true, int durationMs = 1000, }) async { - await tester.binding.setSurfaceSize(Size(width, 800)); + await tester.binding.setSurfaceSize(Size(width, 2000)); return MediaQuery( - data: MediaQueryData(size: Size(width, 800)), + data: MediaQueryData(size: Size(width, 2000)), child: Directionality( textDirection: directionality, child: AdaptiveLayout( @@ -347,7 +528,11 @@ Future layout({ TestBreakpoint400(): SlotLayout.from( key: const Key('Primary Navigation Medium'), builder: on), TestBreakpoint800(): SlotLayout.from( + key: const Key('Primary Navigation MediumLarge'), builder: on), + TestBreakpoint1200(): SlotLayout.from( key: const Key('Primary Navigation Large'), builder: on), + TestBreakpoint1600(): SlotLayout.from( + key: const Key('Primary Navigation ExtraLarge'), builder: on), }, ), secondaryNavigation: SlotLayout( @@ -357,7 +542,12 @@ Future layout({ TestBreakpoint400(): SlotLayout.from( key: const Key('Secondary Navigation Medium'), builder: on), TestBreakpoint800(): SlotLayout.from( + key: const Key('Secondary Navigation MediumLarge'), + builder: on), + TestBreakpoint1200(): SlotLayout.from( key: const Key('Secondary Navigation Large'), builder: on), + TestBreakpoint1600(): SlotLayout.from( + key: const Key('Secondary Navigation ExtraLarge'), builder: on), }, ), topNavigation: SlotLayout( @@ -368,6 +558,10 @@ Future layout({ SlotLayout.from(key: const Key('Top Navigation1'), builder: on), TestBreakpoint800(): SlotLayout.from(key: const Key('Top Navigation2'), builder: on), + TestBreakpoint1200(): + SlotLayout.from(key: const Key('Top Navigation3'), builder: on), + TestBreakpoint1600(): + SlotLayout.from(key: const Key('Top Navigation4'), builder: on), }, ), bottomNavigation: SlotLayout( @@ -378,6 +572,10 @@ Future layout({ SlotLayout.from(key: const Key('bnav1'), builder: on), TestBreakpoint800(): SlotLayout.from(key: const Key('bnav2'), builder: on), + TestBreakpoint1200(): + SlotLayout.from(key: const Key('bnav3'), builder: on), + TestBreakpoint1600(): + SlotLayout.from(key: const Key('bnav4'), builder: on), }, ), body: SlotLayout( @@ -387,11 +585,19 @@ Future layout({ builder: (_) => Container(color: Colors.red), ), TestBreakpoint400(): SlotLayout.from( - key: const Key('Test Breakpoint 1'), + key: const Key('Test Breakpoint 400'), builder: (_) => Container(color: Colors.red), ), TestBreakpoint800(): SlotLayout.from( - key: const Key('Test Breakpoint 2'), + key: const Key('Test Breakpoint 800'), + builder: (_) => Container(color: Colors.red), + ), + TestBreakpoint1200(): SlotLayout.from( + key: const Key('Test Breakpoint 1200'), + builder: (_) => Container(color: Colors.red), + ), + TestBreakpoint1600(): SlotLayout.from( + key: const Key('Test Breakpoint 1600'), builder: (_) => Container(color: Colors.red), ), }, @@ -403,11 +609,19 @@ Future layout({ builder: (_) => Container(color: Colors.blue), ), TestBreakpoint400(): SlotLayout.from( - key: const Key('Secondary Test Breakpoint 1'), + key: const Key('Secondary Test Breakpoint 400'), builder: (_) => Container(color: Colors.blue), ), TestBreakpoint800(): SlotLayout.from( - key: const Key('Secondary Test Breakpoint 2'), + key: const Key('Secondary Test Breakpoint 800'), + builder: (_) => Container(color: Colors.blue), + ), + TestBreakpoint1200(): SlotLayout.from( + key: const Key('Secondary Test Breakpoint 1200'), + builder: (_) => Container(color: Colors.blue), + ), + TestBreakpoint1600(): SlotLayout.from( + key: const Key('Secondary Test Breakpoint 1600'), builder: (_) => Container(color: Colors.blue), ), }, @@ -441,7 +655,8 @@ AnimatedWidget leftInOut(Widget child, Animation animation) { MediaQuery slot(double width, Duration duration, WidgetTester tester) { return MediaQuery( - data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)), + data: + MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 2000)), child: Directionality( textDirection: TextDirection.ltr, child: SlotLayout( @@ -460,6 +675,27 @@ MediaQuery slot(double width, Duration duration, WidgetTester tester) { key: const Key('400'), builder: (_) => const SizedBox(width: 10, height: 10), ), + TestBreakpoint800(): SlotLayout.from( + inAnimation: leftOutIn, + outAnimation: leftInOut, + duration: duration, + key: const Key('800'), + builder: (_) => const SizedBox(width: 10, height: 10), + ), + TestBreakpoint1200(): SlotLayout.from( + inAnimation: leftOutIn, + outAnimation: leftInOut, + duration: duration, + key: const Key('1200'), + builder: (_) => const SizedBox(width: 10, height: 10), + ), + TestBreakpoint1600(): SlotLayout.from( + inAnimation: leftOutIn, + outAnimation: leftInOut, + duration: duration, + key: const Key('1600'), + builder: (_) => const SizedBox(width: 10, height: 10), + ), }, ), ), diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart index 40b6754d30b..45afcddce87 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart @@ -14,13 +14,21 @@ void main() { (WidgetTester tester) async { final Finder smallBody = find.byKey(const Key('smallBody')); final Finder body = find.byKey(const Key('body')); + final Finder mediumLargeBody = find.byKey(const Key('mediumLargeBody')); final Finder largeBody = find.byKey(const Key('largeBody')); + final Finder extraLargeBody = find.byKey(const Key('extraLargeBody')); + final Finder smallSBody = find.byKey(const Key('smallSBody')); final Finder sBody = find.byKey(const Key('sBody')); + final Finder mediumLargeSBody = find.byKey(const Key('mediumLargeSBody')); final Finder largeSBody = find.byKey(const Key('largeSBody')); + final Finder extraLargeSBody = find.byKey(const Key('extraLargeSBody')); + final Finder bottomNav = find.byKey(const Key('bottomNavigation')); final Finder primaryNav = find.byKey(const Key('primaryNavigation')); final Finder primaryNav1 = find.byKey(const Key('primaryNavigation1')); + final Finder primaryNav2 = find.byKey(const Key('primaryNavigation2')); + final Finder primaryNav3 = find.byKey(const Key('primaryNavigation3')); await tester.binding.setSurfaceSize(SimulatedLayout.small.size); await tester.pumpWidget(SimulatedLayout.small.app()); @@ -33,7 +41,7 @@ void main() { expect(tester.getTopLeft(smallBody), Offset.zero); expect(tester.getTopLeft(smallSBody), const Offset(200, 0)); - expect(tester.getTopLeft(bottomNav), const Offset(0, 720)); + expect(tester.getTopLeft(bottomNav), const Offset(0, 1920)); await tester.binding.setSurfaceSize(SimulatedLayout.medium.size); await tester.pumpWidget(SimulatedLayout.medium.app()); @@ -49,23 +57,55 @@ void main() { expect(tester.getTopLeft(body), const Offset(88, 0)); expect(tester.getTopLeft(sBody), const Offset(400, 0)); expect(tester.getTopLeft(primaryNav), Offset.zero); - expect(tester.getBottomRight(primaryNav), const Offset(88, 800)); + expect(tester.getBottomRight(primaryNav), const Offset(88, 2000)); - await tester.binding.setSurfaceSize(SimulatedLayout.large.size); - await tester.pumpWidget(SimulatedLayout.large.app()); + await tester.binding.setSurfaceSize(SimulatedLayout.mediumLarge.size); + await tester.pumpWidget(SimulatedLayout.mediumLarge.app()); await tester.pumpAndSettle(); expect(body, findsNothing); - expect(largeBody, findsOneWidget); + expect(mediumLargeBody, findsOneWidget); expect(sBody, findsNothing); - expect(largeSBody, findsOneWidget); + expect(mediumLargeSBody, findsOneWidget); expect(primaryNav, findsNothing); expect(primaryNav1, findsOneWidget); - expect(tester.getTopLeft(largeBody), const Offset(208, 0)); - expect(tester.getTopLeft(largeSBody), const Offset(550, 0)); + expect(tester.getTopLeft(mediumLargeBody), const Offset(208, 0)); + expect(tester.getTopLeft(mediumLargeSBody), const Offset(500, 0)); expect(tester.getTopLeft(primaryNav1), Offset.zero); - expect(tester.getBottomRight(primaryNav1), const Offset(208, 800)); + expect(tester.getBottomRight(primaryNav1), const Offset(208, 2000)); + + await tester.binding.setSurfaceSize(SimulatedLayout.large.size); + await tester.pumpWidget(SimulatedLayout.large.app()); + await tester.pumpAndSettle(); + + expect(mediumLargeBody, findsNothing); + expect(largeBody, findsOneWidget); + expect(mediumLargeSBody, findsNothing); + expect(largeSBody, findsOneWidget); + expect(primaryNav1, findsNothing); + expect(primaryNav2, findsOneWidget); + + expect(tester.getTopLeft(largeBody), const Offset(208, 0)); + expect(tester.getTopLeft(largeSBody), const Offset(600, 0)); + expect(tester.getTopLeft(primaryNav2), Offset.zero); + expect(tester.getBottomRight(primaryNav2), const Offset(208, 2000)); + + await tester.binding.setSurfaceSize(SimulatedLayout.extraLarge.size); + await tester.pumpWidget(SimulatedLayout.extraLarge.app()); + await tester.pumpAndSettle(); + + expect(largeBody, findsNothing); + expect(extraLargeBody, findsOneWidget); + expect(largeSBody, findsNothing); + expect(extraLargeSBody, findsOneWidget); + expect(primaryNav2, findsNothing); + expect(primaryNav3, findsOneWidget); + + expect(tester.getTopLeft(extraLargeBody), const Offset(208, 0)); + expect(tester.getTopLeft(extraLargeSBody), const Offset(800, 0)); + expect(tester.getTopLeft(primaryNav3), Offset.zero); + expect(tester.getBottomRight(primaryNav3), const Offset(208, 2000)); }); testWidgets('adaptive scaffold animations work correctly', @@ -83,30 +123,30 @@ void main() { expect(tester.getTopLeft(b), const Offset(17.6, 0)); expect(tester.getBottomRight(b), - offsetMoreOrLessEquals(const Offset(778.2, 736), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(778.2, 1936), epsilon: 1.0)); expect(tester.getTopLeft(sBody), offsetMoreOrLessEquals(const Offset(778.2, 0), epsilon: 1.0)); expect(tester.getBottomRight(sBody), - offsetMoreOrLessEquals(const Offset(1178.2, 736), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(1178.2, 1936), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 600)); expect(tester.getTopLeft(b), const Offset(70.4, 0)); expect(tester.getBottomRight(b), - offsetMoreOrLessEquals(const Offset(416.0, 784), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(416.0, 1984), epsilon: 1.0)); expect(tester.getTopLeft(sBody), offsetMoreOrLessEquals(const Offset(416, 0), epsilon: 1.0)); expect(tester.getBottomRight(sBody), - offsetMoreOrLessEquals(const Offset(816, 784), epsilon: 1.0)); + offsetMoreOrLessEquals(const Offset(816, 1984), epsilon: 1.0)); await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(b), const Offset(88.0, 0)); - expect(tester.getBottomRight(b), const Offset(400, 800)); + expect(tester.getBottomRight(b), const Offset(400, 2000)); expect(tester.getTopLeft(sBody), const Offset(400, 0)); - expect(tester.getBottomRight(sBody), const Offset(800, 800)); + expect(tester.getBottomRight(sBody), const Offset(800, 2000)); }); testWidgets('adaptive scaffold animations can be disabled', @@ -124,9 +164,9 @@ void main() { await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(b), const Offset(88.0, 0)); - expect(tester.getBottomRight(b), const Offset(400, 800)); + expect(tester.getBottomRight(b), const Offset(400, 2000)); expect(tester.getTopLeft(sBody), const Offset(400, 0)); - expect(tester.getBottomRight(sBody), const Offset(800, 800)); + expect(tester.getBottomRight(sBody), const Offset(800, 2000)); }); // The goal of this test is to run through each of the navigation elements @@ -207,7 +247,7 @@ void main() { await tester.pumpWidget(app); await tester.pumpAndSettle(); - if (region.size == SimulatedLayout.large.size) { + if (region.size == SimulatedLayout.mediumLarge.size) { expect(find.text('leading_extended'), findsOneWidget); expect(find.text('leading_unextended'), findsNothing); expect(find.text('trailing'), findsOneWidget); @@ -456,19 +496,19 @@ void main() { ); testWidgets( - 'when view in large screen, navigation rail must be visible as per theme data values.', + 'when view in mediumLarge screen, navigation rail must be visible as per theme data values.', (WidgetTester tester) async { - await tester.binding.setSurfaceSize(SimulatedLayout.large.size); - await tester.pumpWidget(SimulatedLayout.large.app()); + await tester.binding.setSurfaceSize(SimulatedLayout.mediumLarge.size); + await tester.pumpWidget(SimulatedLayout.mediumLarge.app()); await tester.pumpAndSettle(); - final Finder primaryNavigationLarge = find.byKey( + final Finder primaryNavigationMediumLarge = find.byKey( const Key('primaryNavigation1'), ); - expect(primaryNavigationLarge, findsOneWidget); + expect(primaryNavigationMediumLarge, findsOneWidget); final Finder navigationRailFinder = find.descendant( - of: primaryNavigationLarge, + of: primaryNavigationMediumLarge, matching: find.byType(NavigationRail), ); expect(navigationRailFinder, findsOneWidget); @@ -708,8 +748,8 @@ void main() { expect(appBar, findsOneWidget); expect(drawer, findsNothing); - await tester.binding.setSurfaceSize(SimulatedLayout.large.size); - await tester.pumpWidget(SimulatedLayout.large + await tester.binding.setSurfaceSize(SimulatedLayout.mediumLarge.size); + await tester.pumpWidget(SimulatedLayout.mediumLarge .app(appBarBreakpoint: AppBarAlwaysOnBreakpoint())); expect(drawer, findsNothing); await tester.pumpAndSettle(); diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index c0975092db6..e990b3a7f91 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -17,17 +17,33 @@ void main() { expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.smallDesktop')), findsNothing); - // Do the same with a medium layout on a mobile + // Do the same with a medium layout on a mobile. await tester.pumpWidget(SimulatedLayout.medium.slot(tester)); await tester.pumpAndSettle(); expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.mediumDesktop')), findsNothing); - // Large layout on mobile + // Do the same with an mediumLarge layout on a mobile. + await tester.pumpWidget(SimulatedLayout.mediumLarge.slot(tester)); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('Breakpoints.mediumLargeMobile')), findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.mediumLargeDesktop')), findsNothing); + + // Do the same with an large layout on a mobile. await tester.pumpWidget(SimulatedLayout.large.slot(tester)); await tester.pumpAndSettle(); expect(find.byKey(const Key('Breakpoints.largeMobile')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsNothing); + + // Do the same with an extraLarge layout on a mobile. + await tester.pumpWidget(SimulatedLayout.extraLarge.slot(tester)); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('Breakpoints.extraLargeMobile')), findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.extraLargeDesktop')), findsNothing); }, variant: TargetPlatformVariant.mobile()); testWidgets('Mobile breakpoints do not show on desktop device', @@ -45,34 +61,48 @@ void main() { expect(find.byKey(const Key('Breakpoints.mediumDesktop')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + // Do the same with an mediumLarge layout on a desktop. + await tester.pumpWidget(SimulatedLayout.mediumLarge.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumLargeDesktop')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.mediumLargeMobile')), findsNothing); + // Large layout on desktop await tester.pumpWidget(SimulatedLayout.large.slot(tester)); await tester.pumpAndSettle(); expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + + await tester.pumpWidget(SimulatedLayout.extraLarge.slot(tester)); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('Breakpoints.extraLargeDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.extraLargeMobile')), findsNothing); }, variant: TargetPlatformVariant.desktop()); testWidgets('Breakpoint.isActive should not trigger unnecessary rebuilds', (WidgetTester tester) async { - await tester.pumpWidget(const DymmyWidget()); + await tester.pumpWidget(const DummyWidget()); expect(find.byKey(const Key('button')), findsOneWidget); // First build. - expect(DymmyWidget.built, isTrue); + expect(DummyWidget.built, isTrue); // Invoke `isActive` method. await tester.tap(find.byKey(const Key('button'))); - DymmyWidget.built = false; + DummyWidget.built = false; // Should not rebuild after modifying any property in `MediaQuery`. tester.platformDispatcher.textScaleFactorTestValue = 2; await tester.pumpAndSettle(); - expect(DymmyWidget.built, isFalse); + expect(DummyWidget.built, isFalse); }); } -class DymmyWidget extends StatelessWidget { - const DymmyWidget({super.key}); +class DummyWidget extends StatelessWidget { + const DummyWidget({super.key}); static bool built = false; @override diff --git a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart index 706883d997d..8c8b6ce5251 100644 --- a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart +++ b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart @@ -82,14 +82,20 @@ class TestScaffoldState extends State { internalAnimations: widget.isAnimated, smallBreakpoint: TestBreakpoint0(), mediumBreakpoint: TestBreakpoint800(), - largeBreakpoint: TestBreakpoint1000(), + mediumLargeBreakpoint: TestBreakpoint1000(), + largeBreakpoint: TestBreakpoint1200(), + extraLargeBreakpoint: TestBreakpoint1600(), destinations: TestScaffold.destinations, smallBody: (_) => Container(color: Colors.red), body: (_) => Container(color: Colors.green), - largeBody: (_) => Container(color: Colors.blue), + mediumLargeBody: (_) => Container(color: Colors.blue), + largeBody: (_) => Container(color: Colors.yellow), + extraLargeBody: (_) => Container(color: Colors.purple), smallSecondaryBody: (_) => Container(color: Colors.red), secondaryBody: (_) => Container(color: Colors.green), - largeSecondaryBody: (_) => Container(color: Colors.blue), + mediumLargeSecondaryBody: (_) => Container(color: Colors.blue), + largeSecondaryBody: (_) => Container(color: Colors.yellow), + extraLargeSecondaryBody: (_) => Container(color: Colors.purple), leadingExtendedNavRail: const Text('leading_extended'), leadingUnextendedNavRail: const Text('leading_unextended'), trailingNavRail: const Text('trailing'), @@ -100,7 +106,9 @@ class TestScaffoldState extends State { enum SimulatedLayout { small(width: 400, navSlotKey: 'bottomNavigation'), medium(width: 800, navSlotKey: 'primaryNavigation'), - large(width: 1100, navSlotKey: 'primaryNavigation1'); + mediumLarge(width: 1000, navSlotKey: 'primaryNavigation1'), + large(width: 1200, navSlotKey: 'primaryNavigation2'), + extraLarge(width: 1600, navSlotKey: 'primaryNavigation3'); const SimulatedLayout({ required double width, @@ -108,7 +116,7 @@ enum SimulatedLayout { }) : _width = width; final double _width; - final double _height = 800; + final double _height = 2000; final String navSlotKey; static const Color navigationRailThemeBgColor = Colors.white; @@ -184,6 +192,18 @@ enum SimulatedLayout { key: const Key('Breakpoints.mediumDesktop'), builder: (BuildContext context) => Container(), ), + Breakpoints.mediumLarge: SlotLayout.from( + key: const Key('Breakpoints.mediumLarge'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.mediumLargeMobile: SlotLayout.from( + key: const Key('Breakpoints.mediumLargeMobile'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.mediumLargeDesktop: SlotLayout.from( + key: const Key('Breakpoints.mediumLargeDesktop'), + builder: (BuildContext context) => Container(), + ), Breakpoints.large: SlotLayout.from( key: const Key('Breakpoints.large'), builder: (BuildContext context) => Container(), @@ -196,6 +216,18 @@ enum SimulatedLayout { key: const Key('Breakpoints.largeDesktop'), builder: (BuildContext context) => Container(), ), + Breakpoints.extraLarge: SlotLayout.from( + key: const Key('Breakpoints.extraLarge'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.extraLargeMobile: SlotLayout.from( + key: const Key('Breakpoints.extraLargeMobile'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.extraLargeDesktop: SlotLayout.from( + key: const Key('Breakpoints.extraLargeDesktop'), + builder: (BuildContext context) => Container(), + ), }, ), ), diff --git a/packages/flutter_adaptive_scaffold/test/test_breakpoints.dart b/packages/flutter_adaptive_scaffold/test/test_breakpoints.dart index 2a54acd6b5c..ef94b2f2aa2 100644 --- a/packages/flutter_adaptive_scaffold/test/test_breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/test/test_breakpoints.dart @@ -31,7 +31,23 @@ class TestBreakpoint800 extends Breakpoint { class TestBreakpoint1000 extends Breakpoint { @override bool isActive(BuildContext context) { - return MediaQuery.of(context).size.width >= 1000; + return MediaQuery.of(context).size.width >= 1000 && + MediaQuery.of(context).size.width < 1200; + } +} + +class TestBreakpoint1200 extends Breakpoint { + @override + bool isActive(BuildContext context) { + return MediaQuery.of(context).size.width >= 1200 && + MediaQuery.of(context).size.width < 1600; + } +} + +class TestBreakpoint1600 extends Breakpoint { + @override + bool isActive(BuildContext context) { + return MediaQuery.of(context).size.width >= 1600; } }