diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ac3cdf3..eeb0328 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -39,6 +39,9 @@ jobs: architecture: x64 cache: true + - name: Disable flutter cli animations + run: flutter config --no-cli-animations + - name: Install dependencies run: dart pub get @@ -48,8 +51,8 @@ jobs: - name: Analyze static code run: dart analyze - # - name: Run tests - # run: flutter test --no-pub --coverage + - name: Run tests + run: flutter test --no-pub --coverage - name: Check publish warnings run: dart pub publish --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dc2550..aa06d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,11 @@ - Add optional size and scaleFactor parameters - Introduce constructors on Gutter: `Gutter.tiny`, `Gutter.small`, `Gutter.medium`, `Gutter.large`, `Gutter.extraLarge` -- Add `Gutter.expand` and `Gutter.singleLine` - Add extension to add Gutter on Lists - Migrate to `flutter_adaptive_scaffold` to remove deprecated package - Add `AdaptiveGutter` that switches `Gutter` size based on the current breakpoint -- Add `PaddingGutter` that adds material padding - BREAKING CHANGE: Removal of `Gap` +- BREAKING CHANGE: Rename `Margin` to `GutterMargin` ## [1.0.3] - 07/27/2024 diff --git a/README.md b/README.md index a481e20..e01a701 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,10 @@ Gutters and margins: definition (small gap on small screens, larger gap on large screens) `Gutter.tiny`, `Gutter.small`, `Gutter.medium`, `Gutter.large` and `Gutter.extraLarge` all provide gaps that are factors of the base gutter -size for situations where larger or smaller gaps are more appropriate. `Gutter.expand` can be used to expand the Gutter on the crossAxis. -The default sizing is used on: `Gutter` or `Gutter.medium`. +size for situations where larger or smaller gaps are more appropriate. The default sizing is used on: `Gutter` or `Gutter.medium`. For more flexibility, you can also use the provided extension on `BuildContext` to reference the -gutter and margin sizes directly (`context.gutter`, `context.margin`). +gutter and margin sizes directly (`context.gutter`, `context.gutterLarge`, `context.margin`, etc). It is possible to manually create a `Gutter` with a specific `size` or `scaleFactor`. @@ -49,6 +48,18 @@ On Iterable Widgets you can set a Gutter on every item: ].withGutter() // Optional parameter to set which Gutter size you want to use ``` +Using `GutterPadding` you can add a `Widget` that insets its child by the material padding or the given padding. + +```dart +const GutterPadding.only( + left: Gutter.medium(), + right: Gutter.small(), + top: Gutter(size: 20), + bottom: Gutter.medium(scaleFactor: 3), + child: ColoredBox(color: Colors.blue, child: Text('Child')), +) +``` + You can use `Gutter` with other packages using `GutterConfiguration` and `widgetToAxis` (see example). ## Supported Widgets diff --git a/example/lib/main.dart b/example/lib/main.dart index 1435008..19d1449 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -60,14 +60,14 @@ class _MyHomePageState extends State { ), const Gutter.medium(), const Text('times'), - const Gutter(size: 20, scaleFactor: 3, type: GutterType.large), + const Gutter.from(size: 20, scaleFactor: 3), const Text('test'), const AdaptiveGutter( small: Gutter.tiny(), medium: Gutter.large(), large: Gutter.extraLarge(), ), - ].withGutter(), + ], ), ), ), diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 30fe7e9..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:example/main.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/lib/flutter_gutter.dart b/lib/flutter_gutter.dart index fe4b5a0..398843e 100644 --- a/lib/flutter_gutter.dart +++ b/lib/flutter_gutter.dart @@ -1,5 +1,7 @@ export 'src/adaptive_gutter.dart'; -export 'src/axis_aware.dart'; -export 'src/breakpoints_utils.dart'; +export 'src/axis_aware_orientation.dart'; export 'src/gutter.dart'; +export 'src/gutter_configuration.dart'; +export 'src/gutter_extensions.dart'; +export 'src/gutter_margin.dart'; export 'src/gutter_type.dart'; diff --git a/lib/src/axis_aware.dart b/lib/src/axis_aware_orientation.dart similarity index 94% rename from lib/src/axis_aware.dart rename to lib/src/axis_aware_orientation.dart index b3662d9..558bae2 100644 --- a/lib/src/axis_aware.dart +++ b/lib/src/axis_aware_orientation.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; + import '../flutter_gutter.dart'; /// Axis-aware widget that provides the orientation. -class AxisAware extends StatelessWidget { - /// Creates a new [AxisAware] widget. - const AxisAware({ +class AxisAwareOrientation extends StatelessWidget { + /// Creates a new [AxisAwareOrientation] widget. + const AxisAwareOrientation({ super.key, required this.builder, }); diff --git a/lib/src/breakpoints_utils.dart b/lib/src/breakpoints_utils.dart deleted file mode 100644 index 6bff461..0000000 --- a/lib/src/breakpoints_utils.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; - -import '../flutter_gutter.dart'; - -/// A gap of the standard gutter size according to Material Design's breakpoints -extension BreakpointExtensions on BuildContext { - /// The margin size according to Material Design's breakpoints system. - double get margin => materialSpacing; - - /// The size according to Material Design's with a specified [GutterType]. - double gutter({GutterType type = GutterType.medium, double scaleFactor = 1}) { - return scaleFactor * - switch (type) { - GutterType.tiny => materialSpacing / Gutter.scaleFactorMediumDefault, - GutterType.small => materialSpacing / Gutter.scaleFactorSmallDefault, - GutterType.medium => materialSpacing, - GutterType.large => materialSpacing * Gutter.scaleFactorSmallDefault, - GutterType.extraLarge => - materialSpacing * Gutter.scaleFactorMediumDefault, - GutterType.expand => double.infinity, - } - ..floor(); - } - - /// The size according to Material Design's breakpoints system. - double get materialSpacing { - if (Breakpoints.small.isActive(this)) { - // Use the compact screen values. - // See: https://m3.material.io/foundations/layout/applying-layout/compact - return Gutter.materialSpacingSmall; - } else if (Breakpoints.medium.isActive(this)) { - // Use the medium/expanded screen values. - // See: https://m3.material.io/foundations/layout/applying-layout/medium - return Gutter.materialSpacingMediumAndUp; - } else if (Breakpoints.large.isActive(this)) { - // Use the large screen values. - // See: https://m3.material.io/foundations/layout/applying-layout/large-extra-large - return Gutter.materialSpacingMediumAndUp; - } else { - return Gutter.materialSpacingSmall; - } - } -} - -/// Extension to add a gutter between the items of an iterable. -extension SeparatedIterable on Iterable { - /// Allows to insert a [Gutter] between the items of the iterable. - List withGutter({Gutter gutter = const Gutter.medium()}) { - final List result = []; - final Iterator iterator = this.iterator; - if (iterator.moveNext()) { - result.add(iterator.current); - while (iterator.moveNext()) { - result - ..add(gutter) - ..add(iterator.current); - } - } - return result; - } -} diff --git a/lib/src/gutter.dart b/lib/src/gutter.dart index 0ec9c84..61f506a 100644 --- a/lib/src/gutter.dart +++ b/lib/src/gutter.dart @@ -10,102 +10,85 @@ class Gutter extends StatelessWidget { /// Creates a new [Gutter] widget. const Gutter({ super.key, - this.size, this.type = GutterType.medium, this.scaleFactor = 1, - }); + }) : size = null; /// The spacing used on small screens according to Material Design's - static const double materialSpacingSmall = 16.0; + /// See: https://m3.material.io/foundations/layout/applying-layout/compact + static const double materialSpacingSmall = 16; /// The spacing used on medium and large screens according to Material Design's - static const double materialSpacingMediumAndUp = 24.0; + /// See: https://m3.material.io/foundations/layout/applying-layout/medium + static const double materialSpacingMediumAndUp = 24; + + /// The default scale factor for a gutter. + static const double scaleFactorDefault = 1; /// The default scale factor for a small increase. - static const int scaleFactorSmallDefault = 2; + static const double scaleFactorDefaultSmall = 2; /// The default scale factor for a medium increase. - static const int scaleFactorMediumDefault = 4; + static const double scaleFactorDefaultMedium = 4; /// The type of gutter to create. final GutterType type; - /// The size of the gap. + /// The size of the [Gutter]. final double? size; /// The scale factor to apply to the gutter size. final double scaleFactor; - /// Creates a new [Gutter] widget with a single line size. - const Gutter.singleLine({super.key}) - : size = 1, - type = GutterType.expand, - scaleFactor = 1; + /// Creates a new [Gutter] widget from a size and scaleFactor. + const Gutter.from({super.key, this.size, this.scaleFactor = 1}) + : type = GutterType.medium; /// Creates a new [Gutter] widget with a tiny size. const Gutter.tiny({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.tiny; + : type = GutterType.tiny, + size = null; /// Creates a new [Gutter] widget with a small size. const Gutter.small({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.small; + : type = GutterType.small, + size = null; /// Creates a new [Gutter] widget with a medium size. const Gutter.medium({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.medium; + : type = GutterType.medium, + size = null; /// Creates a new [Gutter] widget with a large size. const Gutter.large({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.large; + : type = GutterType.large, + size = null; /// Creates a new [Gutter] widget with an extra large size. const Gutter.extraLarge({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.extraLarge; - - /// Creates a new [Gutter] widget with an expand size. - const Gutter.expand({super.key, this.scaleFactor = 1}) - : size = null, - type = GutterType.expand; + : type = GutterType.extraLarge, + size = null; @override Widget build(BuildContext context) { - return AxisAware( + return AxisAwareOrientation( key: key, builder: (BuildContext context, Orientation orientation) { - final double gapSize = - calculateGapSize(context, size, type, scaleFactor); - - if (type == GutterType.expand) { - return Expanded( - child: SizedBox( - width: orientation == Orientation.landscape ? gapSize : null, - height: orientation != Orientation.portrait ? null : gapSize, - ), - ); - } else { - return SizedBox( - width: orientation == Orientation.landscape ? gapSize : null, - height: orientation != Orientation.portrait ? null : gapSize, - ); - } + final double gutterSize = context.gutterSize( + type: type, + size: size, + scaleFactor: scaleFactor, + ); + + return SizedBox( + width: orientation == Orientation.landscape ? gutterSize : null, + height: orientation != Orientation.portrait ? null : gutterSize, + ); }, ); } } -/// calculates the gap size based on the size, type and scale factor. -double calculateGapSize( - BuildContext context, double? size, GutterType type, double scaleFactor) { - return size != null - ? (size * scaleFactor) - : context.gutter(type: type, scaleFactor: scaleFactor); -} - /// A gap a quarter the standard gutter size according to Material Design's /// breakpoints system class GutterTiny extends Gutter { @@ -120,6 +103,12 @@ class GutterSmall extends Gutter { const GutterSmall({super.key}) : super.small(); } +/// A gap of the standard gutter size according to Material Design's +class GutterMedium extends Gutter { + /// Creates a new [GutterMedium] widget. + const GutterMedium({super.key}) : super.medium(); +} + /// A gap twice the standard gutter size according to Material Design's /// breakpoints system. class GutterLarge extends Gutter { @@ -133,165 +122,3 @@ class GutterExtraLarge extends Gutter { /// Creates a new [GutterExtraLarge] widget. const GutterExtraLarge({super.key}) : super.extraLarge(); } - -/// A gap of the standard margin size according to Material Design's breakpoints -/// system -class Margin extends Gutter { - /// Creates a new [Margin] widget. - const Margin({super.key, super.size, super.type, super.scaleFactor}); -} - -/// A widget that insets its child by the material padding or the given padding. -class GutterPadding extends Gutter { - /// Creates a new [GutterPadding] widget with custom padding. - const GutterPadding({ - super.key, - this.child, - super.size, - super.type = GutterType.medium, - super.scaleFactor = 1, - }) : left = null, - top = null, - right = null, - bottom = null; - - /// Creates a new [GutterPadding] widget with specific edge insets. - const GutterPadding.only({ - super.key, - this.child, - this.left = 0, - this.top = 0, - this.right = 0, - this.bottom = 0, - super.type = GutterType.medium, - super.scaleFactor = 1, - }); - - /// Creates a new [GutterPadding] widget with symmetric padding. - const GutterPadding.symmetric({ - super.key, - this.child, - double? vertical = 0, - double? horizontal = 0, - super.type = GutterType.medium, - super.scaleFactor = 1, - }) : left = horizontal, - top = vertical, - right = horizontal, - bottom = vertical; - - /// Creates a new [GutterPadding] widget with uniform padding. - const GutterPadding.all( - double? padding, { - super.key, - this.child, - super.type = GutterType.medium, - super.scaleFactor = 1, - }) : left = padding, - top = padding, - right = padding, - bottom = padding; - - /// The child widget. - final Widget? child; - - /// The offset from the left. - final double? left; - - /// The offset from the right. - final double? right; - - /// The offset from the top. - final double? top; - - /// The offset from the bottom. - final double? bottom; - - @override - Widget build(BuildContext context) { - final double gapSize = calculateGapSize(context, size, type, scaleFactor); - - return Padding( - padding: EdgeInsets.only( - left: left ?? gapSize, - top: top ?? gapSize, - right: right ?? gapSize, - bottom: bottom ?? gapSize, - ), - child: child, - ); - } -} - -/// An inherited widget that sets custom [Gutter] and [Gap] behavior. -class GutterConfigurationData { - /// Creates a new [GutterConfigurationData] instance. - const GutterConfigurationData({ - this.widgetToAxis, - this.dynamicAxisCheck = true, - }); - - /// A function to get the axis of a widget not supported by flutter_gutter. - /// - /// This should return an `Axis` if a widget is recognized and should return - /// null if it is not. - /// - /// Note that this is not necessary if your widget already has an - /// `Axis.direction` attribute as Flutter Gutter will find the axis from this - /// automatically. - /// - /// ```dart - /// widgetToAxis: (widget) { - /// if (widget is MyCustomHorizontalWidget) { - /// return Axis.horizontal; - /// } - /// return null; - /// }, - /// ``` - final Axis? Function(Widget widget)? widgetToAxis; - - /// Whether or not gutter widgets should dynamically check for an `axis` - /// argument on unrecognized widgets. - /// - /// Turning this off is useful if you have breakpoints on uncaught exceptions - /// on as the dynamic check relies on a try...catch. However, widgets from - /// other packages (eg `Boxy`) will not be supported out of the box if you - /// turn off the dynamic check. - /// - /// Defaults to true. - final bool dynamicAxisCheck; - - @override - bool operator ==(Object other) { - return other is GutterConfigurationData && - other.widgetToAxis == widgetToAxis; - } - - @override - int get hashCode => widgetToAxis.hashCode; -} - -/// An inherited widget that sets custom [Gutter] and [GutterPadding] behavior. -class GutterConfiguration extends InheritedWidget { - /// Creates a new [GutterConfiguration] instance. - const GutterConfiguration({ - super.key, - required super.child, - required this.data, - }); - - /// The configuration data. - final GutterConfigurationData data; - - /// Gets the [GutterConfigurationData] from the nearest [GutterConfiguration] - static GutterConfigurationData? maybeOf(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType() - ?.data; - } - - @override - bool updateShouldNotify(GutterConfiguration oldWidget) { - return oldWidget.data == data; - } -} diff --git a/lib/src/gutter_configuration.dart b/lib/src/gutter_configuration.dart new file mode 100644 index 0000000..39c7e65 --- /dev/null +++ b/lib/src/gutter_configuration.dart @@ -0,0 +1,74 @@ +import 'package:flutter/widgets.dart'; + +/// An inherited widget that sets custom [Gutter] and [GutterPadding] behavior. +class GutterConfigurationData { + /// Creates a new [GutterConfigurationData] instance. + const GutterConfigurationData({ + this.widgetToAxis, + this.dynamicAxisCheck = true, + }); + + /// A function to get the axis of a widget not supported by flutter_gutter. + /// + /// This should return an `Axis` if a widget is recognized and should return + /// null if it is not. + /// + /// Note that this is not necessary if your widget already has an + /// `Axis.direction` attribute as Flutter Gutter will find the axis from this + /// automatically. + /// + /// ```dart + /// widgetToAxis: (widget) { + /// if (widget is MyCustomHorizontalWidget) { + /// return Axis.horizontal; + /// } + /// return null; + /// }, + /// ``` + final Axis? Function(Widget widget)? widgetToAxis; + + /// Whether or not gutter widgets should dynamically check for an `axis` + /// argument on unrecognized widgets. + /// + /// Turning this off is useful if you have breakpoints on uncaught exceptions + /// on as the dynamic check relies on a try...catch. However, widgets from + /// other packages (eg `Boxy`) will not be supported out of the box if you + /// turn off the dynamic check. + /// + /// Defaults to true. + final bool dynamicAxisCheck; + + @override + bool operator ==(Object other) { + return other is GutterConfigurationData && + other.widgetToAxis == widgetToAxis; + } + + @override + int get hashCode => widgetToAxis.hashCode; +} + +/// An inherited widget that sets custom [Gutter] and [GutterPadding] behavior. +class GutterConfiguration extends InheritedWidget { + /// Creates a new [GutterConfiguration] instance. + const GutterConfiguration({ + super.key, + required super.child, + required this.data, + }); + + /// The configuration data. + final GutterConfigurationData data; + + /// Gets the [GutterConfigurationData] from the nearest [GutterConfiguration] + static GutterConfigurationData? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.data; + } + + @override + bool updateShouldNotify(GutterConfiguration oldWidget) { + return oldWidget.data == data; + } +} diff --git a/lib/src/gutter_extensions.dart b/lib/src/gutter_extensions.dart new file mode 100644 index 0000000..9ccac33 --- /dev/null +++ b/lib/src/gutter_extensions.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; + +import '../flutter_gutter.dart'; + +/// Extension to add gutter sizes according to Material Design's breakpoints system. +extension GutterExtensions on BuildContext { + /// The margin size according to Material Design's breakpoints system. + double get margin => materialSpacing; + + static double? _gutterTiny; + static double? _gutterSmall; + static double? _gutterMedium; + static double? _gutterLarge; + static double? _gutterExtraLarge; + + /// A quarter the default gutter size. + double get gutterTiny => _gutterTiny ??= gutterSize(type: GutterType.tiny); + + /// Half the default gutter size. + double get gutterSmall => _gutterSmall ??= gutterSize(type: GutterType.small); + + /// The default gutter size. + double get gutter => gutterMedium; + + /// The default gutter size. + double get gutterMedium => + _gutterMedium ??= gutterSize(type: GutterType.medium); + + /// Double the default gutter size. + double get gutterLarge => _gutterLarge ??= gutterSize(type: GutterType.large); + + /// Triple the default gutter size. + double get gutterExtraLarge => + _gutterExtraLarge ??= gutterSize(type: GutterType.extraLarge); + + /// The size according to Material Design's with a specified [GutterType]. + /// If [size] is provided, it will be used instead of the default size. + /// If [scaleFactor] is provided, it will be used to scale the size. + double gutterSize({ + GutterType type = GutterType.medium, + double? size, + double scaleFactor = 1, + }) { + return size != null + ? (size * scaleFactor) + : scaleFactor * + switch (type) { + GutterType.tiny => materialSpacing / GutterType.tiny.scaleFactor, + GutterType.small => + materialSpacing / GutterType.small.scaleFactor, + GutterType.medium => + materialSpacing * GutterType.medium.scaleFactor, + GutterType.large => + materialSpacing * GutterType.large.scaleFactor, + GutterType.extraLarge => + materialSpacing * GutterType.extraLarge.scaleFactor, + }; + } + + /// The size according to Material Design's breakpoints system. + double get materialSpacing { + if (Breakpoints.small.isActive(this)) { + // Use the compact screen values. + return Gutter.materialSpacingSmall; + } else if (Breakpoints.medium.isActive(this)) { + // Use the medium/expanded screen values. + return Gutter.materialSpacingMediumAndUp; + } else if (Breakpoints.large.isActive(this)) { + // Use the large screen values. + // See: https://m3.material.io/foundations/layout/applying-layout/large-extra-large + return Gutter.materialSpacingMediumAndUp; + } else { + return Gutter.materialSpacingSmall; + } + } +} diff --git a/lib/src/gutter_margin.dart b/lib/src/gutter_margin.dart new file mode 100644 index 0000000..d43d831 --- /dev/null +++ b/lib/src/gutter_margin.dart @@ -0,0 +1,12 @@ +import '../flutter_gutter.dart'; + +/// A gap of the standard margin size according to Material Design's breakpoints +/// system +class GutterMargin extends Gutter { + /// Creates a new [GutterMargin] widget. + const GutterMargin({super.key, super.type, super.scaleFactor}); + + /// Creates a new [GutterMargin] widget with a specified size. + const GutterMargin.from({super.key, super.size, super.scaleFactor}) + : super.from(); +} diff --git a/lib/src/gutter_type.dart b/lib/src/gutter_type.dart index 1761e7e..1bf6196 100644 --- a/lib/src/gutter_type.dart +++ b/lib/src/gutter_type.dart @@ -3,22 +3,26 @@ import '../flutter_gutter.dart'; /// Type to indicate the size of a gutter. enum GutterType { /// A tiny gutter. - tiny, + tiny(Gutter.scaleFactorDefaultMedium), /// A small gutter. - small, + small(Gutter.scaleFactorDefaultSmall), /// A medium gutter. - medium, + medium(Gutter.scaleFactorDefault), /// A large gutter. - large, + large(Gutter.scaleFactorDefaultSmall), /// An extra large gutter. - extraLarge, + extraLarge(Gutter.scaleFactorDefaultMedium); - /// An expanded gutter. - expand, + const GutterType( + this.scaleFactor, + ); + + /// The scale factor to apply to the gutter size. + final double scaleFactor; } /// Extension to convert [GutterType]. @@ -36,8 +40,6 @@ extension GutterTypeExtensions on GutterType { return const Gutter.large(); case GutterType.extraLarge: return const Gutter.extraLarge(); - case GutterType.expand: - return const Gutter(size: double.infinity); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 5124bff..31fe4a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,9 @@ homepage: https://github.com/caseycrogers/flutter_gutter topics: - gap - gutter + - padding + - adaptive + - spacing version: 2.0.0 diff --git a/test/adaptive_gutter_test.dart b/test/adaptive_gutter_test.dart new file mode 100644 index 0000000..5b3cdb6 --- /dev/null +++ b/test/adaptive_gutter_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Breakpoints + const Size small = Size(599, 800); // Breakpoints.small + const Size medium = Size(600, 800); // Breakpoints.medium + const Size large = Size(840, 1000); // Breakpoints.large + + // Helper function to reduce redundancy + Future testAdaptiveGutter( + WidgetTester tester, + Size size, + Gutter small, + Gutter medium, + Gutter large, + ) async { + tester.view.physicalSize = size; + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Column( + children: [ + AdaptiveGutter( + small: small, + medium: medium, + large: large, + ), + ], + ), + ), + )); + + final Element context = tester.element(find.byType(MaterialApp)); + final Gutter gutterWidget = tester.widget(find.byType(Gutter)); + + final double expectedSize = context.gutterSize( + type: gutterWidget.type, + size: gutterWidget.size, + scaleFactor: gutterWidget.scaleFactor, + ); + + final Size gutterSize = tester.getSize(find.byType(Gutter)); + expect(gutterSize.height, expectedSize); + } + + // Tests for AdaptiveGutter with different configurations + testWidgets('should apply custom Gutter.large for small screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + small, + const Gutter.large(), + const Gutter.medium(), + const Gutter.small(), + ); + }); + + testWidgets('should apply custom Gutter.small for medium screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + medium, + const Gutter.large(), + const Gutter.small(), + const Gutter.medium(), + ); + }); + + testWidgets('should apply custom Gutter.medium for large screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + large, + const Gutter.small(), + const Gutter.large(), + const Gutter.medium(), + ); + }); + + // Tests for Gutter.tiny and Gutter.extraLarge + testWidgets('should apply custom Gutter.tiny for small screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + small, + const Gutter.tiny(), + const Gutter.medium(), + const Gutter.extraLarge(), + ); + }); + + testWidgets('should apply custom Gutter.extraLarge for medium screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + medium, + const Gutter.small(), + const Gutter.extraLarge(), + const Gutter.medium(), + ); + }); + + // Tests with custom size and scaleFactor + testWidgets('should apply custom size for Gutter on small screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + small, + const Gutter.from(size: 10), + const Gutter.medium(), + const Gutter.large(), + ); + }); + + testWidgets('should apply custom scaleFactor for Gutter on medium screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + medium, + const Gutter.small(), + const Gutter.medium(scaleFactor: 2), + const Gutter.large(), + ); + }); + + testWidgets( + 'should apply custom size and scaleFactor for Gutter on large screens', + (WidgetTester tester) async { + await testAdaptiveGutter( + tester, + large, + const Gutter.small(), + const Gutter.medium(), + const Gutter.from(size: 30, scaleFactor: 1.5), + ); + }); +} diff --git a/test/axis_aware_orientation_test.dart b/test/axis_aware_orientation_test.dart new file mode 100644 index 0000000..34ce3fd --- /dev/null +++ b/test/axis_aware_orientation_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('AxisAwareOrientation with Row widget', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Row( + children: [ + AxisAwareOrientation( + builder: (BuildContext context, Orientation orientation) { + return Text(orientation == Orientation.landscape + ? 'Landscape' + : 'Portrait'); + }, + ), + ], + ), + ), + ); + + final Finder textFinder = find.text('Landscape'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('AxisAwareOrientation with Column widget', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Column( + children: [ + AxisAwareOrientation( + builder: (BuildContext context, Orientation orientation) { + return Text(orientation == Orientation.landscape + ? 'Landscape' + : 'Portrait'); + }, + ), + ], + ), + ), + ); + + final Finder textFinder = find.text('Portrait'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('AxisAwareOrientation with horizontal Scrollable widget', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: AxisAwareOrientation( + builder: (BuildContext context, Orientation orientation) { + return Text(orientation == Orientation.landscape + ? 'Landscape' + : 'Portrait'); + }, + ), + ), + ), + ); + + final Finder textFinder = find.text('Landscape'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('AxisAwareOrientation with vertical Scrollable widget', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: AxisAwareOrientation( + builder: (BuildContext context, Orientation orientation) { + return Text(orientation == Orientation.landscape + ? 'Landscape' + : 'Portrait'); + }, + ), + ), + ), + ); + + final Finder textFinder = find.text('Portrait'); + expect(textFinder, findsOneWidget); + }); +} diff --git a/test/gutter_configuration_test.dart b/test/gutter_configuration_test.dart new file mode 100644 index 0000000..6bd26df --- /dev/null +++ b/test/gutter_configuration_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('GutterConfiguration provides data to descendants', + (WidgetTester tester) async { + final GutterConfigurationData testData = GutterConfigurationData( + widgetToAxis: (Widget widget) => widget is Text ? Axis.horizontal : null, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GutterConfiguration( + data: testData, + child: Builder( + builder: (BuildContext context) { + final GutterConfigurationData? data = + GutterConfiguration.maybeOf(context); + return Text(data == testData ? 'Found' : 'Not found'); + }, + ), + ), + ), + ); + + final Finder textFinder = find.text('Found'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('GutterConfiguration does not provide data to non-descendants', + (WidgetTester tester) async { + final GutterConfigurationData testData = GutterConfigurationData( + widgetToAxis: (Widget widget) => widget is Text ? Axis.horizontal : null, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + GutterConfiguration( + data: testData, + child: Container(), + ), + Builder( + builder: (BuildContext context) { + final GutterConfigurationData? data = + GutterConfiguration.maybeOf(context); + return Text(data == null ? 'Not found' : 'Found'); + }, + ), + ], + ), + ), + ); + + final Finder textFinder = find.text('Not found'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('GutterConfiguration updates data when widget changes', + (WidgetTester tester) async { + final GutterConfigurationData testData1 = GutterConfigurationData( + widgetToAxis: (Widget widget) => widget is Text ? Axis.horizontal : null, + ); + final GutterConfigurationData testData2 = GutterConfigurationData( + widgetToAxis: (Widget widget) => widget is Text ? Axis.vertical : null, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GutterConfiguration( + data: testData1, + child: Builder( + builder: (BuildContext context) { + final GutterConfigurationData? data = + GutterConfiguration.maybeOf(context); + return Text(data == testData1 ? 'First' : 'Second'); + }, + ), + ), + ), + ); + + final Finder textFinderFirst = find.text('First'); + expect(textFinderFirst, findsOneWidget); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GutterConfiguration( + data: testData2, + child: Builder( + builder: (BuildContext context) { + final GutterConfigurationData? data = + GutterConfiguration.maybeOf(context); + return Text(data == testData2 ? 'Second' : 'First'); + }, + ), + ), + ), + ); + + final Finder textFinderSecond = find.text('Second'); + expect(textFinderSecond, findsOneWidget); + }); + + testWidgets('GutterConfiguration respects dynamicAxisCheck', + (WidgetTester tester) async { + final GutterConfigurationData testData = GutterConfigurationData( + widgetToAxis: (Widget widget) => widget is Text ? Axis.horizontal : null, + dynamicAxisCheck: false, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GutterConfiguration( + data: testData, + child: Builder( + builder: (BuildContext context) { + final GutterConfigurationData? data = + GutterConfiguration.maybeOf(context); + return Text(data?.dynamicAxisCheck == false + ? 'Dynamic Check Off' + : 'Dynamic Check On'); + }, + ), + ), + ), + ); + + final Finder textFinder = find.text('Dynamic Check Off'); + expect(textFinder, findsOneWidget); + }); +} diff --git a/test/gutter_extensions_test.dart b/test/gutter_extensions_test.dart new file mode 100644 index 0000000..7866641 --- /dev/null +++ b/test/gutter_extensions_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('GutterExtensions', () { + testWidgets('gutterTiny returns correct size', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + final double gutterTiny = context.gutterTiny; + expect(gutterTiny, + equals(context.gutterSize(type: GutterType.tiny))); + return Container(); + }, + ), + ), + ); + }); + + testWidgets('gutterSmall returns correct size', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + final double gutterSmall = context.gutterSmall; + expect(gutterSmall, + equals(context.gutterSize(type: GutterType.small))); + return Container(); + }, + ), + ), + ); + }); + + testWidgets('gutterMedium returns correct size', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + final double gutterMedium = context.gutterMedium; + expect(gutterMedium, + equals(context.gutterSize(type: GutterType.medium))); + return Container(); + }, + ), + ), + ); + }); + + testWidgets('gutterLarge returns correct size', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + final double gutterLarge = context.gutterLarge; + expect(gutterLarge, + equals(context.gutterSize(type: GutterType.large))); + return Container(); + }, + ), + ), + ); + }); + + testWidgets('gutterExtraLarge returns correct size', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + final double gutterExtraLarge = context.gutterExtraLarge; + expect(gutterExtraLarge, + equals(context.gutterSize(type: GutterType.extraLarge))); + return Container(); + }, + ), + ), + ); + }); + }); +} diff --git a/test/gutter_margin_test.dart b/test/gutter_margin_test.dart new file mode 100644 index 0000000..6ab7085 --- /dev/null +++ b/test/gutter_margin_test.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Breakpoints + const Size small = Size(599, 800); // Breakpoints.small + const Size medium = Size(600, 800); // Breakpoints.medium + const Size large = Size(840, 1000); // Breakpoints.large + + // Helper function to reduce redundancy + Future testGutterMargin( + WidgetTester tester, Size size, GutterMargin gutterMargin) async { + tester.view.physicalSize = size; + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Column( + children: [gutterMargin], + ), + ), + )); + + final Element context = tester.element(find.byType(MaterialApp)); + final GutterMargin gutterWidget = + tester.widget(find.byType(GutterMargin)); + + final double expectedSize = context.gutterSize( + type: gutterWidget.type, + size: gutterWidget.size, + scaleFactor: gutterWidget.scaleFactor, + ); + + final Size gutterSize = tester.getSize(find.byType(GutterMargin)); + expect(gutterSize.height, expectedSize); + } + + // Tests for GutterMargin + testWidgets('should apply correct margin for GutterMargin on small screens', + (WidgetTester tester) async { + await testGutterMargin( + tester, small, const GutterMargin(type: GutterType.small)); + }); + + testWidgets('should apply correct margin for GutterMargin on medium screens', + (WidgetTester tester) async { + await testGutterMargin( + tester, medium, const GutterMargin(type: GutterType.small)); + }); + + testWidgets('should apply correct margin for GutterMargin on large screens', + (WidgetTester tester) async { + await testGutterMargin( + tester, large, const GutterMargin(type: GutterType.small)); + }); + + // Tests with custom size and scaleFactor + testWidgets( + 'should apply correct custom margin for GutterMargin on small screens', + (WidgetTester tester) async { + await testGutterMargin(tester, small, + const GutterMargin(type: GutterType.small, scaleFactor: 2)); + }); + + testWidgets( + 'should apply correct custom margin for GutterMargin on medium screens', + (WidgetTester tester) async { + await testGutterMargin(tester, medium, + const GutterMargin(type: GutterType.small, scaleFactor: 1.5)); + }); + + testWidgets( + 'should apply correct custom margin for GutterMargin on large screens', + (WidgetTester tester) async { + await testGutterMargin(tester, large, + const GutterMargin(type: GutterType.small, scaleFactor: 3)); + }); + + testWidgets( + 'should apply correct margin for GutterMargin with specific size on medium screens', + (WidgetTester tester) async { + await testGutterMargin(tester, medium, const GutterMargin.from(size: 20)); + }); + + testWidgets( + 'should apply correct margin for GutterMargin with specific size on medium screens', + (WidgetTester tester) async { + await testGutterMargin( + tester, medium, const GutterMargin.from(size: 30, scaleFactor: 1.2)); + }); + + testWidgets( + 'should apply correct margin for GutterMargin with specific size on large screens', + (WidgetTester tester) async { + await testGutterMargin( + tester, large, const GutterMargin.from(size: 40, scaleFactor: 2)); + }); + + testWidgets( + 'should apply correct margin for GutterMargin with specific size on small screens', + (WidgetTester tester) async { + await testGutterMargin(tester, small, const GutterMargin.from(size: 10)); + }); +} diff --git a/test/gutter_test.dart b/test/gutter_test.dart new file mode 100644 index 0000000..c542fb1 --- /dev/null +++ b/test/gutter_test.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Breakpoints + const Size small = Size(599, 800); // Breakpoints.small + const Size medium = Size(600, 800); // Breakpoints.medium + const Size large = Size(840, 1000); // Breakpoints.large + + // Helper function to reduce redundancy + Future testGutterSize( + WidgetTester tester, Size size, Gutter gutter) async { + tester.view.physicalSize = size; + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Column( + mainAxisSize: MainAxisSize.max, + children: [gutter], + ), + ), + )); + + final Element context = tester.element(find.byType(MaterialApp)); + final Gutter gutterWidget = tester.widget(find.byType(Gutter)); + + final double expectedSize = context.gutterSize( + type: gutterWidget.type, + size: gutterWidget.size, + scaleFactor: gutterWidget.scaleFactor, + ); + + final Size gutterSize = tester.getSize(find.byType(Gutter)); + expect(gutterSize.height, expectedSize); + } + + // Tests for Gutter.tiny + testWidgets('should apply correct gap size for tiny gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.tiny()); + }); + + testWidgets('should apply correct gap size for tiny gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.tiny()); + }); + + testWidgets('should apply correct gap size for tiny gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.tiny()); + }); + + // Tests for Gutter.small + testWidgets('should apply correct gap size for small gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.small()); + }); + + testWidgets( + 'should apply correct gap size for small gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.small()); + }); + + testWidgets('should apply correct gap size for small gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.small()); + }); + + // Tests for Gutter.medium + testWidgets( + 'should apply correct gap size for medium gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.medium()); + }); + + testWidgets( + 'should apply correct gap size for medium gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.medium()); + }); + + testWidgets( + 'should apply correct gap size for medium gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.medium()); + }); + + // Tests for Gutter.large + testWidgets('should apply correct gap size for large gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.large()); + }); + + testWidgets( + 'should apply correct gap size for large gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.large()); + }); + + testWidgets('should apply correct gap size for large gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.large()); + }); + + // Tests for Gutter.extraLarge + testWidgets( + 'should apply correct gap size for extraLarge gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.extraLarge()); + }); + + testWidgets( + 'should apply correct gap size for extraLarge gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.extraLarge()); + }); + + testWidgets( + 'should apply correct gap size for extraLarge gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.extraLarge()); + }); + + // Tests with custom size and scaleFactor + testWidgets( + 'should apply correct custom size for tiny gutter on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.tiny(scaleFactor: 2)); + }); + + testWidgets( + 'should apply correct custom size for small gutter on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.small(scaleFactor: 1.5)); + }); + + testWidgets( + 'should apply correct custom size for medium gutter on large screens', + (WidgetTester tester) async { + await testGutterSize(tester, large, const Gutter.medium(scaleFactor: 3)); + }); + + testWidgets( + 'should apply correct custom size for gutter with size on medium screens', + (WidgetTester tester) async { + await testGutterSize(tester, medium, const Gutter.from(size: 20)); + }); + + testWidgets( + 'should apply correct custom size for gutter with size on medium screens', + (WidgetTester tester) async { + await testGutterSize( + tester, medium, const Gutter.from(size: 30, scaleFactor: 1.2)); + }); + + testWidgets( + 'should apply correct custom size for gutter with size on large screens', + (WidgetTester tester) async { + await testGutterSize( + tester, large, const Gutter.from(size: 40, scaleFactor: 2)); + }); + + testWidgets( + 'should apply correct custom size for gutter with size on small screens', + (WidgetTester tester) async { + await testGutterSize(tester, small, const Gutter.from(size: 10)); + }); +}