From 848d7e915e2110984bdb6140e044c232ece00c3a Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Sat, 31 Aug 2024 00:51:10 +0200 Subject: [PATCH] [flutter_adaptive_scaffold] Compare breakpoints (#7531) *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* Sometimes you want to compare a breakpoint to a baseline or another breakpoint to determine which UI to show or how to handle something. This adds operators so you can check those things. ```dart Breakpoint.activeBreakpointOf(context) > Breakpoints.large; ``` *List which issues are fixed by this PR. You must list at least one issue.* --- .../flutter_adaptive_scaffold/CHANGELOG.md | 4 + packages/flutter_adaptive_scaffold/README.md | 126 +++++++++++++++++- .../lib/src/breakpoints.dart | 85 +++++++++++- .../flutter_adaptive_scaffold/pubspec.yaml | 3 +- .../test/breakpoint_test.dart | 60 ++++++++- 5 files changed, 270 insertions(+), 8 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 90d1fc95556f4..b716f19066eea 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.4 + +* Compare breakpoints to each other using operators. + ## 0.2.3 * Update the spacing and margins to the latest material m3 specs. diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md index 77b0a11c480d6..45fa01a241c8e 100644 --- a/packages/flutter_adaptive_scaffold/README.md +++ b/packages/flutter_adaptive_scaffold/README.md @@ -1,5 +1,3 @@ - - # Adaptive Scaffold `AdaptiveScaffold` reacts to input from users, devices and screen elements and @@ -33,7 +31,7 @@ animation should use `AdaptiveLayout`. ### Example Usage - + ```dart @override Widget build(BuildContext context) { @@ -128,6 +126,126 @@ Widget build(BuildContext context) { These are the set of widgets that are used on a lower level and offer more customizability at a cost of more lines of code. +### Breakpoint + +A `Breakpoint` controls the responsive behavior at different screens and configurations. + +You can either use a predefined Material3 breakpoint or create your own. + + +```dart +/// Returns a const [Breakpoint] with the given constraints. +const Breakpoint({ + this.beginWidth, + this.endWidth, + this.beginHeight, + this.endHeight, + this.andUp = false, + this.platform, + this.spacing = kMaterialMediumAndUpSpacing, + this.margin = kMaterialMediumAndUpMargin, + this.padding = kMaterialPadding, + this.recommendedPanes = 1, + this.maxPanes = 1, +}); + +/// 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, + spacing = kMaterialMediumAndUpSpacing, + margin = kMaterialMediumAndUpMargin, + padding = kMaterialPadding, + recommendedPanes = 1, + maxPanes = 1, + andUp = true; + +/// 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, + spacing = kMaterialCompactSpacing, + margin = kMaterialCompactMargin, + padding = kMaterialPadding, + recommendedPanes = 1, + maxPanes = 1; + +/// 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, + spacing = kMaterialMediumAndUpSpacing, + margin = kMaterialMediumAndUpMargin, + padding = kMaterialPadding * 2, + recommendedPanes = 1, + maxPanes = 2; + +/// 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, + spacing = kMaterialMediumAndUpSpacing, + margin = kMaterialMediumAndUpMargin, + padding = kMaterialPadding * 3, + recommendedPanes = 2, + maxPanes = 2; + +/// 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, + spacing = kMaterialMediumAndUpSpacing, + margin = kMaterialMediumAndUpMargin, + padding = kMaterialPadding * 4, + recommendedPanes = 2, + maxPanes = 2; + +/// 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, + spacing = kMaterialMediumAndUpSpacing, + margin = kMaterialMediumAndUpMargin, + padding = kMaterialPadding * 5, + recommendedPanes = 2, + maxPanes = 3; +``` + +It is possible to compare Breakpoints: + + +```dart +/// Returns true if this [Breakpoint] is greater than the given [Breakpoint]. +bool operator >(Breakpoint breakpoint) +// ··· +/// Returns true if this [Breakpoint] is less than the given [Breakpoint]. +bool operator <(Breakpoint breakpoint) +// ··· +/// Returns true if this [Breakpoint] is greater than or equal to the +/// given [Breakpoint]. +bool operator >=(Breakpoint breakpoint) +// ··· +/// Returns true if this [Breakpoint] is less than or equal to the +/// given [Breakpoint]. +bool operator <=(Breakpoint breakpoint) +// ··· +/// Returns true if this [Breakpoint] is between the given [Breakpoint]s. +bool between(Breakpoint lower, Breakpoint upper) +``` + ### AdaptiveLayout !["AdaptiveLayout's Assigned Slots Displayed on Screen"](example/demo_files/screenSlots.png) @@ -151,7 +269,7 @@ displayed and the entrance animation and exit animation. ### Example Usage - + ```dart // AdaptiveLayout has a number of slots that take SlotLayouts and these // SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs. diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 0ca452a8098be..a8493f7901c95 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -131,6 +131,7 @@ class Breakpoints { /// * [SlotLayout.config], which uses breakpoints to dictate the layout of the /// screen. class Breakpoint { + // #docregion Breakpoints /// Returns a const [Breakpoint] with the given constraints. const Breakpoint({ this.beginWidth, @@ -219,6 +220,7 @@ class Breakpoint { padding = kMaterialPadding * 5, recommendedPanes = 2, maxPanes = 3; + // #enddocregion Breakpoints /// A set of [TargetPlatform]s that the [Breakpoint] will be active on desktop. static const Set desktop = { @@ -278,7 +280,6 @@ class Breakpoint { bool isActive(BuildContext context) { final TargetPlatform host = Theme.of(context).platform; final bool isRightPlatform = platform?.contains(host) ?? true; - final bool isDesktop = Breakpoint.desktop.contains(host); final double width = MediaQuery.sizeOf(context).width; final double height = MediaQuery.sizeOf(context).height; @@ -294,7 +295,7 @@ class Breakpoint { ? width >= lowerBoundWidth : width >= lowerBoundWidth && width < upperBoundWidth; - final bool isHeightActive = isDesktop || + final bool isHeightActive = isDesktop(context) || orientation == Orientation.portrait || (orientation == Orientation.landscape && andUp ? isWidthActive || height >= lowerBoundHeight @@ -344,4 +345,84 @@ class Breakpoint { } return currentBreakpoint; } + + /// Returns true if the current platform is Desktop. + static bool isDesktop(BuildContext context) { + return Breakpoint.desktop.contains(Theme.of(context).platform); + } + + /// Returns true if the current platform is Mobile. + static bool isMobile(BuildContext context) { + return Breakpoint.mobile.contains(Theme.of(context).platform); + } + + // #docregion Breakpoint operators + /// Returns true if this [Breakpoint] is greater than the given [Breakpoint]. + bool operator >(Breakpoint breakpoint) + // #enddocregion Breakpoint operators + { + return (beginWidth ?? double.negativeInfinity) > + (breakpoint.beginWidth ?? double.negativeInfinity) && + (endWidth ?? double.infinity) > + (breakpoint.endWidth ?? double.infinity) && + (beginHeight ?? double.negativeInfinity) > + (breakpoint.beginHeight ?? double.negativeInfinity) && + (endHeight ?? double.infinity) > + (breakpoint.endHeight ?? double.infinity); + } + + // #docregion Breakpoint operators + /// Returns true if this [Breakpoint] is less than the given [Breakpoint]. + bool operator <(Breakpoint breakpoint) + // #enddocregion Breakpoint operators + { + return (endWidth ?? double.infinity) < + (breakpoint.endWidth ?? double.infinity) && + (beginWidth ?? double.negativeInfinity) < + (breakpoint.beginWidth ?? double.negativeInfinity) && + (endHeight ?? double.infinity) < + (breakpoint.endHeight ?? double.infinity) && + (beginHeight ?? double.negativeInfinity) < + (breakpoint.beginHeight ?? double.negativeInfinity); + } + + // #docregion Breakpoint operators + /// Returns true if this [Breakpoint] is greater than or equal to the + /// given [Breakpoint]. + bool operator >=(Breakpoint breakpoint) + // #enddocregion Breakpoint operators + { + return (beginWidth ?? double.negativeInfinity) >= + (breakpoint.beginWidth ?? double.negativeInfinity) && + (endWidth ?? double.infinity) >= + (breakpoint.endWidth ?? double.infinity) && + (beginHeight ?? double.negativeInfinity) >= + (breakpoint.beginHeight ?? double.negativeInfinity) && + (endHeight ?? double.infinity) >= + (breakpoint.endHeight ?? double.infinity); + } + + // #docregion Breakpoint operators + /// Returns true if this [Breakpoint] is less than or equal to the + /// given [Breakpoint]. + bool operator <=(Breakpoint breakpoint) + // #enddocregion Breakpoint operators + { + return (endWidth ?? double.infinity) <= + (breakpoint.endWidth ?? double.infinity) && + (beginWidth ?? double.negativeInfinity) <= + (breakpoint.beginWidth ?? double.negativeInfinity) && + (endHeight ?? double.infinity) <= + (breakpoint.endHeight ?? double.infinity) && + (beginHeight ?? double.negativeInfinity) <= + (breakpoint.beginHeight ?? double.negativeInfinity); + } + + // #docregion Breakpoint operators + /// Returns true if this [Breakpoint] is between the given [Breakpoint]s. + bool between(Breakpoint lower, Breakpoint upper) + // #enddocregion Breakpoint operators + { + return this >= lower && this < upper; + } } diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index 5800621d2b031..e811357628c1e 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.3 +version: 0.2.4 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 @@ -20,3 +20,4 @@ topics: - layout - ui - adaptive + - responsive diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index 61bcb5e643ed8..d3ab31fbdb7e9 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -686,7 +686,7 @@ void main() { .element(find.byKey(const Key('Breakpoints.smallMobile')))) .spacing, kMaterialCompactSpacing); - }, variant: TargetPlatformVariant.mobile()); + }); testWidgets('returns kMaterialMediumAndUpSpacing for medium breakpoint', (WidgetTester tester) async { @@ -959,6 +959,64 @@ void main() { 3); }, variant: TargetPlatformVariant.mobile()); }); + + group('Breakpoint method tests', () { + testWidgets('isMobile returns true on mobile platforms', + (WidgetTester tester) async { + await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester)); + await tester.pumpAndSettle(); + + expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))), + isTrue); + + expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))), + isFalse); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets('isDesktop returns true on desktop platforms', + (WidgetTester tester) async { + await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester)); + await tester.pumpAndSettle(); + + expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))), + isTrue); + + expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))), + isFalse); + }, variant: TargetPlatformVariant.desktop()); + + test('Breakpoint comparison operators work correctly', () { + const Breakpoint small = Breakpoints.small; + const Breakpoint medium = Breakpoints.medium; + const Breakpoint large = Breakpoints.large; + + expect(small < medium, isTrue); + expect(large > medium, isTrue); + expect(small <= Breakpoints.small, isTrue); + expect(large >= medium, isTrue); + }); + + test('Breakpoint equality and hashCode', () { + const Breakpoint small1 = Breakpoints.small; + const Breakpoint small2 = Breakpoints.small; + const Breakpoint medium = Breakpoints.medium; + + expect(small1 == small2, isTrue); + expect(small1 == medium, isFalse); + expect(small1.hashCode == small2.hashCode, isTrue); + expect(small1.hashCode == medium.hashCode, isFalse); + }); + + test('Breakpoint between method works correctly', () { + const Breakpoint small = Breakpoints.small; + const Breakpoint medium = Breakpoints.medium; + const Breakpoint large = Breakpoints.large; + + expect(medium.between(small, large), isTrue); + expect(small.between(medium, large), isFalse); + expect(large.between(small, medium), isFalse); + }); + }); } class DummyWidget extends StatelessWidget {