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] Add expanded and extra large breakpoints #7300

Merged
merged 27 commits into from
Aug 12, 2024

Conversation

martijn00
Copy link
Contributor

@martijn00 martijn00 commented Aug 4, 2024

This aligns the material3 specs for window sizes with the official docs: https://m3.material.io/foundations/layout/applying-layout/expanded.

This adds checks for height based breakpoints: https://developer.android.com/develop/ui/compose/layouts/adaptive/window-size-classes

I would like to rename smallBody to compactBody to align that as well but that would be a breaking change. Let me know if you want that to happen.

List which issues are fixed by this PR. You must list at least one issue.

flutter/flutter#118932

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Copy link

google-cla bot commented Aug 4, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@martijn00 martijn00 changed the title Add expanded and extra large breakpoints [flutter_adaptive_scaffold] Add expanded and extra large breakpoints Aug 4, 2024
@martijn00 martijn00 force-pushed the breakpoints branch 2 times, most recently from fb15c81 to e56c89a Compare August 7, 2024 09:03
@martijn00
Copy link
Contributor Author

@gspencergoog I'm looking a bit at refactoring the breakpoints into an enhanced enum.

enum Breakpoints {
  standard(begin: -1),
  small(begin: 0, end: 600, bottom: 480),
  medium(begin: 600, end: 840, top: 480, bottom: 900),
  mediumLarge(begin: 840, end: 1200, top: 900),
  large(begin: 1200, end: 1600, top: 900),
  extraLarge(begin: 1600, top: 900);

  const Breakpoints({
    required this.begin,
    this.end,
    this.top,
    this.bottom,
    this.platform,
  });

  final double begin;
  final double? end;
  final double? top;
  final double? bottom;
  final Set<TargetPlatform>? platform;

  bool isActive(BuildContext context, {bool up = false, bool isDesktop = false, bool isMobile = false}) {
    final TargetPlatform host = Theme.of(context).platform;
    final bool isRightPlatform = platform?.contains(host) ?? true;

    final double width = MediaQuery.sizeOf(context).width;
    final double height = MediaQuery.sizeOf(context).height;
    final Orientation orientation = MediaQuery.orientationOf(context);

    final double lowerBoundWidth = begin;
    final double upperBoundWidth = end ?? double.infinity;

    final double lowerBoundHeight = top ?? double.negativeInfinity;
    final double upperBoundHeight = bottom ?? double.infinity;

    final bool isWidthActive = up ? width >= lowerBoundWidth : width >= lowerBoundWidth && width < upperBoundWidth;

    final bool isHeightActive = (orientation == Orientation.landscape &&
            height >= lowerBoundHeight &&
            height < upperBoundHeight) ||
        orientation == Orientation.portrait;

    bool isPlatformActive = true;
    if (isDesktop) {
      isPlatformActive = _desktop.contains(host);
    } else if (isMobile) {
      isPlatformActive = _mobile.contains(host);
    }

    return isWidthActive && isHeightActive && isRightPlatform && isPlatformActive;
  }
}

This would enable users to better check on the enum. What do you think about that?

@gspencergoog
Copy link
Contributor

I'm looking a bit at refactoring the breakpoints into an enhanced enum.

Ooh, I like that better. You'd be renaming the begin/end/top/bottom parts to be beginWidth/endWidth/beginHeight/endHeight, though, right?

@@ -3,6 +3,11 @@
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

## 0.1.12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're still pre-1.0 here, no need to bump to 1.0, but since you're making breaking changes, you might want to bump to 0.2.0 and note the breaking changes in the CHANGELOG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, it would be great to make some more breaking changes hehe. I have some more ideas for bigger changes to align with Material specs. I would like to land those in smaller pieces in new PRs otherwise it would all just get one massive PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can take a look at those, but let's not put them in this PR. Best to keep the breaking changes in separate PRs so they can be more easily reverted if needed.

I didn't mean to suggest a field day for breaking changes. Let's still consider them on their merits before breaking people, even if it's a pre-1.0 package. But if they're worth it, by all means!

@martijn00
Copy link
Contributor Author

I'm looking a bit at refactoring the breakpoints into an enhanced enum.

Ooh, I like that better. You'd be renaming the begin/end/top/bottom parts to be beginWidth/endWidth/beginHeight/endHeight, though, right?

@gspencergoog i've got this now.

enum Breakpoint {
  /// This is a standard breakpoint that can be used as a fallthrough in the
  /// case that no other breakpoint is active.
  ///
  /// It is active from a width of -1 dp to infinity.
  standard(beginWidth: -1),

  /// A window whose width is less than 600 dp, greater than 0 dp and
  /// whose height is less than 480 dp.
  small(beginWidth: 0, endWidth: 600, endHeight: 480),

  /// A window whose width is less than 840 dp, greater than 600 dp and
  /// whose height is less than 900 dp.
  medium(beginWidth: 600, endWidth: 840, beginHeight: 480, endHeight: 900),

  /// A window whose width is less than 1200 dp, greater than 840 dp and
  /// whose height is more than 900 dp.
  mediumLarge(beginWidth: 840, endWidth: 1200, beginHeight: 900),

  /// A window whose width is less than 1600 dp, greater than 1200 dp and
  /// whose height is more than 900 dp.
  large(beginWidth: 1200, endWidth: 1600, beginHeight: 900),

  /// A window whose width is greater than 1600 dp and whose height is more
  /// than 900 dp.
  extraLarge(beginWidth: 1600, beginHeight: 900);

  const Breakpoint({
    this.beginWidth,
    this.endWidth,
    this.beginHeight,
    this.endHeight,
  });

  /// The beginning width dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginWidth;

  /// The end width dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endWidth;

  /// The beginning height dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginHeight;

  /// The end height dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endHeight;

  /// Returns true if the [Breakpoint] is active based on the conditions of the
  /// [BuildContext].
  bool isActive(
    BuildContext context, {
    bool andUp = false,
    Set<TargetPlatform>? targetPlatform,
  }) {
    final TargetPlatform host = Theme.of(context).platform;
    final bool isRightPlatform = targetPlatform?.contains(host) ?? true;

    final double width = MediaQuery.sizeOf(context).width;
    final double height = MediaQuery.sizeOf(context).height;
    final Orientation orientation = MediaQuery.orientationOf(context);

    final double lowerBoundWidth = beginWidth ?? double.negativeInfinity;
    final double upperBoundWidth = endWidth ?? double.infinity;

    final double lowerBoundHeight = beginHeight ?? double.negativeInfinity;
    final double upperBoundHeight = endHeight ?? double.infinity;

    final bool isWidthActive = andUp
        ? width >= lowerBoundWidth
        : width >= lowerBoundWidth && width < upperBoundWidth;

    final bool isHeightActive = (orientation == Orientation.landscape &&
            height >= lowerBoundHeight &&
            height < upperBoundHeight) ||
        orientation == Orientation.portrait;

    return isWidthActive && isHeightActive && isRightPlatform;
  }
}

The problem with this approach is that you can't extend it. You wouldn't be able to do something like:
largeBreakpoint: const WidthPlatformBreakpoint(beginWidth: 1200, endWidth: 1600),

Do you have any ideas on how to do this?

@diegotori
Copy link

I'm looking a bit at refactoring the breakpoints into an enhanced enum.

Ooh, I like that better. You'd be renaming the begin/end/top/bottom parts to be beginWidth/endWidth/beginHeight/endHeight, though, right?

@gspencergoog i've got this now.

enum Breakpoint {
  /// This is a standard breakpoint that can be used as a fallthrough in the
  /// case that no other breakpoint is active.
  ///
  /// It is active from a width of -1 dp to infinity.
  standard(beginWidth: -1),

  /// A window whose width is less than 600 dp, greater than 0 dp and
  /// whose height is less than 480 dp.
  small(beginWidth: 0, endWidth: 600, endHeight: 480),

  /// A window whose width is less than 840 dp, greater than 600 dp and
  /// whose height is less than 900 dp.
  medium(beginWidth: 600, endWidth: 840, beginHeight: 480, endHeight: 900),

  /// A window whose width is less than 1200 dp, greater than 840 dp and
  /// whose height is more than 900 dp.
  mediumLarge(beginWidth: 840, endWidth: 1200, beginHeight: 900),

  /// A window whose width is less than 1600 dp, greater than 1200 dp and
  /// whose height is more than 900 dp.
  large(beginWidth: 1200, endWidth: 1600, beginHeight: 900),

  /// A window whose width is greater than 1600 dp and whose height is more
  /// than 900 dp.
  extraLarge(beginWidth: 1600, beginHeight: 900);

  const Breakpoint({
    this.beginWidth,
    this.endWidth,
    this.beginHeight,
    this.endHeight,
  });

  /// The beginning width dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginWidth;

  /// The end width dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endWidth;

  /// The beginning height dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginHeight;

  /// The end height dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endHeight;

  /// Returns true if the [Breakpoint] is active based on the conditions of the
  /// [BuildContext].
  bool isActive(
    BuildContext context, {
    bool andUp = false,
    Set<TargetPlatform>? targetPlatform,
  }) {
    final TargetPlatform host = Theme.of(context).platform;
    final bool isRightPlatform = targetPlatform?.contains(host) ?? true;

    final double width = MediaQuery.sizeOf(context).width;
    final double height = MediaQuery.sizeOf(context).height;
    final Orientation orientation = MediaQuery.orientationOf(context);

    final double lowerBoundWidth = beginWidth ?? double.negativeInfinity;
    final double upperBoundWidth = endWidth ?? double.infinity;

    final double lowerBoundHeight = beginHeight ?? double.negativeInfinity;
    final double upperBoundHeight = endHeight ?? double.infinity;

    final bool isWidthActive = andUp
        ? width >= lowerBoundWidth
        : width >= lowerBoundWidth && width < upperBoundWidth;

    final bool isHeightActive = (orientation == Orientation.landscape &&
            height >= lowerBoundHeight &&
            height < upperBoundHeight) ||
        orientation == Orientation.portrait;

    return isWidthActive && isHeightActive && isRightPlatform;
  }
}

The problem with this approach is that you can't extend it. You wouldn't be able to do something like: largeBreakpoint: const WidthPlatformBreakpoint(beginWidth: 1200, endWidth: 1600),

Do you have any ideas on how to do this?

Enhanced enums aren't suitable for this use case, since it would prevent a dev such as myself from defining custom breakpoints outside of the Material 3 spec.

In other words, before your PR, I had to create custom WidthPlatformBreakpoint values in order to fill in the gap.

If you go through with this change, if and when the Material Design team decides to update the breakpoints again in the future and they don't end up making it into this library immediately, then there will be no way to get around that limitation.

@gspencergoog
Copy link
Contributor

The problem with this approach is that you can't extend it. You wouldn't be able to do something like: largeBreakpoint: const WidthPlatformBreakpoint(beginWidth: 1200, endWidth: 1600),

Do you have any ideas on how to do this?

Ahh, you're right, that's not going to work. Sorry, I should have realized that when you suggested it.

@martijn00
Copy link
Contributor Author

@gspencergoog I've tried a bit more and come up with this:

class Breakpoint {
  /// Returns a const [Breakpoint] with the given constraints.
  const Breakpoint({
    this.beginWidth,
    this.endWidth,
    this.beginHeight,
    this.endHeight,
    this.platform,
    this.andUp = false,
  });

  const Breakpoint.small({this.andUp = false, this.platform})
      : beginWidth = 0,
        endWidth = 600,
        beginHeight = null,
        endHeight = 480;

  const Breakpoint.medium({this.andUp = false, this.platform})
      : beginWidth = 600,
        endWidth = 840,
        beginHeight = 480,
        endHeight = 900;

  static const Set<TargetPlatform> desktop = <TargetPlatform>{
    TargetPlatform.linux,
    TargetPlatform.macOS,
    TargetPlatform.windows
  };

  static const Set<TargetPlatform> mobile = <TargetPlatform>{
    TargetPlatform.android,
    TargetPlatform.fuchsia,
    TargetPlatform.iOS,
  };

  /// When set to true, it will include any size above the set width.
  final bool andUp;

  /// The beginning width dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginWidth;

  /// The end width dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endWidth;

  /// The beginning height dp value. If left null then the [Breakpoint] will have
  /// no lower bound.
  final double? beginHeight;

  /// The end height dp value. If left null then the [Breakpoint] will have no
  /// upper bound.
  final double? endHeight;

  /// A Set of [TargetPlatform]s that the [Breakpoint] will be active on. If
  /// left null then it will be active on all platforms.
  final Set<TargetPlatform>? platform;

  /// A method that returns true based on conditions related to the context of
  /// the screen such as MediaQuery.sizeOf(context).width.
  bool isActive(BuildContext context) {
    final TargetPlatform host = Theme.of(context).platform;
    final bool isRightPlatform = platform?.contains(host) ?? true;

    final double width = MediaQuery.sizeOf(context).width;
    final double height = MediaQuery.sizeOf(context).height;
    final Orientation orientation = MediaQuery.orientationOf(context);

    final double lowerBoundWidth = beginWidth ?? double.negativeInfinity;
    final double upperBoundWidth = endWidth ?? double.infinity;

    final double lowerBoundHeight = beginHeight ?? double.negativeInfinity;
    final double upperBoundHeight = endHeight ?? double.infinity;

    final bool isWidthActive = andUp
        ? width >= lowerBoundWidth
        : width >= lowerBoundWidth && width < upperBoundWidth;

    final bool isHeightActive = (orientation == Orientation.landscape &&
            height >= lowerBoundHeight &&
            height < upperBoundHeight) ||
        orientation == Orientation.portrait;

    return isWidthActive && isHeightActive && isRightPlatform;
  }
}

Now you can easily use it like this: this.smallBreakpoint = const Breakpoint.small(andUp: true, platform: Breakpoint.mobile),

I can forward the existing code like this: static const Breakpoint smallAndUp = Breakpoint.small(andUp: true); to be backwards compatible.

This would remove the WidthPlatformBreakpoint.

What do you think?

@gspencergoog
Copy link
Contributor

What do you think?

Yes, that looks better, and still extensible.

Copy link
Contributor

@gspencergoog gspencergoog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32384589-a60f0e74-c078-11e7-9bc1-e5b5287aea9d

packages/flutter_adaptive_scaffold/pubspec.yaml Outdated Show resolved Hide resolved
@martijn00
Copy link
Contributor Author

martijn00 commented Aug 8, 2024

@gspencergoog If you merge #7310 first i'll rebase this one and update the changelog again before the release to pub.

@martijn00
Copy link
Contributor Author

@gspencergoog rebase is done.

@martijn00
Copy link
Contributor Author

@Renzo-Olivares @gspencergoog fixed the comments.

Copy link

@Renzo-Olivares Renzo-Olivares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM w/ last small nit. Thank you for the contribution!

@martijn00
Copy link
Contributor Author

@gspencergoog @Renzo-Olivares done!

@gspencergoog gspencergoog added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 12, 2024
@auto-submit auto-submit bot merged commit 970ba7a into flutter:main Aug 12, 2024
76 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 12, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 12, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 12, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Aug 12, 2024
flutter/packages@f7b1256...d9a6de8

2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [camera]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/camera/camera_android/android (flutter/packages#7371)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [path_provider]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/path_provider/path_provider_android/android (flutter/packages#7376)
2024-08-12 [email protected] [pigeon] removes restriction on number of custom types per file (flutter/packages#6840)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [pigeon]: Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.0.0 to 2.0.10 in /packages/pigeon/platform_tests/test_plugin/android (flutter/packages#7370)
2024-08-12 [email protected] [shared_preferences] Fixes get-all when suite name is used (flutter/packages#7335)
2024-08-12 [email protected] [flutter_adaptive_scaffold] Add expanded and extra large breakpoints (flutter/packages#7300)
2024-08-12 [email protected] Manual roll Flutter from b12d861 to 9b84701 (8 revisions) (flutter/packages#7366)
2024-08-10 [email protected] Manual roll Flutter from 76107bd to b12d861 (14 revisions) (flutter/packages#7358)
2024-08-09 [email protected] [shared_preferences] fix cast error and mutable list error with `getStringList` (flutter/packages#7355)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
? width >= lowerBoundWidth
: width >= lowerBoundWidth && width < upperBoundWidth;

final bool isHeightActive = (orientation == Orientation.landscape &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks desktops. Height is irrelevant for desktops, regardless of orientation, imo :(
@martijn00

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #7347

DBowen33 pushed a commit to DBowen33/flutter that referenced this pull request Aug 16, 2024
flutter/packages@f7b1256...d9a6de8

2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [camera]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/camera/camera_android/android (flutter/packages#7371)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [path_provider]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/path_provider/path_provider_android/android (flutter/packages#7376)
2024-08-12 [email protected] [pigeon] removes restriction on number of custom types per file (flutter/packages#6840)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [pigeon]: Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.0.0 to 2.0.10 in /packages/pigeon/platform_tests/test_plugin/android (flutter/packages#7370)
2024-08-12 [email protected] [shared_preferences] Fixes get-all when suite name is used (flutter/packages#7335)
2024-08-12 [email protected] [flutter_adaptive_scaffold] Add expanded and extra large breakpoints (flutter/packages#7300)
2024-08-12 [email protected] Manual roll Flutter from b12d861 to 9b84701 (8 revisions) (flutter/packages#7366)
2024-08-10 [email protected] Manual roll Flutter from 76107bd to b12d861 (14 revisions) (flutter/packages#7358)
2024-08-09 [email protected] [shared_preferences] fix cast error and mutable list error with `getStringList` (flutter/packages#7355)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Buchimi pushed a commit to Buchimi/flutter that referenced this pull request Sep 2, 2024
flutter/packages@f7b1256...d9a6de8

2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [camera]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/camera/camera_android/android (flutter/packages#7371)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [path_provider]: Bump androidx.annotation:annotation from 1.8.1 to 1.8.2 in /packages/path_provider/path_provider_android/android (flutter/packages#7376)
2024-08-12 [email protected] [pigeon] removes restriction on number of custom types per file (flutter/packages#6840)
2024-08-12 49699333+dependabot[bot]@users.noreply.github.com [pigeon]: Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.0.0 to 2.0.10 in /packages/pigeon/platform_tests/test_plugin/android (flutter/packages#7370)
2024-08-12 [email protected] [shared_preferences] Fixes get-all when suite name is used (flutter/packages#7335)
2024-08-12 [email protected] [flutter_adaptive_scaffold] Add expanded and extra large breakpoints (flutter/packages#7300)
2024-08-12 [email protected] Manual roll Flutter from b12d861 to 9b84701 (8 revisions) (flutter/packages#7366)
2024-08-10 [email protected] Manual roll Flutter from 76107bd to b12d861 (14 revisions) (flutter/packages#7358)
2024-08-09 [email protected] [shared_preferences] fix cast error and mutable list error with `getStringList` (flutter/packages#7355)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: flutter_adaptive_scaffold
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants