Skip to content

Commit acb6a29

Browse files
authored
[flutter_adaptive_scaffold] Add breakpoint extension to get the current active breakpoint (flutter#7347)
This enables people to check what the current breakpoint is and adjust UI based on that. *List which issues are fixed by this PR. You must list at least one issue.*
1 parent 4fb1db0 commit acb6a29

File tree

6 files changed

+281
-14
lines changed

6 files changed

+281
-14
lines changed

packages/flutter_adaptive_scaffold/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.2.1
2+
3+
* Add `Breakpoint.activeBreakpointOf(context)` to find the currently active breakpoint.
4+
* Don't check for height on Desktop platforms.
5+
16
## 0.2.0
27

38
* Add breakpoints for mediumLarge and extraLarge.

packages/flutter_adaptive_scaffold/example/test/main_test.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ void main() {
3838
await tester.pumpAndSettle();
3939
}
4040

41-
testWidgets('dislays correct item of config based on screen width',
41+
testWidgets('displays correct item of config based on screen width',
4242
(WidgetTester tester) async {
4343
await updateScreen(300, tester);
4444
expect(smallBody, findsOneWidget);
45-
expect(sBody, findsNothing);
45+
expect(body, findsNothing);
4646
expect(mediumLargeBody, findsNothing);
4747
expect(largeBody, findsNothing);
4848
expect(extraLargeBody, findsNothing);
@@ -60,7 +60,7 @@ void main() {
6060

6161
await updateScreen(800, tester);
6262
expect(smallBody, findsNothing);
63-
expect(sBody, findsOneWidget);
63+
expect(body, findsOneWidget);
6464
expect(mediumLargeBody, findsNothing);
6565
expect(largeBody, findsNothing);
6666
expect(extraLargeBody, findsNothing);
@@ -78,7 +78,7 @@ void main() {
7878

7979
await updateScreen(1100, tester);
8080
expect(smallBody, findsNothing);
81-
expect(sBody, findsNothing);
81+
expect(body, findsNothing);
8282
expect(mediumLargeBody, findsOneWidget);
8383
expect(largeBody, findsNothing);
8484
expect(extraLargeBody, findsNothing);
@@ -96,7 +96,7 @@ void main() {
9696

9797
await updateScreen(1400, tester);
9898
expect(smallBody, findsNothing);
99-
expect(sBody, findsNothing);
99+
expect(body, findsNothing);
100100
expect(mediumLargeBody, findsNothing);
101101
expect(largeBody, findsOneWidget);
102102
expect(extraLargeBody, findsNothing);
@@ -114,7 +114,7 @@ void main() {
114114

115115
await updateScreen(1800, tester);
116116
expect(smallBody, findsNothing);
117-
expect(sBody, findsNothing);
117+
expect(body, findsNothing);
118118
expect(mediumLargeBody, findsNothing);
119119
expect(largeBody, findsNothing);
120120
expect(extraLargeBody, findsOneWidget);

packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'package:flutter/material.dart';
66

7+
import '../flutter_adaptive_scaffold.dart';
8+
79
/// A group of standard breakpoints built according to the material
810
/// specifications for screen width size.
911
///
@@ -193,6 +195,7 @@ class Breakpoint {
193195
bool isActive(BuildContext context) {
194196
final TargetPlatform host = Theme.of(context).platform;
195197
final bool isRightPlatform = platform?.contains(host) ?? true;
198+
final bool isDesktop = Breakpoint.desktop.contains(host);
196199

197200
final double width = MediaQuery.sizeOf(context).width;
198201
final double height = MediaQuery.sizeOf(context).height;
@@ -208,11 +211,92 @@ class Breakpoint {
208211
? width >= lowerBoundWidth
209212
: width >= lowerBoundWidth && width < upperBoundWidth;
210213

211-
final bool isHeightActive = (orientation == Orientation.landscape &&
214+
final bool isHeightActive = isDesktop ||
215+
orientation == Orientation.portrait ||
216+
(orientation == Orientation.landscape &&
212217
height >= lowerBoundHeight &&
213-
height < upperBoundHeight) ||
214-
orientation == Orientation.portrait;
218+
height < upperBoundHeight);
215219

216220
return isWidthActive && isHeightActive && isRightPlatform;
217221
}
222+
223+
/// Returns the currently active [Breakpoint] based on the [SlotLayout] in the
224+
/// context.
225+
static Breakpoint? maybeActiveBreakpointFromSlotLayout(BuildContext context) {
226+
final SlotLayout? slotLayout =
227+
context.findAncestorWidgetOfExactType<SlotLayout>();
228+
Breakpoint? fallbackBreakpoint;
229+
230+
if (slotLayout != null) {
231+
for (final MapEntry<Breakpoint, SlotLayoutConfig?> config
232+
in slotLayout.config.entries) {
233+
if (config.key.isActive(context)) {
234+
if (config.key.platform != null) {
235+
return config.key;
236+
} else {
237+
fallbackBreakpoint ??= config.key;
238+
}
239+
}
240+
}
241+
}
242+
return fallbackBreakpoint;
243+
}
244+
245+
/// Returns the default [Breakpoint] based on the [BuildContext].
246+
static Breakpoint defaultBreakpointOf(BuildContext context) {
247+
final TargetPlatform host = Theme.of(context).platform;
248+
final bool isDesktop = Breakpoint.desktop.contains(host);
249+
final bool isMobile = Breakpoint.mobile.contains(host);
250+
251+
for (final Breakpoint breakpoint in <Breakpoint>[
252+
Breakpoints.small,
253+
Breakpoints.medium,
254+
Breakpoints.mediumLarge,
255+
Breakpoints.large,
256+
Breakpoints.extraLarge,
257+
]) {
258+
if (breakpoint.isActive(context)) {
259+
if (isDesktop) {
260+
switch (breakpoint) {
261+
case Breakpoints.small:
262+
return Breakpoints.smallDesktop;
263+
case Breakpoints.medium:
264+
return Breakpoints.mediumDesktop;
265+
case Breakpoints.mediumLarge:
266+
return Breakpoints.mediumLargeDesktop;
267+
case Breakpoints.large:
268+
return Breakpoints.largeDesktop;
269+
case Breakpoints.extraLarge:
270+
return Breakpoints.extraLargeDesktop;
271+
default:
272+
return Breakpoints.standard;
273+
}
274+
} else if (isMobile) {
275+
switch (breakpoint) {
276+
case Breakpoints.small:
277+
return Breakpoints.smallMobile;
278+
case Breakpoints.medium:
279+
return Breakpoints.mediumMobile;
280+
case Breakpoints.mediumLarge:
281+
return Breakpoints.mediumLargeMobile;
282+
case Breakpoints.large:
283+
return Breakpoints.largeMobile;
284+
case Breakpoints.extraLarge:
285+
return Breakpoints.extraLargeMobile;
286+
default:
287+
return Breakpoints.standard;
288+
}
289+
} else {
290+
return breakpoint;
291+
}
292+
}
293+
}
294+
return Breakpoints.standard;
295+
}
296+
297+
/// Returns the currently active [Breakpoint].
298+
static Breakpoint activeBreakpointOf(BuildContext context) {
299+
return maybeActiveBreakpointFromSlotLayout(context) ??
300+
defaultBreakpointOf(context);
301+
}
218302
}

packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,29 @@ class SlotLayout extends StatefulWidget {
1515
/// Creates a [SlotLayout] widget.
1616
const SlotLayout({required this.config, super.key});
1717

18-
/// Given a context and a config, it returns the [SlotLayoutConfig] that will g
18+
/// Given a context and a config, it returns the [SlotLayoutConfig] that will
1919
/// be chosen from the config under the context's conditions.
2020
static SlotLayoutConfig? pickWidget(
2121
BuildContext context, Map<Breakpoint, SlotLayoutConfig?> config) {
2222
SlotLayoutConfig? chosenWidget;
23-
config.forEach((Breakpoint breakpoint, SlotLayoutConfig? pickedWidget) {
23+
24+
for (final Breakpoint breakpoint in config.keys) {
2425
if (breakpoint.isActive(context)) {
25-
chosenWidget = pickedWidget;
26+
final SlotLayoutConfig? pickedWidget = config[breakpoint];
27+
if (pickedWidget != null) {
28+
if (breakpoint.platform != null) {
29+
// Prioritize platform-specific breakpoints.
30+
return pickedWidget;
31+
} else {
32+
// Fallback to non-platform-specific.
33+
chosenWidget = pickedWidget;
34+
}
35+
} else {
36+
chosenWidget = null;
37+
}
2638
}
27-
});
39+
}
40+
2841
return chosenWidget;
2942
}
3043

packages/flutter_adaptive_scaffold/pubspec.yaml

Lines changed: 1 addition & 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.0
3+
version: 0.2.1
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

packages/flutter_adaptive_scaffold/test/breakpoint_test.dart

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,171 @@ void main() {
9999
await tester.pumpAndSettle();
100100
expect(DummyWidget.built, isFalse);
101101
});
102+
103+
// Test the `maybeActiveBreakpointFromSlotLayout` method.
104+
group('maybeActiveBreakpointFromSlotLayout', () {
105+
testWidgets('returns correct breakpoint from SlotLayout on mobile devices',
106+
(WidgetTester tester) async {
107+
// Small layout on mobile.
108+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
109+
await tester.pumpAndSettle();
110+
expect(
111+
Breakpoint.maybeActiveBreakpointFromSlotLayout(
112+
tester.element(find.byKey(const Key('Breakpoints.smallMobile')))),
113+
Breakpoints.smallMobile);
114+
115+
// Medium layout on mobile.
116+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
117+
await tester.pumpAndSettle();
118+
expect(
119+
Breakpoint.maybeActiveBreakpointFromSlotLayout(tester
120+
.element(find.byKey(const Key('Breakpoints.mediumMobile')))),
121+
Breakpoints.mediumMobile);
122+
123+
// Large layout on mobile.
124+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
125+
await tester.pumpAndSettle();
126+
expect(
127+
Breakpoint.maybeActiveBreakpointFromSlotLayout(
128+
tester.element(find.byKey(const Key('Breakpoints.largeMobile')))),
129+
Breakpoints.largeMobile);
130+
}, variant: TargetPlatformVariant.mobile());
131+
132+
testWidgets('returns correct breakpoint from SlotLayout on desktop devices',
133+
(WidgetTester tester) async {
134+
// Small layout on desktop.
135+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
136+
await tester.pumpAndSettle();
137+
expect(
138+
Breakpoint.maybeActiveBreakpointFromSlotLayout(tester
139+
.element(find.byKey(const Key('Breakpoints.smallDesktop')))),
140+
Breakpoints.smallDesktop);
141+
142+
// Medium layout on desktop.
143+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
144+
await tester.pumpAndSettle();
145+
expect(
146+
Breakpoint.maybeActiveBreakpointFromSlotLayout(tester
147+
.element(find.byKey(const Key('Breakpoints.mediumDesktop')))),
148+
Breakpoints.mediumDesktop);
149+
150+
// Large layout on desktop.
151+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
152+
await tester.pumpAndSettle();
153+
expect(
154+
Breakpoint.maybeActiveBreakpointFromSlotLayout(tester
155+
.element(find.byKey(const Key('Breakpoints.largeDesktop')))),
156+
Breakpoints.largeDesktop);
157+
}, variant: TargetPlatformVariant.desktop());
158+
});
159+
160+
// Test the `defaultBreakpointOf` method.
161+
group('defaultBreakpointOf', () {
162+
testWidgets('returns correct default breakpoint on mobile devices',
163+
(WidgetTester tester) async {
164+
// Small layout on mobile.
165+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
166+
await tester.pumpAndSettle();
167+
expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))),
168+
Breakpoints.smallMobile);
169+
170+
// Medium layout on mobile.
171+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
172+
await tester.pumpAndSettle();
173+
expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))),
174+
Breakpoints.mediumMobile);
175+
176+
// Large layout on mobile.
177+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
178+
await tester.pumpAndSettle();
179+
expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))),
180+
Breakpoints.largeMobile);
181+
}, variant: TargetPlatformVariant.mobile());
182+
183+
testWidgets('returns correct default breakpoint on desktop devices',
184+
(WidgetTester tester) async {
185+
// Small layout on desktop.
186+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
187+
await tester.pumpAndSettle();
188+
expect(
189+
Breakpoint.defaultBreakpointOf(
190+
tester.element(find.byType(Directionality))),
191+
Breakpoints.smallDesktop);
192+
193+
// Medium layout on desktop.
194+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
195+
await tester.pumpAndSettle();
196+
expect(
197+
Breakpoint.defaultBreakpointOf(
198+
tester.element(find.byType(Directionality))),
199+
Breakpoints.mediumDesktop);
200+
201+
// Large layout on desktop.
202+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
203+
await tester.pumpAndSettle();
204+
expect(
205+
Breakpoint.defaultBreakpointOf(
206+
tester.element(find.byType(Directionality))),
207+
Breakpoints.largeDesktop);
208+
}, variant: TargetPlatformVariant.desktop());
209+
});
210+
211+
// Test the `activeBreakpointOf` method.
212+
group('activeBreakpointOf', () {
213+
testWidgets('returns correct active breakpoint on mobile devices',
214+
(WidgetTester tester) async {
215+
// Small layout on mobile.
216+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
217+
await tester.pumpAndSettle();
218+
expect(
219+
Breakpoint.activeBreakpointOf(
220+
tester.element(find.byKey(const Key('Breakpoints.smallMobile')))),
221+
Breakpoints.smallMobile);
222+
223+
// Medium layout on mobile.
224+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
225+
await tester.pumpAndSettle();
226+
expect(
227+
Breakpoint.activeBreakpointOf(tester
228+
.element(find.byKey(const Key('Breakpoints.mediumMobile')))),
229+
Breakpoints.mediumMobile);
230+
231+
// Large layout on mobile.
232+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
233+
await tester.pumpAndSettle();
234+
expect(
235+
Breakpoint.activeBreakpointOf(
236+
tester.element(find.byKey(const Key('Breakpoints.largeMobile')))),
237+
Breakpoints.largeMobile);
238+
}, variant: TargetPlatformVariant.mobile());
239+
240+
testWidgets('returns correct active breakpoint on desktop devices',
241+
(WidgetTester tester) async {
242+
// Small layout on desktop.
243+
await tester.pumpWidget(SimulatedLayout.small.slot(tester));
244+
await tester.pumpAndSettle();
245+
expect(
246+
Breakpoint.activeBreakpointOf(tester
247+
.element(find.byKey(const Key('Breakpoints.smallDesktop')))),
248+
Breakpoints.smallDesktop);
249+
250+
// Medium layout on desktop.
251+
await tester.pumpWidget(SimulatedLayout.medium.slot(tester));
252+
await tester.pumpAndSettle();
253+
expect(
254+
Breakpoint.activeBreakpointOf(tester
255+
.element(find.byKey(const Key('Breakpoints.mediumDesktop')))),
256+
Breakpoints.mediumDesktop);
257+
258+
// Large layout on desktop.
259+
await tester.pumpWidget(SimulatedLayout.large.slot(tester));
260+
await tester.pumpAndSettle();
261+
expect(
262+
Breakpoint.activeBreakpointOf(tester
263+
.element(find.byKey(const Key('Breakpoints.largeDesktop')))),
264+
Breakpoints.largeDesktop);
265+
}, variant: TargetPlatformVariant.desktop());
266+
});
102267
}
103268

104269
class DummyWidget extends StatelessWidget {

0 commit comments

Comments
 (0)