11import 'dart:math' ;
22
33import 'package:collection/collection.dart' show IterableExtension;
4+ import 'package:flutter/material.dart' ;
45import 'package:flutter/widgets.dart' ;
56
67import 'utils/responsive_utils.dart' ;
@@ -50,6 +51,23 @@ import 'utils/responsive_utils.dart';
5051class 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