Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion lib/responsive_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ class ResponsiveWrapper extends StatefulWidget {
final bool defaultScale;
final double defaultScaleFactor;

/// Calculate responsiveness based on the shortest
/// side of the screen, instead of the actual
/// landscape orientation.
///
/// This is useful for apps that want to avoid
/// scrolling screens and distribute their content
/// based on width/height regardless of orientation.
/// Size units can remain the same when the phone
/// is in landscape mode or portrait mode.
/// The developer needs only change a few widgets'
/// hard-coded size depending on the orientation.
/// The rest of the widgets maintain their size but
/// change the way they are displayed.
///
/// `useShortestSide` can be used in conjunction with
/// [breakpointsLandscape] for additional configurability.
/// Landscape breakpoints will activate when the
/// physical device is in landscape mode but base
/// calculations on the shortest side instead of
/// the actual landscape width.
final bool useShortestSide;

/// Landscape minWidth value. Defaults to [minWidth] if not set.
final double? minWidthLandscape;

Expand Down Expand Up @@ -135,6 +157,7 @@ class ResponsiveWrapper extends StatefulWidget {
this.mediaQueryData,
this.shrinkWrap = true,
this.alignment = Alignment.topCenter,
this.useShortestSide = false,
this.debugLog = false,
}) : super(key: key);

Expand All @@ -152,6 +175,7 @@ class ResponsiveWrapper extends StatefulWidget {
bool defaultScale = false,
double defaultScaleFactor = 1,
double? minWidthLandscape,
bool useShortestSide = false,
double? maxWidthLandscape,
String? defaultNameLandscape,
bool? defaultScaleLandscape,
Expand All @@ -170,6 +194,7 @@ class ResponsiveWrapper extends StatefulWidget {
minWidth: minWidth,
maxWidth: maxWidth,
defaultName: defaultName,
useShortestSide: useShortestSide,
defaultScale: defaultScale,
defaultScaleFactor: defaultScaleFactor,
minWidthLandscape: minWidthLandscape,
Expand Down Expand Up @@ -230,7 +255,10 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
/// Get screen width calculation.
double screenWidth = 0;
double getScreenWidth() {
activeBreakpointSegment = getActiveBreakpointSegment(windowWidth);
double widthCalc = useShortestSide
? (windowWidth < windowHeight ? windowWidth : windowHeight)
: windowWidth;
activeBreakpointSegment = getActiveBreakpointSegment(widthCalc);
// Special 0 width condition.
if (activeBreakpointSegment.responsiveBreakpoint.breakpoint == 0) return 0;
// Check if screenWidth exceeds maxWidth.
Expand Down Expand Up @@ -490,6 +518,7 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
double get defaultScaleFactor => isLandscape
? (widget.defaultScaleFactorLandscape ?? widget.defaultScaleFactor)
: widget.defaultScaleFactor;
bool get useShortestSide => widget.useShortestSide;

/// Calculate updated dimensions.
void setDimensions() {
Expand Down
203 changes: 203 additions & 0 deletions test/responsive_wrapper_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1156,4 +1156,207 @@ void main() {
debugDefaultTargetPlatformOverride = null;
});
});

group('useShortestSide Behaviour', () {
// Test that useShortestSide is disabled by default
testWidgets('useShortestSide Disabled', (WidgetTester tester) async {
// Width is greater than height.
setScreenSize(tester, Size(1200, 800));

// Set target platform to Android.
debugDefaultTargetPlatformOverride = TargetPlatform.android;

Key key = UniqueKey();
Widget widget = Builder(
builder: (context) {
return MaterialApp(
home: ResponsiveWrapper(
key: key,
breakpoints: [],
breakpointsLandscape: [],
minWidth: 400,
minWidthLandscape: 800,
maxWidth: 1200,
maxWidthLandscape: 2560,
child: Container(),
shrinkWrap: false,
),
);
},
);
await tester.pumpWidget(widget);
await tester.pump();
dynamic state = tester.state(find.byKey(key));
// Test Landscape
expect(state.useShortestSide, false);
expect(state.minWidth, 800);
expect(state.maxWidth, 2560);
// Change to Portrait
resetScreenSize(tester);
setScreenSize(tester, Size(600, 1200));
await tester.pump();
// Test Portrait
expect(state.useShortestSide, false);
expect(state.minWidth, 400);
expect(state.maxWidth, 1200);
// Unset global to avoid crash.
debugDefaultTargetPlatformOverride = null;
});

// Test that useShortestSide is disabled by default
testWidgets('useShortestSide + Breakpoints', (WidgetTester tester) async {
// Height is greater than width.
setScreenSize(tester, Size(400, 800));

// Set target platform to Android.
debugDefaultTargetPlatformOverride = TargetPlatform.android;

List<ResponsiveBreakpoint> breakpoints = [
ResponsiveBreakpoint.autoScale(350, scaleFactor: 1),
ResponsiveBreakpoint.autoScale(600, scaleFactor: 2),
ResponsiveBreakpoint.autoScale(750, scaleFactor: 3),
];

Key key = UniqueKey();
Widget widget = Builder(
builder: (context) {
return MaterialApp(
home: ResponsiveWrapper(
key: key,
breakpoints: breakpoints,
minWidth: 100,
useShortestSide: true,
maxWidth: 400,
child: Container(),
shrinkWrap: false,
),
);
},
);
await tester.pumpWidget(widget);
await tester.pump();
dynamic state = tester.state(find.byKey(key));
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 1);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(800, 400));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 1);
// Make screen biggger
resetScreenSize(tester);
setScreenSize(tester, Size(650, 900));
await tester.pump();
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 2);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(900, 650));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 2);
// Make screen biggger
resetScreenSize(tester);
setScreenSize(tester, Size(800, 1200));
await tester.pump();
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 3);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(1200, 800));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 3);
// Unset global to avoid crash.
debugDefaultTargetPlatformOverride = null;
});
testWidgets('useShortestSide + Breakpoints + breakpointsLandscape',
(WidgetTester tester) async {
// Height is greater than width.
setScreenSize(tester, Size(400, 800));

// Set target platform to Android.
debugDefaultTargetPlatformOverride = TargetPlatform.android;

List<ResponsiveBreakpoint> breakpoints = [
ResponsiveBreakpoint.autoScale(350, scaleFactor: 1),
ResponsiveBreakpoint.autoScale(600, scaleFactor: 2),
ResponsiveBreakpoint.autoScale(750, scaleFactor: 3),
];
// Over exagerated to note the difference
List<ResponsiveBreakpoint> breakpointsLandscape = [
ResponsiveBreakpoint.autoScale(350, scaleFactor: 6),
ResponsiveBreakpoint.autoScale(600, scaleFactor: 7),
ResponsiveBreakpoint.autoScale(750, scaleFactor: 8),
];

Key key = UniqueKey();
Widget widget = Builder(
builder: (context) {
return MaterialApp(
home: ResponsiveWrapper(
key: key,
breakpoints: breakpoints,
breakpointsLandscape: breakpointsLandscape,
minWidth: 100,
useShortestSide: true,
maxWidth: 400,
child: Container(),
shrinkWrap: false,
),
);
},
);
await tester.pumpWidget(widget);
await tester.pump();
dynamic state = tester.state(find.byKey(key));
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 1);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(800, 400));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 6);
// Make screen biggger
resetScreenSize(tester);
setScreenSize(tester, Size(650, 900));
await tester.pump();
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 2);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(900, 650));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 7);
// Make screen biggger
resetScreenSize(tester);
setScreenSize(tester, Size(800, 1200));
await tester.pump();
// Test Portrait scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 3);
// Change to Landscape
resetScreenSize(tester);
setScreenSize(tester, Size(1200, 800));
await tester.pump();
// Test Landscape scaleFactor
expect(state.useShortestSide, true);
expect(state.activeBreakpointSegment.responsiveBreakpoint.scaleFactor, 8);
// Unset global to avoid crash.
debugDefaultTargetPlatformOverride = null;
});
});
}