From d67d4a2f0901b7555edbc25531e07842c8d1c26a Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Fri, 16 Aug 2024 14:42:58 +0200 Subject: [PATCH 1/8] Fix landscape not showing in andUp --- .../lib/src/breakpoints.dart | 17 +- .../test/breakpoint_test.dart | 321 ++++++++++++++++++ .../test/simulated_layout.dart | 40 ++- 3 files changed, 371 insertions(+), 7 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 286a49811f10..8457252fded4 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -17,7 +17,7 @@ class Breakpoints { /// case that no other breakpoint is active. /// /// It is active from a width of -1 dp to infinity. - static const Breakpoint standard = Breakpoint(beginWidth: -1); + static const Breakpoint standard = Breakpoint.standard(); /// A window whose width is less than 600 dp and greater than 0 dp. static const Breakpoint small = Breakpoint.small(); @@ -117,6 +117,15 @@ class Breakpoint { this.andUp = false, }); + /// Returns a [Breakpoint] that can be used as a fallthrough in the + /// case that no other breakpoint is active. + const Breakpoint.standard({this.platform}) + : beginWidth = -1, + endWidth = null, + beginHeight = null, + endHeight = null, + andUp = true; + /// Returns a [Breakpoint] with the given constraints for a small screen. const Breakpoint.small({this.andUp = false, this.platform}) : beginWidth = 0, @@ -213,9 +222,9 @@ class Breakpoint { final bool isHeightActive = isDesktop || orientation == Orientation.portrait || - (orientation == Orientation.landscape && - height >= lowerBoundHeight && - height < upperBoundHeight); + (orientation == Orientation.landscape && andUp + ? height >= lowerBoundHeight + : height >= lowerBoundHeight && height < upperBoundHeight); return isWidthActive && isHeightActive && isRightPlatform; } diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index cbef74c570ee..32d39798a9e7 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -264,6 +264,327 @@ void main() { Breakpoints.largeDesktop); }, variant: TargetPlatformVariant.desktop()); }); + + group('Landscape Layout Tests', () { + testWidgets('Desktop breakpoints do not show on mobile device (landscape)', + (WidgetTester tester) async { + // Small landscape layout on a mobile device. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallDesktop')), findsNothing); + + // Medium landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumDesktop')), findsNothing); + + // MediumLarge landscape layout on a mobile. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumLargeMobile')), + findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumLargeDesktop')), + findsNothing); + + // Large landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsNothing); + + // ExtraLarge landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.extraLargeLandscape.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 (landscape)', + (WidgetTester tester) async { + // Small landscape layout on a desktop device. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); + + // Medium landscape layout on a desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('Breakpoints.mediumDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + + // MediumLarge landscape layout on a desktop. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumLargeDesktop')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.mediumLargeMobile')), findsNothing); + + // Large landscape layout on a desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.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.extraLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.extraLargeDesktop')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.extraLargeMobile')), findsNothing); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `maybeActiveBreakpointFromSlotLayout` + testWidgets( + 'maybeActiveBreakpointFromSlotLayout returns correct breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout( + tester.element(find.byKey(const Key('Breakpoints.smallMobile')))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.mediumMobile')))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout( + tester.element(find.byKey(const Key('Breakpoints.largeMobile')))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'maybeActiveBreakpointFromSlotLayout returns correct breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.smallDesktop')))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.mediumDesktop')))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.largeDesktop')))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `defaultBreakpointOf` + testWidgets( + 'defaultBreakpointOf returns correct default breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'defaultBreakpointOf returns correct default breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `activeBreakpointOf` + testWidgets( + 'activeBreakpointOf returns correct active breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf( + tester.element(find.byKey(const Key('Breakpoints.smallMobile')))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.mediumMobile')))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf( + tester.element(find.byKey(const Key('Breakpoints.largeMobile')))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'activeBreakpointOf returns correct active breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.smallDesktop')))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.mediumDesktop')))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.largeDesktop')))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + }); + + group('Slot And Up Layout Tests', () { + testWidgets('slotAndUp shows correct slot for small layout', + (WidgetTester tester) async { + // Small layout should only show the small slot. + await tester.pumpWidget(SimulatedLayout.small.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets('slotAndUp shows correct slot for medium layout and up', + (WidgetTester tester) async { + // Medium layout should show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.medium.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // MediumLarge layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.mediumLarge.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // Large layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.large.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // ExtraLarge layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.extraLarge.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + }); + + testWidgets('slotAndUp shows correct slot for small landscape layout', + (WidgetTester tester) async { + // Small landscape layout should only show the small slot. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for medium and larger landscape layouts', + (WidgetTester tester) async { + // Medium landscape layout should show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.mediumLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // MediumLarge landscape layout should also show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // Large landscape layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // ExtraLarge landscape layout should also show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.extraLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + }); + }); } class DummyWidget extends StatelessWidget { diff --git a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart index 8c8b6ce52518..a378ad1b6a36 100644 --- a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart +++ b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart @@ -108,15 +108,24 @@ enum SimulatedLayout { medium(width: 800, navSlotKey: 'primaryNavigation'), mediumLarge(width: 1000, navSlotKey: 'primaryNavigation1'), large(width: 1200, navSlotKey: 'primaryNavigation2'), - extraLarge(width: 1600, navSlotKey: 'primaryNavigation3'); + extraLarge(width: 1600, navSlotKey: 'primaryNavigation3'), + smallLandscape(width: 500, height: 400, navSlotKey: 'bottomNavigation'), + mediumLandscape(width: 800, height: 600, navSlotKey: 'primaryNavigation'), + mediumLargeLandscape( + width: 1100, height: 900, navSlotKey: 'primaryNavigation1'), + largeLandscape(width: 1400, height: 1000, navSlotKey: 'primaryNavigation2'), + extraLargeLandscape( + width: 1700, height: 1000, navSlotKey: 'primaryNavigation3'); const SimulatedLayout({ required double width, + double height = 2000, required this.navSlotKey, - }) : _width = width; + }) : _width = width, + _height = height; final double _width; - final double _height = 2000; + final double _height; final String navSlotKey; static const Color navigationRailThemeBgColor = Colors.white; @@ -234,4 +243,29 @@ enum SimulatedLayout { ), ); } + + MediaQuery slotAndUp(WidgetTester tester) { + return MediaQuery( + data: MediaQueryData.fromView(tester.view) + .copyWith(size: Size(_width, _height)), + child: Theme( + data: ThemeData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('Breakpoints.small'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.mediumAndUp: SlotLayout.from( + key: const Key('Breakpoints.mediumAndUp'), + builder: (BuildContext context) => Container(), + ), + }, + ), + ), + ), + ); + } } From c3c884118ddffefebb66f9cbe883247f2c353b00 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Fri, 16 Aug 2024 14:46:42 +0200 Subject: [PATCH 2/8] Changelog --- packages/flutter_adaptive_scaffold/CHANGELOG.md | 4 ++++ packages/flutter_adaptive_scaffold/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 8b4782b0d345..13661bf52e06 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+1 + +* Fix a bug where landscape would not show body when using `andUp`. + ## 0.2.1 * Add `Breakpoint.activeBreakpointOf(context)` to find the currently active breakpoint. diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index a4b7215af985..f25724a5326d 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.2.1 +version: 0.2.1+1 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 From 221dce5d2ee553e950a1885eae2a44ad948a085f Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Sat, 17 Aug 2024 11:57:15 +0200 Subject: [PATCH 3/8] Test --- .../flutter_adaptive_scaffold/CHANGELOG.md | 2 +- .../lib/src/breakpoints.dart | 114 ++++++++---------- .../lib/src/slot_layout.dart | 25 +--- .../flutter_adaptive_scaffold/pubspec.yaml | 2 +- .../test/breakpoint_test.dart | 42 +++++++ .../test/simulated_layout.dart | 10 +- 6 files changed, 108 insertions(+), 87 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 13661bf52e06..9646801d5336 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.1+1 +## 0.2.2 * Fix a bug where landscape would not show body when using `andUp`. diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 8457252fded4..e51338f2866f 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -86,6 +86,30 @@ class Breakpoints { /// A mobile window whose width is greater than 1600 dp. static const Breakpoint extraLargeMobile = Breakpoint.extraLarge(platform: Breakpoint.mobile); + + /// A list of all the standard breakpoints. + static const List all = [ + smallDesktop, + smallMobile, + small, + mediumDesktop, + mediumMobile, + medium, + mediumLargeDesktop, + mediumLargeMobile, + mediumLarge, + largeDesktop, + largeMobile, + large, + extraLargeDesktop, + extraLargeMobile, + extraLarge, + smallAndUp, + mediumAndUp, + mediumLargeAndUp, + largeAndUp, + standard, + ]; } /// A class to define the conditions that distinguish between types of @@ -113,8 +137,8 @@ class Breakpoint { this.endWidth, this.beginHeight, this.endHeight, - this.platform, this.andUp = false, + this.platform, }); /// Returns a [Breakpoint] that can be used as a fallthrough in the @@ -234,73 +258,15 @@ class Breakpoint { static Breakpoint? maybeActiveBreakpointFromSlotLayout(BuildContext context) { final SlotLayout? slotLayout = context.findAncestorWidgetOfExactType(); - Breakpoint? fallbackBreakpoint; - - if (slotLayout != null) { - for (final MapEntry config - in slotLayout.config.entries) { - if (config.key.isActive(context)) { - if (config.key.platform != null) { - return config.key; - } else { - fallbackBreakpoint ??= config.key; - } - } - } - } - return fallbackBreakpoint; + + return slotLayout != null + ? activeBreakpointIn(context, slotLayout.config.keys.toList()) + : null; } /// Returns the default [Breakpoint] based on the [BuildContext]. static Breakpoint defaultBreakpointOf(BuildContext context) { - final TargetPlatform host = Theme.of(context).platform; - final bool isDesktop = Breakpoint.desktop.contains(host); - final bool isMobile = Breakpoint.mobile.contains(host); - - for (final Breakpoint breakpoint in [ - Breakpoints.small, - Breakpoints.medium, - Breakpoints.mediumLarge, - Breakpoints.large, - Breakpoints.extraLarge, - ]) { - if (breakpoint.isActive(context)) { - if (isDesktop) { - switch (breakpoint) { - case Breakpoints.small: - return Breakpoints.smallDesktop; - case Breakpoints.medium: - return Breakpoints.mediumDesktop; - case Breakpoints.mediumLarge: - return Breakpoints.mediumLargeDesktop; - case Breakpoints.large: - return Breakpoints.largeDesktop; - case Breakpoints.extraLarge: - return Breakpoints.extraLargeDesktop; - default: - return Breakpoints.standard; - } - } else if (isMobile) { - switch (breakpoint) { - case Breakpoints.small: - return Breakpoints.smallMobile; - case Breakpoints.medium: - return Breakpoints.mediumMobile; - case Breakpoints.mediumLarge: - return Breakpoints.mediumLargeMobile; - case Breakpoints.large: - return Breakpoints.largeMobile; - case Breakpoints.extraLarge: - return Breakpoints.extraLargeMobile; - default: - return Breakpoints.standard; - } - } else { - return breakpoint; - } - } - } - return Breakpoints.standard; + return activeBreakpointIn(context, Breakpoints.all) ?? Breakpoints.standard; } /// Returns the currently active [Breakpoint]. @@ -308,4 +274,24 @@ class Breakpoint { return maybeActiveBreakpointFromSlotLayout(context) ?? defaultBreakpointOf(context); } + + /// Returns the currently active [Breakpoint] based on the [BuildContext] and + /// a list of [Breakpoint]s. + static Breakpoint? activeBreakpointIn( + BuildContext context, List breakpoints) { + Breakpoint? currentBreakpoint; + + for (final Breakpoint breakpoint in breakpoints) { + if (breakpoint.isActive(context)) { + if (breakpoint.platform != null) { + // Prioritize platform-specific breakpoints. + return breakpoint; + } else { + // Fallback to non-platform-specific. + currentBreakpoint = breakpoint; + } + } + } + return currentBreakpoint; + } } diff --git a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart index 07cfea33a9d6..e2909792c273 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart @@ -19,26 +19,11 @@ class SlotLayout extends StatefulWidget { /// be chosen from the config under the context's conditions. static SlotLayoutConfig? pickWidget( BuildContext context, Map config) { - SlotLayoutConfig? chosenWidget; - - for (final Breakpoint breakpoint in config.keys) { - if (breakpoint.isActive(context)) { - final SlotLayoutConfig? pickedWidget = config[breakpoint]; - if (pickedWidget != null) { - if (breakpoint.platform != null) { - // Prioritize platform-specific breakpoints. - return pickedWidget; - } else { - // Fallback to non-platform-specific. - chosenWidget = pickedWidget; - } - } else { - chosenWidget = null; - } - } - } - - return chosenWidget; + final Breakpoint? breakpoint = + Breakpoint.activeBreakpointIn(context, config.keys.toList()); + return breakpoint != null && config.containsKey(breakpoint) + ? config[breakpoint] + : null; } /// Maps [Breakpoint]s to [SlotLayoutConfig]s to determine what Widget to diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index f25724a5326d..ad18e0cf9cc9 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.2.1+1 +version: 0.2.2 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/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index 32d39798a9e7..4b27275bbebf 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -508,6 +508,48 @@ void main() { }, variant: TargetPlatformVariant.desktop()); }); + group('Portrait and Landscape Mixed Layout Tests', () { + testWidgets( + 'Layout for smallPortraitMediumLandscape shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallLandscapeMediumPortrait shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumPortrait.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallPortraitMediumLargeLandscape shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallLandscapeMediumLargePortrait shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumLargePortrait.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + }); + }); + group('Slot And Up Layout Tests', () { testWidgets('slotAndUp shows correct slot for small layout', (WidgetTester tester) async { diff --git a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart index a378ad1b6a36..46a7d33f2b88 100644 --- a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart +++ b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart @@ -115,7 +115,15 @@ enum SimulatedLayout { width: 1100, height: 900, navSlotKey: 'primaryNavigation1'), largeLandscape(width: 1400, height: 1000, navSlotKey: 'primaryNavigation2'), extraLargeLandscape( - width: 1700, height: 1000, navSlotKey: 'primaryNavigation3'); + width: 1700, height: 1000, navSlotKey: 'primaryNavigation3'), + smallPortraitMediumLandscape( + width: 360, height: 650, navSlotKey: 'bottomNavigation'), + smallLandscapeMediumPortrait( + width: 650, height: 360, navSlotKey: 'bottomNavigation'), + smallPortraitMediumLargeLandscape( + width: 360, height: 900, navSlotKey: 'bottomNavigation'), + smallLandscapeMediumLargePortrait( + width: 900, height: 360, navSlotKey: 'bottomNavigation'); const SimulatedLayout({ required double width, From ea3234a5e8ecb13a186dc5a65e612695830b4fb6 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Sat, 17 Aug 2024 13:09:40 +0200 Subject: [PATCH 4/8] Fix tests --- .../lib/src/breakpoints.dart | 3 +- .../test/breakpoint_test.dart | 51 ++++++++++++++++++- .../test/simulated_layout.dart | 2 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index e51338f2866f..ba10e4ecc04d 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -247,7 +247,8 @@ class Breakpoint { final bool isHeightActive = isDesktop || orientation == Orientation.portrait || (orientation == Orientation.landscape && andUp - ? height >= lowerBoundHeight + ? isWidthActive && height <= lowerBoundHeight || + height >= lowerBoundHeight : height >= lowerBoundHeight && height < upperBoundHeight); return isWidthActive && isHeightActive && isRightPlatform; diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index 4b27275bbebf..9cd3d6ba674f 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -525,7 +525,7 @@ void main() { await tester.pumpWidget( SimulatedLayout.smallLandscapeMediumPortrait.slot(tester)); await tester.pumpAndSettle(); - expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); }); @@ -545,7 +545,7 @@ void main() { await tester.pumpWidget( SimulatedLayout.smallLandscapeMediumLargePortrait.slot(tester)); await tester.pumpAndSettle(); - expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); }); }); @@ -627,6 +627,53 @@ void main() { expect(find.byKey(const Key('Breakpoints.small')), findsNothing); }); }); + + group('Slot And Up Layout Tests with Portrait and Landscape Mixed Layout', + () { + testWidgets( + 'slotAndUp shows correct slot for smallPortraitMediumLandscape layout', + (WidgetTester tester) async { + // smallPortraitMediumLandscape layout should only show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallLandscapeMediumPortrait layout', + (WidgetTester tester) async { + // smallLandscapeMediumPortrait layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumPortrait.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallPortraitMediumLargeLandscape layout', + (WidgetTester tester) async { + // smallPortraitMediumLargeLandscape layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallLandscapeMediumLargePortrait layout', + (WidgetTester tester) async { + // smallLandscapeMediumLargePortrait layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumLargePortrait.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + }); + }); } class DummyWidget extends StatelessWidget { diff --git a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart index 46a7d33f2b88..a72014192283 100644 --- a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart +++ b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart @@ -123,7 +123,7 @@ enum SimulatedLayout { smallPortraitMediumLargeLandscape( width: 360, height: 900, navSlotKey: 'bottomNavigation'), smallLandscapeMediumLargePortrait( - width: 900, height: 360, navSlotKey: 'bottomNavigation'); + width: 900, height: 360, navSlotKey: 'primaryNavigation'); const SimulatedLayout({ required double width, From b5644bb09e85e3f9c2b712391c90fea8fa6c1492 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Tue, 20 Aug 2024 19:11:39 +0200 Subject: [PATCH 5/8] Fix --- .../flutter_adaptive_scaffold/test/breakpoint_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index 9cd3d6ba674f..88a8569136c6 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -342,7 +342,7 @@ void main() { find.byKey(const Key('Breakpoints.extraLargeMobile')), findsNothing); }, variant: TargetPlatformVariant.desktop()); - // Additional landscape tests for `maybeActiveBreakpointFromSlotLayout` + // Additional landscape tests for `maybeActiveBreakpointFromSlotLayout`. testWidgets( 'maybeActiveBreakpointFromSlotLayout returns correct breakpoint on mobile (landscape)', (WidgetTester tester) async { @@ -399,7 +399,7 @@ void main() { Breakpoints.largeDesktop); }, variant: TargetPlatformVariant.desktop()); - // Additional landscape tests for `defaultBreakpointOf` + // Additional landscape tests for `defaultBreakpointOf`. testWidgets( 'defaultBreakpointOf returns correct default breakpoint on mobile (landscape)', (WidgetTester tester) async { @@ -450,7 +450,7 @@ void main() { Breakpoints.largeDesktop); }, variant: TargetPlatformVariant.desktop()); - // Additional landscape tests for `activeBreakpointOf` + // Additional landscape tests for `activeBreakpointOf`. testWidgets( 'activeBreakpointOf returns correct active breakpoint on mobile (landscape)', (WidgetTester tester) async { From b1fc28566b8913580f213c0b82fb2923a830a490 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Tue, 20 Aug 2024 19:13:37 +0200 Subject: [PATCH 6/8] Fix --- packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index ba10e4ecc04d..b891cb2a26df 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -247,8 +247,7 @@ class Breakpoint { final bool isHeightActive = isDesktop || orientation == Orientation.portrait || (orientation == Orientation.landscape && andUp - ? isWidthActive && height <= lowerBoundHeight || - height >= lowerBoundHeight + ? isWidthActive || height >= lowerBoundHeight : height >= lowerBoundHeight && height < upperBoundHeight); return isWidthActive && isHeightActive && isRightPlatform; From 94dc2068319e33032be4484f84eb950ee85558ec Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Wed, 21 Aug 2024 00:27:17 +0200 Subject: [PATCH 7/8] Update breakpoints.dart --- packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index b891cb2a26df..0a27f97a3430 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -199,7 +199,7 @@ class Breakpoint { TargetPlatform.iOS, }; - /// When set to true, it will include any size above the set width. + /// When set to true, it will include any size above the set width and any height above the set height. final bool andUp; /// The beginning width dp value. If left null then the [Breakpoint] will have From 44ea363f4b9f7d43175cb809e013fe4d166ab566 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Wed, 21 Aug 2024 01:16:44 +0200 Subject: [PATCH 8/8] Update breakpoints.dart --- packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 0a27f97a3430..c752222746f8 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -199,7 +199,7 @@ class Breakpoint { TargetPlatform.iOS, }; - /// When set to true, it will include any size above the set width and any height above the set height. + /// When set to true, it will include any size above the set width and set height. final bool andUp; /// The beginning width dp value. If left null then the [Breakpoint] will have