Skip to content

Commit 2bfd7ad

Browse files
committed
Landscape Orientation Support #2
- Add landscape breakpoints to ResponsiveWrapper.
1 parent e45723c commit 2bfd7ad

File tree

1 file changed

+94
-16
lines changed

1 file changed

+94
-16
lines changed

lib/responsive_wrapper.dart

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:math';
22

33
import 'package:collection/collection.dart' show IterableExtension;
4+
import 'package:flutter/material.dart';
45
import 'package:flutter/widgets.dart';
56

67
import 'utils/responsive_utils.dart';
@@ -50,6 +51,23 @@ import 'utils/responsive_utils.dart';
5051
class ResponsiveWrapper extends StatefulWidget {
5152
final Widget? child;
5253
final List<ResponsiveBreakpoint>? breakpoints;
54+
55+
/// A list of breakpoints that are active when the device is in landscape orientation.
56+
///
57+
/// In Flutter, the returned device orientation is not the real device orientation,
58+
/// but is calculated based on the screen width and height.
59+
/// This means that landscape only makes sense on devices that support
60+
/// orientation changes. By default, landscape breakpoints are only
61+
/// active when the [TargetPlatform] is Android, iOS, or Fuchsia.
62+
/// To enable landscape breakpoints on other platforms, pass a custom
63+
/// list of supported platforms to [landscapePlatforms].
64+
final List<ResponsiveBreakpoint>? landscapeBreakpoints;
65+
66+
/// Override list of platforms to enable landscape mode on.
67+
/// By default, only mobile platforms support landscape mode.
68+
/// This override exists primarily to enable custom landscape vs portrait behavior
69+
/// and future compatibility with Fuschia.
70+
final List<TargetPlatform>? landscapePlatforms;
5371
final double minWidth;
5472
final double? maxWidth;
5573
final String? defaultName;
@@ -84,6 +102,8 @@ class ResponsiveWrapper extends StatefulWidget {
84102
Key? key,
85103
required this.child,
86104
this.breakpoints,
105+
this.landscapeBreakpoints,
106+
this.landscapePlatforms,
87107
this.minWidth = 450,
88108
this.maxWidth,
89109
this.defaultName,
@@ -103,6 +123,8 @@ class ResponsiveWrapper extends StatefulWidget {
103123
static Widget builder(
104124
Widget? child, {
105125
List<ResponsiveBreakpoint>? breakpoints,
126+
List<ResponsiveBreakpoint>? landscapeBreakpoints,
127+
List<TargetPlatform>? landscapePlatforms,
106128
double minWidth = 450,
107129
double? maxWidth,
108130
String? defaultName,
@@ -117,6 +139,8 @@ class ResponsiveWrapper extends StatefulWidget {
117139
return ResponsiveWrapper(
118140
child: child,
119141
breakpoints: breakpoints,
142+
landscapeBreakpoints: landscapeBreakpoints,
143+
landscapePlatforms: landscapePlatforms,
120144
minWidth: minWidth,
121145
maxWidth: maxWidth,
122146
defaultName: defaultName,
@@ -169,14 +193,16 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
169193
MediaQuery.of(context).size.height;
170194
}
171195

196+
TargetPlatform get platform => Theme.of(context).platform;
197+
172198
late List<ResponsiveBreakpoint> breakpoints;
173199
late List<ResponsiveBreakpointSegment> breakpointSegments;
174200

175201
/// Get screen width calculation.
176202
double screenWidth = 0;
177203
double getScreenWidth() {
178-
// Special 0 width condition.
179204
activeBreakpointSegment = getActiveBreakpointSegment(windowWidth);
205+
// Special 0 width condition.
180206
if (activeBreakpointSegment.responsiveBreakpoint.breakpoint == 0) return 0;
181207
// Check if screenWidth exceeds maxWidth.
182208
if (widget.maxWidth != null && windowWidth > widget.maxWidth!) {
@@ -396,6 +422,20 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
396422
/// Default fullscreen enabled.
397423
get fullscreen => widget.maxWidth == null;
398424

425+
Orientation get orientation => (screenWidth > screenHeight)
426+
? Orientation.landscape
427+
: Orientation.portrait;
428+
429+
static const List<TargetPlatform> _landscapePlatforms = [
430+
TargetPlatform.iOS,
431+
TargetPlatform.android,
432+
TargetPlatform.fuchsia,
433+
];
434+
435+
bool get isLandscapePlatform =>
436+
(widget.landscapePlatforms ?? _landscapePlatforms)
437+
.contains(Theme.of(context).platform);
438+
399439
/// Calculate updated dimensions.
400440
void setDimensions() {
401441
devicePixelRatio = getDevicePixelRatio();
@@ -410,6 +450,32 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
410450
scaledPadding = getScaledPadding();
411451
}
412452

453+
/// Get enabled breakpoints based on [orientation] and [platform].
454+
List<ResponsiveBreakpoint> getActiveBreakpoints() {
455+
// If the device is landscape enabled and the current orientation is landscape, use landscape breakpoints.
456+
if (orientation == Orientation.landscape &&
457+
isLandscapePlatform &&
458+
widget.landscapeBreakpoints != null) {
459+
return widget.landscapeBreakpoints ?? [];
460+
}
461+
462+
return widget.breakpoints ?? [];
463+
}
464+
465+
/// Calculate [breakpointSegments] from [breakpoints].
466+
List<ResponsiveBreakpointSegment> calcBreakpointSegments(
467+
List<ResponsiveBreakpoint> breakpoints) {
468+
// Seed breakpoint based on config values.
469+
ResponsiveBreakpoint defaultBreakpoint = ResponsiveBreakpoint(
470+
breakpoint: widget.minWidth,
471+
name: widget.defaultName,
472+
behavior: widget.defaultScale
473+
? ResponsiveBreakpointBehavior.AUTOSCALE
474+
: ResponsiveBreakpointBehavior.RESIZE,
475+
scaleFactor: widget.defaultScaleFactor);
476+
return getBreakpointSegments(breakpoints, defaultBreakpoint);
477+
}
478+
413479
/// Set [activeBreakpointSegment].
414480
/// Active breakpoint segment is the first breakpoint segment
415481
/// smaller or equal to the [windowWidth].
@@ -420,27 +486,39 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
420486
return activeBreakpointSegment;
421487
}
422488

489+
/// Set [breakpoints] and [breakpointSegments].
490+
void setBreakpoints() {
491+
breakpoints = getActiveBreakpoints();
492+
breakpointSegments = calcBreakpointSegments(breakpoints);
493+
}
494+
423495
@override
424496
void initState() {
425497
super.initState();
426-
breakpoints = widget.breakpoints ?? [];
427-
ResponsiveBreakpoint defaultBreakpoint = ResponsiveBreakpoint(
428-
breakpoint: widget.minWidth,
429-
name: widget.defaultName,
430-
behavior: widget.defaultScale
431-
? ResponsiveBreakpointBehavior.AUTOSCALE
432-
: ResponsiveBreakpointBehavior.RESIZE,
433-
scaleFactor: widget.defaultScaleFactor);
434-
breakpointSegments = getBreakpointSegments(breakpoints, defaultBreakpoint);
498+
// Breakpoints must be initialized before the first frame is drawn.
499+
setBreakpoints();
435500

436501
// Log breakpoints to console.
437-
if (widget.debugLog)
438-
ResponsiveUtils.debugLogBreakpointSegments(breakpointSegments);
502+
if (widget.debugLog) {
503+
List<ResponsiveBreakpoint> defaultBreakpoints = widget.breakpoints ?? [];
504+
List<ResponsiveBreakpointSegment> defaultBreakpointSegments =
505+
calcBreakpointSegments(defaultBreakpoints);
506+
ResponsiveUtils.debugLogBreakpointSegments(defaultBreakpointSegments);
507+
// Print landscape breakpoints.
508+
if (widget.landscapeBreakpoints != null) {
509+
List<ResponsiveBreakpoint> landscapeBreakpoints =
510+
widget.landscapeBreakpoints ?? [];
511+
List<ResponsiveBreakpointSegment> landscapeBreakpointSegments =
512+
calcBreakpointSegments(landscapeBreakpoints);
513+
print('Landscape Breakpoints:');
514+
ResponsiveUtils.debugLogBreakpointSegments(landscapeBreakpointSegments);
515+
}
516+
}
439517

440518
// Dimensions are only available after first frame paint.
441519
WidgetsBinding.instance!.addObserver(this);
442520
WidgetsBinding.instance!.addPostFrameCallback((_) {
443-
// Updating dimensions is safe because frame callbacks
521+
// Directly updating dimensions is safe because frame callbacks
444522
// in initState are guaranteed.
445523
setDimensions();
446524
setState(() {});
@@ -463,6 +541,7 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
463541
// Widget could be destroyed by resize. Verify widget
464542
// exists before updating dimensions.
465543
if (mounted) {
544+
setBreakpoints();
466545
setDimensions();
467546
setState(() {});
468547
}
@@ -476,6 +555,7 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
476555
// used directly in the widget tree and a parent
477556
// MediaQueryData changes, update state.
478557
// The screen dimensions are passed immediately.
558+
setBreakpoints();
479559
setDimensions();
480560
setState(() {});
481561
}
@@ -611,9 +691,7 @@ class ResponsiveWrapperData {
611691
state.activeBreakpointSegment.responsiveBreakpoint.name == TABLET,
612692
isDesktop:
613693
state.activeBreakpointSegment.responsiveBreakpoint.name == DESKTOP,
614-
orientation: state.screenWidth > state.screenHeight
615-
? Orientation.portrait
616-
: Orientation.landscape,
694+
orientation: state.orientation,
617695
);
618696
}
619697

0 commit comments

Comments
 (0)