-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add responsive layout builder and gap widget
- Loading branch information
1 parent
6acb577
commit f6842e0
Showing
10 changed files
with
393 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/// Defines the breakpoints for the Sudoku app UI. | ||
abstract class SudokuBreakpoint { | ||
/// Max width for a small layout. | ||
static const double small = 742; | ||
|
||
/// Max width for a medium layout. | ||
static const double medium = 1280; | ||
|
||
/// Max width for a large layout. | ||
static const double large = 1440; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export 'breakpoints.dart'; | ||
export 'responsive_gap.dart'; | ||
export 'responsive_layout_builder.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:gap/gap.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
|
||
/// {@template responsive_gap} | ||
/// A wrapper around [Gap] that renders a [small], [medium] | ||
/// or a [large] gap depending on the screen size. | ||
/// {@endtemplate} | ||
class ResponsiveGap extends StatelessWidget { | ||
/// {@macro responsive_gap} | ||
const ResponsiveGap({ | ||
this.small = 0, | ||
this.medium = 0, | ||
this.large = 0, | ||
super.key, | ||
}); | ||
|
||
/// A gap rendered on a small layout. | ||
final double small; | ||
|
||
/// A gap rendered on a medium layout. | ||
final double medium; | ||
|
||
/// A gap rendered on a large layout. | ||
final double large; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ResponsiveLayoutBuilder( | ||
small: (_, __) => Gap(small), | ||
medium: (_, __) => Gap(medium), | ||
large: (_, __) => Gap(large), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
|
||
/// Represents the layout size passed to [ResponsiveLayoutBuilder.child]. | ||
enum ResponsiveLayoutSize { | ||
/// Small layout | ||
small, | ||
|
||
/// Medium layout | ||
medium, | ||
|
||
/// Large layout | ||
large, | ||
} | ||
|
||
/// Signature for the individual builders (`small`, `medium`, `large`). | ||
typedef ResponsiveLayoutWidgetBuilder = Widget Function(BuildContext, Widget?); | ||
|
||
/// {@template responsive_layout_builder} | ||
/// A wrapper around [LayoutBuilder] which exposes builders | ||
/// for various responsive breakpoints. | ||
/// {@endtemplate} | ||
class ResponsiveLayoutBuilder extends StatelessWidget { | ||
/// {@macro responsive_layout_builder} | ||
const ResponsiveLayoutBuilder({ | ||
required this.small, | ||
required this.medium, | ||
required this.large, | ||
this.child, | ||
super.key, | ||
}); | ||
|
||
/// [ResponsiveLayoutWidgetBuilder] for small layout. | ||
final ResponsiveLayoutWidgetBuilder small; | ||
|
||
/// [ResponsiveLayoutWidgetBuilder] for medium layout. | ||
final ResponsiveLayoutWidgetBuilder medium; | ||
|
||
/// [ResponsiveLayoutWidgetBuilder] for large layout. | ||
final ResponsiveLayoutWidgetBuilder large; | ||
|
||
/// Optional child widget builder based on the current layout size | ||
/// which will be passed to the `small`, `medium` and `large` builders | ||
/// as a way to share/optimize shared layout. | ||
final Widget Function(ResponsiveLayoutSize currentSize)? child; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return LayoutBuilder( | ||
builder: (context, constraints) { | ||
final screenWidth = MediaQuery.of(context).size.width; | ||
|
||
if (screenWidth <= SudokuBreakpoint.small) { | ||
return small(context, child?.call(ResponsiveLayoutSize.small)); | ||
} | ||
if (screenWidth <= SudokuBreakpoint.medium) { | ||
return medium(context, child?.call(ResponsiveLayoutSize.medium)); | ||
} | ||
if (screenWidth <= SudokuBreakpoint.large) { | ||
return large(context, child?.call(ResponsiveLayoutSize.large)); | ||
} | ||
|
||
return large(context, child?.call(ResponsiveLayoutSize.large)); | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export 'pump_app.dart'; | ||
export 'set_display_size.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
|
||
extension ResponsiveWidgetTester on WidgetTester { | ||
void setDisplaySize(Size size) { | ||
view | ||
..physicalSize = size | ||
..devicePixelRatio = 1.0; | ||
addTearDown(() { | ||
view | ||
..resetPhysicalSize() | ||
..resetDevicePixelRatio(); | ||
}); | ||
} | ||
|
||
void setLargeDisplaySize() { | ||
setDisplaySize(const Size(SudokuBreakpoint.large, 1000)); | ||
} | ||
|
||
void setMediumDisplaySize() { | ||
setDisplaySize(const Size(SudokuBreakpoint.medium, 1000)); | ||
} | ||
|
||
void setSmallDisplaySize() { | ||
setDisplaySize(const Size(SudokuBreakpoint.small, 1000)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// ignore_for_file: prefer_const_constructors | ||
|
||
import 'package:flutter/widgets.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:gap/gap.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
|
||
import '../helpers/helpers.dart'; | ||
|
||
void main() { | ||
group('ResponsiveGap', () { | ||
const smallGap = 10.0; | ||
const mediumGap = 15.0; | ||
const largeGap = 20.0; | ||
|
||
late ResponsiveGap widget; | ||
|
||
setUp(() { | ||
widget = ResponsiveGap( | ||
small: smallGap, | ||
medium: mediumGap, | ||
large: largeGap, | ||
); | ||
}); | ||
|
||
testWidgets('renders a large gap on a large display', (tester) async { | ||
tester.setLargeDisplaySize(); | ||
await tester.pumpApp( | ||
SingleChildScrollView(child: widget), | ||
); | ||
|
||
expect( | ||
find.byWidgetPredicate( | ||
(widget) => widget is Gap && widget.mainAxisExtent == largeGap, | ||
), | ||
findsOneWidget, | ||
); | ||
}); | ||
|
||
testWidgets('renders a medium gap on a medium display', (tester) async { | ||
tester.setMediumDisplaySize(); | ||
await tester.pumpApp( | ||
SingleChildScrollView(child: widget), | ||
); | ||
|
||
expect( | ||
find.byWidgetPredicate( | ||
(widget) => widget is Gap && widget.mainAxisExtent == mediumGap, | ||
), | ||
findsOneWidget, | ||
); | ||
}); | ||
|
||
testWidgets('renders a small gap on a small display', (tester) async { | ||
tester.setSmallDisplaySize(); | ||
await tester.pumpApp( | ||
SingleChildScrollView(child: widget), | ||
); | ||
|
||
expect( | ||
find.byWidgetPredicate( | ||
(widget) => widget is Gap && widget.mainAxisExtent == smallGap, | ||
), | ||
findsOneWidget, | ||
); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.