Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flutter_adaptive_scaffold] Compare breakpoints #7531

Merged
merged 12 commits into from
Aug 30, 2024
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
4 changes: 4 additions & 0 deletions packages/flutter_adaptive_scaffold/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.4

* Compare breakpoints to each other using operators.

## 0.2.3

* Update the spacing and margins to the latest material m3 specs.
Expand Down
126 changes: 122 additions & 4 deletions packages/flutter_adaptive_scaffold/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<?code-excerpt path-base="example/lib"?>

# Adaptive Scaffold

`AdaptiveScaffold` reacts to input from users, devices and screen elements and
Expand Down Expand Up @@ -33,7 +31,7 @@ animation should use `AdaptiveLayout`.

### Example Usage

<?code-excerpt "adaptive_scaffold_demo.dart (Example)"?>
<?code-excerpt "example/lib/adaptive_scaffold_demo.dart (Example)"?>
```dart
@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -128,6 +126,126 @@ Widget build(BuildContext context) {
These are the set of widgets that are used on a lower level and offer more
customizability at a cost of more lines of code.

### Breakpoint

A `Breakpoint` controls the responsive behavior at different screens and configurations.

You can either use a predefined Material3 breakpoint or create your own.
martijn00 marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/src/breakpoints.dart (Breakpoints)"?>
```dart
/// Returns a const [Breakpoint] with the given constraints.
const Breakpoint({
this.beginWidth,
this.endWidth,
this.beginHeight,
this.endHeight,
this.andUp = false,
this.platform,
this.spacing = kMaterialMediumAndUpSpacing,
this.margin = kMaterialMediumAndUpMargin,
this.padding = kMaterialPadding,
this.recommendedPanes = 1,
this.maxPanes = 1,
});

/// Returns a [Breakpoint] that can be used as a fallthrough in the
/// case that no other breakpoint is active.
const Breakpoint.standard({this.platform})
: beginWidth = -1,
endWidth = null,
beginHeight = null,
endHeight = null,
spacing = kMaterialMediumAndUpSpacing,
margin = kMaterialMediumAndUpMargin,
padding = kMaterialPadding,
recommendedPanes = 1,
maxPanes = 1,
andUp = true;

/// Returns a [Breakpoint] with the given constraints for a small screen.
const Breakpoint.small({this.andUp = false, this.platform})
: beginWidth = 0,
endWidth = 600,
beginHeight = null,
endHeight = 480,
spacing = kMaterialCompactSpacing,
margin = kMaterialCompactMargin,
padding = kMaterialPadding,
recommendedPanes = 1,
maxPanes = 1;

/// Returns a [Breakpoint] with the given constraints for a medium screen.
const Breakpoint.medium({this.andUp = false, this.platform})
: beginWidth = 600,
endWidth = 840,
beginHeight = 480,
endHeight = 900,
spacing = kMaterialMediumAndUpSpacing,
margin = kMaterialMediumAndUpMargin,
padding = kMaterialPadding * 2,
recommendedPanes = 1,
maxPanes = 2;

/// Returns a [Breakpoint] with the given constraints for a mediumLarge screen.
const Breakpoint.mediumLarge({this.andUp = false, this.platform})
: beginWidth = 840,
endWidth = 1200,
beginHeight = 900,
endHeight = null,
spacing = kMaterialMediumAndUpSpacing,
margin = kMaterialMediumAndUpMargin,
padding = kMaterialPadding * 3,
recommendedPanes = 2,
maxPanes = 2;

/// Returns a [Breakpoint] with the given constraints for a large screen.
const Breakpoint.large({this.andUp = false, this.platform})
: beginWidth = 1200,
endWidth = 1600,
beginHeight = 900,
endHeight = null,
spacing = kMaterialMediumAndUpSpacing,
margin = kMaterialMediumAndUpMargin,
padding = kMaterialPadding * 4,
recommendedPanes = 2,
maxPanes = 2;

/// Returns a [Breakpoint] with the given constraints for an extraLarge screen.
const Breakpoint.extraLarge({this.andUp = false, this.platform})
: beginWidth = 1600,
endWidth = null,
beginHeight = 900,
endHeight = null,
spacing = kMaterialMediumAndUpSpacing,
margin = kMaterialMediumAndUpMargin,
padding = kMaterialPadding * 5,
recommendedPanes = 2,
maxPanes = 3;
```

It is possible to compare Breakpoints:

<?code-excerpt "lib/src/breakpoints.dart (Breakpoint operators)"?>
```dart
/// Returns true if this [Breakpoint] is greater than the given [Breakpoint].
bool operator >(Breakpoint breakpoint)
// ···
/// Returns true if this [Breakpoint] is less than the given [Breakpoint].
bool operator <(Breakpoint breakpoint)
// ···
/// Returns true if this [Breakpoint] is greater than or equal to the
/// given [Breakpoint].
bool operator >=(Breakpoint breakpoint)
// ···
/// Returns true if this [Breakpoint] is less than or equal to the
/// given [Breakpoint].
bool operator <=(Breakpoint breakpoint)
// ···
/// Returns true if this [Breakpoint] is between the given [Breakpoint]s.
bool between(Breakpoint lower, Breakpoint upper)
```

### AdaptiveLayout

!["AdaptiveLayout's Assigned Slots Displayed on Screen"](example/demo_files/screenSlots.png)
Expand All @@ -151,7 +269,7 @@ displayed and the entrance animation and exit animation.

### Example Usage

<?code-excerpt "adaptive_layout_demo.dart (Example)"?>
<?code-excerpt "example/lib/adaptive_layout_demo.dart (Example)"?>
```dart
// AdaptiveLayout has a number of slots that take SlotLayouts and these
// SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.
Expand Down
85 changes: 83 additions & 2 deletions packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class Breakpoints {
/// * [SlotLayout.config], which uses breakpoints to dictate the layout of the
/// screen.
class Breakpoint {
// #docregion Breakpoints
/// Returns a const [Breakpoint] with the given constraints.
const Breakpoint({
this.beginWidth,
Expand Down Expand Up @@ -219,6 +220,7 @@ class Breakpoint {
padding = kMaterialPadding * 5,
recommendedPanes = 2,
maxPanes = 3;
// #enddocregion Breakpoints

/// A set of [TargetPlatform]s that the [Breakpoint] will be active on desktop.
static const Set<TargetPlatform> desktop = <TargetPlatform>{
Expand Down Expand Up @@ -278,7 +280,6 @@ class Breakpoint {
bool isActive(BuildContext context) {
final TargetPlatform host = Theme.of(context).platform;
final bool isRightPlatform = platform?.contains(host) ?? true;
final bool isDesktop = Breakpoint.desktop.contains(host);

final double width = MediaQuery.sizeOf(context).width;
final double height = MediaQuery.sizeOf(context).height;
Expand All @@ -294,7 +295,7 @@ class Breakpoint {
? width >= lowerBoundWidth
: width >= lowerBoundWidth && width < upperBoundWidth;

final bool isHeightActive = isDesktop ||
final bool isHeightActive = isDesktop(context) ||
orientation == Orientation.portrait ||
(orientation == Orientation.landscape && andUp
? isWidthActive || height >= lowerBoundHeight
Expand Down Expand Up @@ -344,4 +345,84 @@ class Breakpoint {
}
return currentBreakpoint;
}

/// Returns true if the current platform is Desktop.
static bool isDesktop(BuildContext context) {
return Breakpoint.desktop.contains(Theme.of(context).platform);
}

/// Returns true if the current platform is Mobile.
static bool isMobile(BuildContext context) {
return Breakpoint.mobile.contains(Theme.of(context).platform);
}

// #docregion Breakpoint operators
/// Returns true if this [Breakpoint] is greater than the given [Breakpoint].
bool operator >(Breakpoint breakpoint)
// #enddocregion Breakpoint operators
{
return (beginWidth ?? double.negativeInfinity) >
(breakpoint.beginWidth ?? double.negativeInfinity) &&
(endWidth ?? double.infinity) >
(breakpoint.endWidth ?? double.infinity) &&
(beginHeight ?? double.negativeInfinity) >
(breakpoint.beginHeight ?? double.negativeInfinity) &&
(endHeight ?? double.infinity) >
(breakpoint.endHeight ?? double.infinity);
}

// #docregion Breakpoint operators
/// Returns true if this [Breakpoint] is less than the given [Breakpoint].
bool operator <(Breakpoint breakpoint)
// #enddocregion Breakpoint operators
{
return (endWidth ?? double.infinity) <
(breakpoint.endWidth ?? double.infinity) &&
(beginWidth ?? double.negativeInfinity) <
(breakpoint.beginWidth ?? double.negativeInfinity) &&
(endHeight ?? double.infinity) <
(breakpoint.endHeight ?? double.infinity) &&
(beginHeight ?? double.negativeInfinity) <
(breakpoint.beginHeight ?? double.negativeInfinity);
}

// #docregion Breakpoint operators
/// Returns true if this [Breakpoint] is greater than or equal to the
/// given [Breakpoint].
bool operator >=(Breakpoint breakpoint)
// #enddocregion Breakpoint operators
{
return (beginWidth ?? double.negativeInfinity) >=
(breakpoint.beginWidth ?? double.negativeInfinity) &&
(endWidth ?? double.infinity) >=
(breakpoint.endWidth ?? double.infinity) &&
(beginHeight ?? double.negativeInfinity) >=
(breakpoint.beginHeight ?? double.negativeInfinity) &&
(endHeight ?? double.infinity) >=
(breakpoint.endHeight ?? double.infinity);
}

// #docregion Breakpoint operators
/// Returns true if this [Breakpoint] is less than or equal to the
/// given [Breakpoint].
bool operator <=(Breakpoint breakpoint)
// #enddocregion Breakpoint operators
{
return (endWidth ?? double.infinity) <=
(breakpoint.endWidth ?? double.infinity) &&
(beginWidth ?? double.negativeInfinity) <=
(breakpoint.beginWidth ?? double.negativeInfinity) &&
(endHeight ?? double.infinity) <=
(breakpoint.endHeight ?? double.infinity) &&
(beginHeight ?? double.negativeInfinity) <=
(breakpoint.beginHeight ?? double.negativeInfinity);
}

// #docregion Breakpoint operators
/// Returns true if this [Breakpoint] is between the given [Breakpoint]s.
bool between(Breakpoint lower, Breakpoint upper)
// #enddocregion Breakpoint operators
{
return this >= lower && this < upper;
}
}
3 changes: 2 additions & 1 deletion packages/flutter_adaptive_scaffold/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
version: 0.2.3
version: 0.2.4
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold

Expand All @@ -20,3 +20,4 @@ topics:
- layout
- ui
- adaptive
- responsive
60 changes: 59 additions & 1 deletion packages/flutter_adaptive_scaffold/test/breakpoint_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ void main() {
.element(find.byKey(const Key('Breakpoints.smallMobile'))))
.spacing,
kMaterialCompactSpacing);
}, variant: TargetPlatformVariant.mobile());
});

testWidgets('returns kMaterialMediumAndUpSpacing for medium breakpoint',
(WidgetTester tester) async {
Expand Down Expand Up @@ -959,6 +959,64 @@ void main() {
3);
}, variant: TargetPlatformVariant.mobile());
});

group('Breakpoint method tests', () {
testWidgets('isMobile returns true on mobile platforms',
(WidgetTester tester) async {
await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester));
await tester.pumpAndSettle();

expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))),
isTrue);

expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))),
isFalse);
}, variant: TargetPlatformVariant.mobile());

testWidgets('isDesktop returns true on desktop platforms',
(WidgetTester tester) async {
await tester.pumpWidget(SimulatedLayout.medium.scaffold(tester));
await tester.pumpAndSettle();

expect(Breakpoint.isDesktop(tester.element(find.byType(TestScaffold))),
isTrue);

expect(Breakpoint.isMobile(tester.element(find.byType(TestScaffold))),
isFalse);
}, variant: TargetPlatformVariant.desktop());

test('Breakpoint comparison operators work correctly', () {
const Breakpoint small = Breakpoints.small;
const Breakpoint medium = Breakpoints.medium;
const Breakpoint large = Breakpoints.large;

expect(small < medium, isTrue);
expect(large > medium, isTrue);
expect(small <= Breakpoints.small, isTrue);
expect(large >= medium, isTrue);
});

test('Breakpoint equality and hashCode', () {
const Breakpoint small1 = Breakpoints.small;
const Breakpoint small2 = Breakpoints.small;
const Breakpoint medium = Breakpoints.medium;

expect(small1 == small2, isTrue);
expect(small1 == medium, isFalse);
expect(small1.hashCode == small2.hashCode, isTrue);
expect(small1.hashCode == medium.hashCode, isFalse);
});

test('Breakpoint between method works correctly', () {
const Breakpoint small = Breakpoints.small;
const Breakpoint medium = Breakpoints.medium;
const Breakpoint large = Breakpoints.large;

expect(medium.between(small, large), isTrue);
expect(small.between(medium, large), isFalse);
expect(large.between(small, medium), isFalse);
});
});
}

class DummyWidget extends StatelessWidget {
Expand Down