Skip to content

Commit 848d7e9

Browse files
authored
[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.*
1 parent b835465 commit 848d7e9

File tree

5 files changed

+270
-8
lines changed

5 files changed

+270
-8
lines changed

packages/flutter_adaptive_scaffold/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.2.4
2+
3+
* Compare breakpoints to each other using operators.
4+
15
## 0.2.3
26

37
* Update the spacing and margins to the latest material m3 specs.

packages/flutter_adaptive_scaffold/README.md

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
<?code-excerpt path-base="example/lib"?>
2-
31
# Adaptive Scaffold
42

53
`AdaptiveScaffold` reacts to input from users, devices and screen elements and
@@ -33,7 +31,7 @@ animation should use `AdaptiveLayout`.
3331

3432
### Example Usage
3533

36-
<?code-excerpt "adaptive_scaffold_demo.dart (Example)"?>
34+
<?code-excerpt "example/lib/adaptive_scaffold_demo.dart (Example)"?>
3735
```dart
3836
@override
3937
Widget build(BuildContext context) {
@@ -128,6 +126,126 @@ Widget build(BuildContext context) {
128126
These are the set of widgets that are used on a lower level and offer more
129127
customizability at a cost of more lines of code.
130128

129+
### Breakpoint
130+
131+
A `Breakpoint` controls the responsive behavior at different screens and configurations.
132+
133+
You can either use a predefined Material3 breakpoint or create your own.
134+
135+
<?code-excerpt "lib/src/breakpoints.dart (Breakpoints)"?>
136+
```dart
137+
/// Returns a const [Breakpoint] with the given constraints.
138+
const Breakpoint({
139+
this.beginWidth,
140+
this.endWidth,
141+
this.beginHeight,
142+
this.endHeight,
143+
this.andUp = false,
144+
this.platform,
145+
this.spacing = kMaterialMediumAndUpSpacing,
146+
this.margin = kMaterialMediumAndUpMargin,
147+
this.padding = kMaterialPadding,
148+
this.recommendedPanes = 1,
149+
this.maxPanes = 1,
150+
});
151+
152+
/// Returns a [Breakpoint] that can be used as a fallthrough in the
153+
/// case that no other breakpoint is active.
154+
const Breakpoint.standard({this.platform})
155+
: beginWidth = -1,
156+
endWidth = null,
157+
beginHeight = null,
158+
endHeight = null,
159+
spacing = kMaterialMediumAndUpSpacing,
160+
margin = kMaterialMediumAndUpMargin,
161+
padding = kMaterialPadding,
162+
recommendedPanes = 1,
163+
maxPanes = 1,
164+
andUp = true;
165+
166+
/// Returns a [Breakpoint] with the given constraints for a small screen.
167+
const Breakpoint.small({this.andUp = false, this.platform})
168+
: beginWidth = 0,
169+
endWidth = 600,
170+
beginHeight = null,
171+
endHeight = 480,
172+
spacing = kMaterialCompactSpacing,
173+
margin = kMaterialCompactMargin,
174+
padding = kMaterialPadding,
175+
recommendedPanes = 1,
176+
maxPanes = 1;
177+
178+
/// Returns a [Breakpoint] with the given constraints for a medium screen.
179+
const Breakpoint.medium({this.andUp = false, this.platform})
180+
: beginWidth = 600,
181+
endWidth = 840,
182+
beginHeight = 480,
183+
endHeight = 900,
184+
spacing = kMaterialMediumAndUpSpacing,
185+
margin = kMaterialMediumAndUpMargin,
186+
padding = kMaterialPadding * 2,
187+
recommendedPanes = 1,
188+
maxPanes = 2;
189+
190+
/// Returns a [Breakpoint] with the given constraints for a mediumLarge screen.
191+
const Breakpoint.mediumLarge({this.andUp = false, this.platform})
192+
: beginWidth = 840,
193+
endWidth = 1200,
194+
beginHeight = 900,
195+
endHeight = null,
196+
spacing = kMaterialMediumAndUpSpacing,
197+
margin = kMaterialMediumAndUpMargin,
198+
padding = kMaterialPadding * 3,
199+
recommendedPanes = 2,
200+
maxPanes = 2;
201+
202+
/// Returns a [Breakpoint] with the given constraints for a large screen.
203+
const Breakpoint.large({this.andUp = false, this.platform})
204+
: beginWidth = 1200,
205+
endWidth = 1600,
206+
beginHeight = 900,
207+
endHeight = null,
208+
spacing = kMaterialMediumAndUpSpacing,
209+
margin = kMaterialMediumAndUpMargin,
210+
padding = kMaterialPadding * 4,
211+
recommendedPanes = 2,
212+
maxPanes = 2;
213+
214+
/// Returns a [Breakpoint] with the given constraints for an extraLarge screen.
215+
const Breakpoint.extraLarge({this.andUp = false, this.platform})
216+
: beginWidth = 1600,
217+
endWidth = null,
218+
beginHeight = 900,
219+
endHeight = null,
220+
spacing = kMaterialMediumAndUpSpacing,
221+
margin = kMaterialMediumAndUpMargin,
222+
padding = kMaterialPadding * 5,
223+
recommendedPanes = 2,
224+
maxPanes = 3;
225+
```
226+
227+
It is possible to compare Breakpoints:
228+
229+
<?code-excerpt "lib/src/breakpoints.dart (Breakpoint operators)"?>
230+
```dart
231+
/// Returns true if this [Breakpoint] is greater than the given [Breakpoint].
232+
bool operator >(Breakpoint breakpoint)
233+
// ···
234+
/// Returns true if this [Breakpoint] is less than the given [Breakpoint].
235+
bool operator <(Breakpoint breakpoint)
236+
// ···
237+
/// Returns true if this [Breakpoint] is greater than or equal to the
238+
/// given [Breakpoint].
239+
bool operator >=(Breakpoint breakpoint)
240+
// ···
241+
/// Returns true if this [Breakpoint] is less than or equal to the
242+
/// given [Breakpoint].
243+
bool operator <=(Breakpoint breakpoint)
244+
// ···
245+
/// Returns true if this [Breakpoint] is between the given [Breakpoint]s.
246+
bool between(Breakpoint lower, Breakpoint upper)
247+
```
248+
131249
### AdaptiveLayout
132250

133251
!["AdaptiveLayout's Assigned Slots Displayed on Screen"](example/demo_files/screenSlots.png)
@@ -151,7 +269,7 @@ displayed and the entrance animation and exit animation.
151269

152270
### Example Usage
153271

154-
<?code-excerpt "adaptive_layout_demo.dart (Example)"?>
272+
<?code-excerpt "example/lib/adaptive_layout_demo.dart (Example)"?>
155273
```dart
156274
// AdaptiveLayout has a number of slots that take SlotLayouts and these
157275
// SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.

packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class Breakpoints {
131131
/// * [SlotLayout.config], which uses breakpoints to dictate the layout of the
132132
/// screen.
133133
class Breakpoint {
134+
// #docregion Breakpoints
134135
/// Returns a const [Breakpoint] with the given constraints.
135136
const Breakpoint({
136137
this.beginWidth,
@@ -219,6 +220,7 @@ class Breakpoint {
219220
padding = kMaterialPadding * 5,
220221
recommendedPanes = 2,
221222
maxPanes = 3;
223+
// #enddocregion Breakpoints
222224

223225
/// A set of [TargetPlatform]s that the [Breakpoint] will be active on desktop.
224226
static const Set<TargetPlatform> desktop = <TargetPlatform>{
@@ -278,7 +280,6 @@ class Breakpoint {
278280
bool isActive(BuildContext context) {
279281
final TargetPlatform host = Theme.of(context).platform;
280282
final bool isRightPlatform = platform?.contains(host) ?? true;
281-
final bool isDesktop = Breakpoint.desktop.contains(host);
282283

283284
final double width = MediaQuery.sizeOf(context).width;
284285
final double height = MediaQuery.sizeOf(context).height;
@@ -294,7 +295,7 @@ class Breakpoint {
294295
? width >= lowerBoundWidth
295296
: width >= lowerBoundWidth && width < upperBoundWidth;
296297

297-
final bool isHeightActive = isDesktop ||
298+
final bool isHeightActive = isDesktop(context) ||
298299
orientation == Orientation.portrait ||
299300
(orientation == Orientation.landscape && andUp
300301
? isWidthActive || height >= lowerBoundHeight
@@ -344,4 +345,84 @@ class Breakpoint {
344345
}
345346
return currentBreakpoint;
346347
}
348+
349+
/// Returns true if the current platform is Desktop.
350+
static bool isDesktop(BuildContext context) {
351+
return Breakpoint.desktop.contains(Theme.of(context).platform);
352+
}
353+
354+
/// Returns true if the current platform is Mobile.
355+
static bool isMobile(BuildContext context) {
356+
return Breakpoint.mobile.contains(Theme.of(context).platform);
357+
}
358+
359+
// #docregion Breakpoint operators
360+
/// Returns true if this [Breakpoint] is greater than the given [Breakpoint].
361+
bool operator >(Breakpoint breakpoint)
362+
// #enddocregion Breakpoint operators
363+
{
364+
return (beginWidth ?? double.negativeInfinity) >
365+
(breakpoint.beginWidth ?? double.negativeInfinity) &&
366+
(endWidth ?? double.infinity) >
367+
(breakpoint.endWidth ?? double.infinity) &&
368+
(beginHeight ?? double.negativeInfinity) >
369+
(breakpoint.beginHeight ?? double.negativeInfinity) &&
370+
(endHeight ?? double.infinity) >
371+
(breakpoint.endHeight ?? double.infinity);
372+
}
373+
374+
// #docregion Breakpoint operators
375+
/// Returns true if this [Breakpoint] is less than the given [Breakpoint].
376+
bool operator <(Breakpoint breakpoint)
377+
// #enddocregion Breakpoint operators
378+
{
379+
return (endWidth ?? double.infinity) <
380+
(breakpoint.endWidth ?? double.infinity) &&
381+
(beginWidth ?? double.negativeInfinity) <
382+
(breakpoint.beginWidth ?? double.negativeInfinity) &&
383+
(endHeight ?? double.infinity) <
384+
(breakpoint.endHeight ?? double.infinity) &&
385+
(beginHeight ?? double.negativeInfinity) <
386+
(breakpoint.beginHeight ?? double.negativeInfinity);
387+
}
388+
389+
// #docregion Breakpoint operators
390+
/// Returns true if this [Breakpoint] is greater than or equal to the
391+
/// given [Breakpoint].
392+
bool operator >=(Breakpoint breakpoint)
393+
// #enddocregion Breakpoint operators
394+
{
395+
return (beginWidth ?? double.negativeInfinity) >=
396+
(breakpoint.beginWidth ?? double.negativeInfinity) &&
397+
(endWidth ?? double.infinity) >=
398+
(breakpoint.endWidth ?? double.infinity) &&
399+
(beginHeight ?? double.negativeInfinity) >=
400+
(breakpoint.beginHeight ?? double.negativeInfinity) &&
401+
(endHeight ?? double.infinity) >=
402+
(breakpoint.endHeight ?? double.infinity);
403+
}
404+
405+
// #docregion Breakpoint operators
406+
/// Returns true if this [Breakpoint] is less than or equal to the
407+
/// given [Breakpoint].
408+
bool operator <=(Breakpoint breakpoint)
409+
// #enddocregion Breakpoint operators
410+
{
411+
return (endWidth ?? double.infinity) <=
412+
(breakpoint.endWidth ?? double.infinity) &&
413+
(beginWidth ?? double.negativeInfinity) <=
414+
(breakpoint.beginWidth ?? double.negativeInfinity) &&
415+
(endHeight ?? double.infinity) <=
416+
(breakpoint.endHeight ?? double.infinity) &&
417+
(beginHeight ?? double.negativeInfinity) <=
418+
(breakpoint.beginHeight ?? double.negativeInfinity);
419+
}
420+
421+
// #docregion Breakpoint operators
422+
/// Returns true if this [Breakpoint] is between the given [Breakpoint]s.
423+
bool between(Breakpoint lower, Breakpoint upper)
424+
// #enddocregion Breakpoint operators
425+
{
426+
return this >= lower && this < upper;
427+
}
347428
}

packages/flutter_adaptive_scaffold/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_adaptive_scaffold
22
description: Widgets to easily build adaptive layouts, including navigation elements.
3-
version: 0.2.3
3+
version: 0.2.4
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
55
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
66

@@ -20,3 +20,4 @@ topics:
2020
- layout
2121
- ui
2222
- adaptive
23+
- responsive

packages/flutter_adaptive_scaffold/test/breakpoint_test.dart

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ void main() {
686686
.element(find.byKey(const Key('Breakpoints.smallMobile'))))
687687
.spacing,
688688
kMaterialCompactSpacing);
689-
}, variant: TargetPlatformVariant.mobile());
689+
});
690690

691691
testWidgets('returns kMaterialMediumAndUpSpacing for medium breakpoint',
692692
(WidgetTester tester) async {
@@ -959,6 +959,64 @@ void main() {
959959
3);
960960
}, variant: TargetPlatformVariant.mobile());
961961
});
962+
963+
group('Breakpoint method tests', () {
964+
testWidgets('isMobile returns true on mobile platforms',
965+
(WidgetTester tester) async {
966+
await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester));
967+
await tester.pumpAndSettle();
968+
969+
expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))),
970+
isTrue);
971+
972+
expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))),
973+
isFalse);
974+
}, variant: TargetPlatformVariant.mobile());
975+
976+
testWidgets('isDesktop returns true on desktop platforms',
977+
(WidgetTester tester) async {
978+
await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester));
979+
await tester.pumpAndSettle();
980+
981+
expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))),
982+
isTrue);
983+
984+
expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))),
985+
isFalse);
986+
}, variant: TargetPlatformVariant.desktop());
987+
988+
test('Breakpoint comparison operators work correctly', () {
989+
const Breakpoint small = Breakpoints.small;
990+
const Breakpoint medium = Breakpoints.medium;
991+
const Breakpoint large = Breakpoints.large;
992+
993+
expect(small < medium, isTrue);
994+
expect(large > medium, isTrue);
995+
expect(small <= Breakpoints.small, isTrue);
996+
expect(large >= medium, isTrue);
997+
});
998+
999+
test('Breakpoint equality and hashCode', () {
1000+
const Breakpoint small1 = Breakpoints.small;
1001+
const Breakpoint small2 = Breakpoints.small;
1002+
const Breakpoint medium = Breakpoints.medium;
1003+
1004+
expect(small1 == small2, isTrue);
1005+
expect(small1 == medium, isFalse);
1006+
expect(small1.hashCode == small2.hashCode, isTrue);
1007+
expect(small1.hashCode == medium.hashCode, isFalse);
1008+
});
1009+
1010+
test('Breakpoint between method works correctly', () {
1011+
const Breakpoint small = Breakpoints.small;
1012+
const Breakpoint medium = Breakpoints.medium;
1013+
const Breakpoint large = Breakpoints.large;
1014+
1015+
expect(medium.between(small, large), isTrue);
1016+
expect(small.between(medium, large), isFalse);
1017+
expect(large.between(small, medium), isFalse);
1018+
});
1019+
});
9621020
}
9631021

9641022
class DummyWidget extends StatelessWidget {

0 commit comments

Comments
 (0)