diff --git a/lints_with_dcm.yaml b/lints_with_dcm.yaml index adcd7083b..05262ac5b 100644 --- a/lints_with_dcm.yaml +++ b/lints_with_dcm.yaml @@ -25,7 +25,12 @@ dart_code_metrics: prefer-prefixed-global-constants: false avoid-returning-widgets: false arguments-ordering: - child-last: true + first: + - key + - spec + last: + - child + - children avoid-nested-conditional-expressions: acceptable-level: 3 member-ordering: diff --git a/melos.yaml b/melos.yaml index 23d5690b3..6926ab284 100644 --- a/melos.yaml +++ b/melos.yaml @@ -30,11 +30,11 @@ scripts: failFast: true analyze:dart: - run: melos exec -c 10 -- dart analyze . + run: melos exec -c 4 -- dart analyze . description: Run Dart static analysis checks. analyze:dcm: - run: melos exec -c 10 -- dcm analyze . --fatal-style --fatal-performance --fatal-warnings + run: melos exec -c 4 -- dcm analyze . --fatal-style --fatal-performance --fatal-warnings description: Run DCM static analysis checks. packageFilters: dependsOn: "dart_code_metrics_presets" diff --git a/packages/mix/example/lib/main.dart b/packages/mix/example/lib/main.dart index d9e2c59d7..d3f9f813b 100644 --- a/packages/mix/example/lib/main.dart +++ b/packages/mix/example/lib/main.dart @@ -1,12 +1,8 @@ import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; -void main() async { - runApp( - const MaterialApp( - home: MyApp(), - ), - ); +void main() { + runApp(const MaterialApp(home: MyApp())); } final style = Style( @@ -17,11 +13,7 @@ final style = Style( $box.borderRadius(10), $box.padding(20, 10), $box.margin(10), - $box.border( - color: Colors.black, - width: 1, - style: BorderStyle.solid, - ), + $box.border(color: Colors.black, style: BorderStyle.solid, width: 1), ); class MyApp extends StatelessWidget { @@ -30,11 +22,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: Box( - style: style, - child: const Center(child: Text('Hello Mix')), + body: Center( + child: Box( + style: style, + child: const Center(child: Text('Hello Mix')), + ), ), - )); + ); } } diff --git a/packages/mix/lib/mix.dart b/packages/mix/lib/mix.dart index 1fda7fcbb..ef70982e7 100644 --- a/packages/mix/lib/mix.dart +++ b/packages/mix/lib/mix.dart @@ -1,15 +1,15 @@ -/// /\\\\ /\\\\ /\\\\\\\\\\\ /\\\ /\\\ -/// \/\\\\\\ /\\\\\\ \/////\\\/// \///\\\ /\\\/ -/// \/\\\//\\\ /\\\//\\\ \/\\\ \///\\\\\\/ -/// \/\\\\///\\\/\\\/ \/\\\ \/\\\ \//\\\\ -/// \/\\\ \///\\\/ \/\\\ \/\\\ \/\\\\ -/// \/\\\ \/// \/\\\ \/\\\ /\\\\\\ -/// \/\\\ \/\\\ \/\\\ /\\\////\\\ -/// \/\\\ \/\\\ /\\\\\\\\\\\ /\\\/ \///\\\ -/// \/// \/// \/////////// \/// \/// -/// -/// https://fluttermix.com +/// /\\\\ /\\\\ /\\\\\\\\\\\ /\\\ /\\\ +/// \/\\\\\\ /\\\\\\ \/////\\\/// \///\\\ /\\\/ +/// \/\\\//\\\ /\\\//\\\ \/\\\ \///\\\\\\/ +/// \/\\\\///\\\/\\\/ \/\\\ \/\\\ \//\\\\ +/// \/\\\ \///\\\/ \/\\\ \/\\\ \/\\\\ +/// \/\\\ \/// \/\\\ \/\\\ /\\\\\\ +/// \/\\\ \/\\\ \/\\\ /\\\////\\\ +/// \/\\\ \/\\\ /\\\\\\\\\\\ /\\\/ \///\\\ +/// \/// \/// \/////////// \/// \/// /// +/// https://fluttermix.com +/// /// /\///////////////////////////////////////////////////\ /// \/\ ***** GENERATED CODE ***** \ \ /// \/\ ** DO NOT EDIT THIS FILE ** \ \ @@ -54,7 +54,6 @@ export 'src/attributes/strut_style/strut_style_dto.dart'; export 'src/attributes/text_height_behavior/text_height_behavior_dto.dart'; export 'src/attributes/text_style/text_style_dto.dart'; export 'src/attributes/text_style/text_style_util.dart'; - /// CORE export 'src/core/attribute.dart'; export 'src/core/attributes_map.dart'; @@ -72,7 +71,6 @@ export 'src/core/styled_widget.dart'; export 'src/core/utility.dart'; export 'src/core/variant.dart'; export 'src/core/widget_state/widget_state_controller.dart'; - /// MODIFIERS export 'src/modifiers/align_widget_modifier.dart'; export 'src/modifiers/aspect_ratio_widget_modifier.dart'; @@ -89,12 +87,13 @@ export 'src/modifiers/sized_box_widget_modifier.dart'; export 'src/modifiers/transform_widget_modifier.dart'; export 'src/modifiers/visibility_widget_modifier.dart'; export 'src/modifiers/widget_modifiers_util.dart'; - /// SPECS export 'src/specs/box/box_spec.dart'; export 'src/specs/box/box_widget.dart'; export 'src/specs/flex/flex_spec.dart'; export 'src/specs/flex/flex_widget.dart'; +export 'src/specs/flexbox/flexbox_spec.dart'; +export 'src/specs/flexbox/flexbox_widget.dart'; export 'src/specs/icon/icon_spec.dart'; export 'src/specs/icon/icon_widget.dart'; export 'src/specs/image/image_spec.dart'; @@ -105,7 +104,6 @@ export 'src/specs/stack/stack_widget.dart'; export 'src/specs/text/text_directives_util.dart'; export 'src/specs/text/text_spec.dart'; export 'src/specs/text/text_widget.dart'; - /// THEME export 'src/theme/material/material_theme.dart'; export 'src/theme/material/material_tokens.dart'; @@ -118,7 +116,6 @@ export 'src/theme/tokens/space_token.dart'; export 'src/theme/tokens/text_style_token.dart'; export 'src/theme/tokens/token_resolver.dart'; export 'src/theme/tokens/token_util.dart'; - /// VARIANTS export 'src/variants/context_variant.dart'; export 'src/variants/context_variant_util/on_breakpoint_util.dart'; @@ -130,6 +127,5 @@ export 'src/variants/context_variant_util/on_platform_util.dart'; export 'src/variants/context_variant_util/on_util.dart'; export 'src/variants/variant_attribute.dart'; export 'src/variants/widget_state_variant.dart'; - /// WIDGETS export 'src/widgets/pressable_widget.dart'; diff --git a/packages/mix/lib/src/core/factory/style_widgets_ext.dart b/packages/mix/lib/src/core/factory/style_widgets_ext.dart index 843c2a700..d060e00d1 100644 --- a/packages/mix/lib/src/core/factory/style_widgets_ext.dart +++ b/packages/mix/lib/src/core/factory/style_widgets_ext.dart @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart'; import '../../specs/box/box_widget.dart'; import '../../specs/flex/flex_widget.dart'; +import '../../specs/flexbox/flexbox_widget.dart'; import '../../specs/icon/icon_widget.dart'; import '../../specs/text/text_widget.dart'; import 'style_mix.dart'; @@ -15,8 +16,8 @@ extension StyleExt on Style { Style? style, }) { return Box( - style: merge(style), key: key, + style: merge(style), inherit: inherit, child: child, ); @@ -28,7 +29,7 @@ extension StyleExt on Style { Key? key, Style? style, }) { - return container(inherit: inherit, key: key, style: style, child: child); + return container(key: key, inherit: inherit, style: style, child: child); } HBox hbox({ @@ -38,8 +39,8 @@ extension StyleExt on Style { Style? style, }) { return HBox( - style: merge(style), key: key, + style: merge(style), inherit: inherit, children: children, ); @@ -52,8 +53,8 @@ extension StyleExt on Style { Style? style, }) { return StyledRow( - style: merge(style), key: key, + style: merge(style), inherit: inherit, children: children, ); @@ -68,9 +69,9 @@ extension StyleExt on Style { }) { return StyledText( text, + key: key, semanticsLabel: semanticsLabel, style: merge(style), - key: key, inherit: inherit, ); } @@ -82,8 +83,8 @@ extension StyleExt on Style { Style? style, }) { return VBox( - style: merge(style), key: key, + style: merge(style), inherit: inherit, children: children, ); @@ -96,8 +97,8 @@ extension StyleExt on Style { Style? style, }) { return StyledColumn( - style: merge(style), key: key, + style: merge(style), inherit: inherit, children: children, ); @@ -109,6 +110,6 @@ extension StyleExt on Style { Key? key, Style? style, }) { - return StyledIcon(icon, style: merge(style), key: key, inherit: inherit); + return StyledIcon(icon, key: key, style: merge(style), inherit: inherit); } } diff --git a/packages/mix/lib/src/specs/box/box_widget.dart b/packages/mix/lib/src/specs/box/box_widget.dart index d5e8ab6f9..37b7b893b 100644 --- a/packages/mix/lib/src/specs/box/box_widget.dart +++ b/packages/mix/lib/src/specs/box/box_widget.dart @@ -74,8 +74,8 @@ class BoxSpecWidget extends StatelessWidget { @override Widget build(BuildContext context) { return RenderSpecModifiers( - orderOfModifiers: orderOfModifiers, spec: spec ?? const BoxSpec(), + orderOfModifiers: orderOfModifiers, child: Container( alignment: spec?.alignment, padding: spec?.padding, diff --git a/packages/mix/lib/src/specs/flex/flex_widget.dart b/packages/mix/lib/src/specs/flex/flex_widget.dart index a5ff1c2ff..ca9d5817e 100644 --- a/packages/mix/lib/src/specs/flex/flex_widget.dart +++ b/packages/mix/lib/src/specs/flex/flex_widget.dart @@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart'; import '../../core/styled_widget.dart'; import '../../modifiers/internal/render_widget_modifier.dart'; -import '../box/box_spec.dart'; import 'flex_spec.dart'; /// A flexible layout widget enhanced with `Style` for simplified styling. @@ -88,16 +87,19 @@ class FlexSpecWidget extends StatelessWidget { mainAxisSize: spec?.mainAxisSize ?? _defaultFlex.mainAxisSize, crossAxisAlignment: spec?.crossAxisAlignment ?? _defaultFlex.crossAxisAlignment, + textDirection: spec?.textDirection ?? _defaultFlex.textDirection, verticalDirection: spec?.verticalDirection ?? _defaultFlex.verticalDirection, + textBaseline: spec?.textBaseline ?? _defaultFlex.textBaseline, + clipBehavior: spec?.clipBehavior ?? _defaultFlex.clipBehavior, children: _buildChildren(gap), ); return spec == null ? flexWidget : RenderSpecModifiers( - orderOfModifiers: orderOfModifiers, spec: spec!, + orderOfModifiers: orderOfModifiers, child: flexWidget, ); } @@ -200,98 +202,4 @@ class StyledColumn extends StyledFlex { }) : super(direction: Axis.vertical); } -/// A flex container widget with integrated `Style` for enhanced styling. -/// -/// `FlexBox` combines the features of `StyledContainer` and `StyledFlex`, offering -/// a powerful tool for creating flexible layouts with advanced styling capabilities -/// through `Style`. It's perfect for designing complex layouts that require both -/// container and flex properties with uniform styling. -/// -/// The `direction` parameter sets the layout's orientation, while the `Style` -/// integration simplifies the process of applying consistent styles to all elements. -/// -/// Example Usage: -/// ```dart -/// FlexBox( -/// direction: Axis.horizontal, -/// style: yourStyle, -/// children: [Widget1(), Widget2()], -/// ); -/// ``` -class FlexBox extends StyledWidget { - const FlexBox({ - super.style, - super.key, - super.inherit, - required this.direction, - required this.children, - super.orderOfModifiers = const [], - }); - - final List children; - final Axis direction; - - @override - Widget build(BuildContext context) { - return withMix(context, (mix) { - final boxSpec = BoxSpec.of(mix); - final flexSpec = FlexSpec.of(mix); - - return boxSpec( - orderOfModifiers: orderOfModifiers, - child: flexSpec(direction: direction, children: children), - ); - }); - } -} - -/// A horizontal flex container with `Style` for easy and consistent styling. -/// -/// `HBox` is a specialized `FlexBox` designed for horizontal layouts, simplifying -/// the process of applying horizontal alignment with advanced styling via `Style`. -/// It's an efficient way to achieve consistent styling in horizontal arrangements. -/// -/// Inherits all functionalities of `FlexBox`, optimized for horizontal layouts. -/// -/// Example Usage: -/// ```dart -/// HBox( -/// style: yourStyle, -/// children: [Widget1(), Widget2()], -/// ); -/// ``` -class HBox extends FlexBox { - const HBox({ - super.style, - super.key, - super.inherit, - super.children = const [], - }) : super(direction: Axis.horizontal); -} - -/// A vertical flex container that uses `Style` for streamlined styling. -/// -/// `VBox` is a vertical counterpart to `HBox`, utilizing `Style` for efficient -/// and consistent styling in vertical layouts. It offers an easy way to manage -/// vertical alignment and styling in a cohesive manner. -/// -/// Inherits the comprehensive styling and layout capabilities of `FlexBox`, tailored -/// for vertical orientations. -/// -/// Example Usage: -/// ```dart -/// VBox( -/// style: yourStyle, -/// children: [Widget1(), Widget2()], -/// ); -/// ``` -class VBox extends FlexBox { - const VBox({ - super.style, - super.key, - super.inherit, - super.children = const [], - }) : super(direction: Axis.vertical); -} - final _defaultFlex = Flex(direction: Axis.horizontal, children: const []); diff --git a/packages/mix/lib/src/specs/flexbox/flexbox_spec.dart b/packages/mix/lib/src/specs/flexbox/flexbox_spec.dart new file mode 100644 index 000000000..07cb78f3e --- /dev/null +++ b/packages/mix/lib/src/specs/flexbox/flexbox_spec.dart @@ -0,0 +1,100 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mix_annotations/mix_annotations.dart'; + +import '../../attributes/animated/animated_data.dart'; +import '../../attributes/animated/animated_data_dto.dart'; +import '../../attributes/animated/animated_util.dart'; +import '../../attributes/modifiers/widget_modifiers_data.dart'; +import '../../attributes/modifiers/widget_modifiers_data_dto.dart'; +import '../../attributes/modifiers/widget_modifiers_util.dart'; +import '../../core/attribute.dart'; +import '../../core/factory/mix_data.dart'; +import '../../core/factory/mix_provider.dart'; +import '../../core/spec.dart'; +import '../box/box_spec.dart'; +import '../flex/flex_spec.dart'; +import 'flexbox_widget.dart'; + +part 'flexbox_spec.g.dart'; + +const _boxUtility = MixableUtility( + properties: [ + (path: 'alignment', alias: 'alignment'), + (path: 'padding', alias: 'padding'), + (path: 'margin', alias: 'margin'), + (path: 'constraints', alias: 'constraints'), + (path: 'constraints.minWidth', alias: 'minWidth'), + (path: 'constraints.maxWidth', alias: 'maxWidth'), + (path: 'constraints.minHeight', alias: 'minHeight'), + (path: 'constraints.maxHeight', alias: 'maxHeight'), + (path: 'decoration', alias: 'decoration'), + (path: 'decoration.color', alias: 'color'), + (path: 'decoration.border', alias: 'border'), + (path: 'decoration.border.directional', alias: 'borderDirectional'), + (path: 'decoration.borderRadius', alias: 'borderRadius'), + ( + path: 'decoration.borderRadius.directional', + alias: 'borderRadiusDirectional', + ), + (path: 'decoration.gradient', alias: 'gradient'), + (path: 'decoration.gradient.sweep', alias: 'sweepGradient'), + (path: 'decoration.gradient.radial', alias: 'radialGradient'), + (path: 'decoration.gradient.linear', alias: 'linearGradient'), + (path: 'decoration.boxShadows', alias: 'shadows'), + (path: 'decoration.boxShadow', alias: 'shadow'), + (path: 'decoration.elevation', alias: 'elevation'), + (path: 'shapeDecoration', alias: 'shapeDecoration'), + (path: 'shape', alias: 'shape'), + (path: 'foregroundDecoration', alias: 'foregroundDecoration'), + (path: 'transform', alias: 'transform'), + (path: 'transformAlignment', alias: 'transformAlignment'), + (path: 'clipBehavior', alias: 'clipBehavior'), + (path: 'width', alias: 'width'), + (path: 'height', alias: 'height'), + ], +); + +//TODO: Find a way to reuse as much code as possible from the FlexSpec and BoxSpec +@MixableSpec() +final class FlexBoxSpec extends Spec + with _$FlexBoxSpec, Diagnosticable { + @MixableProperty(utilities: [_boxUtility]) + final BoxSpec box; + + final FlexSpec flex; + + static const of = _$FlexBoxSpec.of; + static const from = _$FlexBoxSpec.from; + + const FlexBoxSpec({ + super.animated, + super.modifiers, + BoxSpec? box, + FlexSpec? flex, + }) : box = box ?? const BoxSpec(), + flex = flex ?? const FlexSpec(); + + Widget call({List children = const [], required Axis direction}) { + return (isAnimated) + ? AnimatedFlexBoxSpecWidget( + spec: this, + direction: direction, + curve: animated!.curve, + duration: animated!.duration, + onEnd: animated!.onEnd, + children: children, + ) + : FlexBoxSpecWidget( + spec: this, + direction: direction, + children: children, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + _debugFillProperties(properties); + } +} diff --git a/packages/mix/lib/src/specs/flexbox/flexbox_spec.g.dart b/packages/mix/lib/src/specs/flexbox/flexbox_spec.g.dart new file mode 100644 index 000000000..fcb879d8e --- /dev/null +++ b/packages/mix/lib/src/specs/flexbox/flexbox_spec.g.dart @@ -0,0 +1,337 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'flexbox_spec.dart'; + +// ************************************************************************** +// MixableSpecGenerator +// ************************************************************************** + +mixin _$FlexBoxSpec on Spec { + static FlexBoxSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const FlexBoxSpec(); + } + + /// {@template flex_box_spec_of} + /// Retrieves the [FlexBoxSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [FlexBoxSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [FlexBoxSpec]. + /// + /// Example: + /// + /// ```dart + /// final flexBoxSpec = FlexBoxSpec.of(context); + /// ``` + /// {@endtemplate} + static FlexBoxSpec of(BuildContext context) { + return _$FlexBoxSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [FlexBoxSpec] but with the given fields + /// replaced with the new values. + @override + FlexBoxSpec copyWith({ + AnimatedData? animated, + WidgetModifiersData? modifiers, + BoxSpec? box, + FlexSpec? flex, + }) { + return FlexBoxSpec( + animated: animated ?? _$this.animated, + modifiers: modifiers ?? _$this.modifiers, + box: box ?? _$this.box, + flex: flex ?? _$this.flex, + ); + } + + /// Linearly interpolates between this [FlexBoxSpec] and another [FlexBoxSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [FlexBoxSpec] is returned. When [t] is 1.0, the [other] [FlexBoxSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [FlexBoxSpec] is returned. + /// + /// If [other] is null, this method returns the current [FlexBoxSpec] instance. + /// + /// The interpolation is performed on each property of the [FlexBoxSpec] using the appropriate + /// interpolation method: + /// + + /// For [animated] and [modifiers] and [box] and [flex], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [FlexBoxSpec] is used. Otherwise, the value + /// from the [other] [FlexBoxSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [FlexBoxSpec] configurations. + @override + FlexBoxSpec lerp(FlexBoxSpec? other, double t) { + if (other == null) return _$this; + + return FlexBoxSpec( + animated: t < 0.5 ? _$this.animated : other.animated, + modifiers: other.modifiers, + box: _$this.box.lerp(other.box, t), + flex: _$this.flex.lerp(other.flex, t), + ); + } + + /// The list of properties that constitute the state of this [FlexBoxSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [FlexBoxSpec] instances for equality. + @override + List get props => [ + _$this.animated, + _$this.modifiers, + _$this.box, + _$this.flex, + ]; + + FlexBoxSpec get _$this => this as FlexBoxSpec; + + void _debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties.add( + DiagnosticsProperty('animated', _$this.animated, defaultValue: null)); + properties.add( + DiagnosticsProperty('modifiers', _$this.modifiers, defaultValue: null)); + properties.add(DiagnosticsProperty('box', _$this.box, defaultValue: null)); + properties + .add(DiagnosticsProperty('flex', _$this.flex, defaultValue: null)); + } +} + +/// Represents the attributes of a [FlexBoxSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [FlexBoxSpec]. +/// +/// Use this class to configure the attributes of a [FlexBoxSpec] and pass it to +/// the [FlexBoxSpec] constructor. +final class FlexBoxSpecAttribute extends SpecAttribute + with Diagnosticable { + final BoxSpecAttribute? box; + final FlexSpecAttribute? flex; + + const FlexBoxSpecAttribute({ + super.animated, + super.modifiers, + this.box, + this.flex, + }); + + /// Resolves to [FlexBoxSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final flexBoxSpec = FlexBoxSpecAttribute(...).resolve(mix); + /// ``` + @override + FlexBoxSpec resolve(MixData mix) { + return FlexBoxSpec( + animated: animated?.resolve(mix) ?? mix.animation, + modifiers: modifiers?.resolve(mix), + box: box?.resolve(mix), + flex: flex?.resolve(mix), + ); + } + + /// Merges the properties of this [FlexBoxSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [FlexBoxSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + FlexBoxSpecAttribute merge(FlexBoxSpecAttribute? other) { + if (other == null) return this; + + return FlexBoxSpecAttribute( + animated: animated?.merge(other.animated) ?? other.animated, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + box: box?.merge(other.box) ?? other.box, + flex: flex?.merge(other.flex) ?? other.flex, + ); + } + + /// The list of properties that constitute the state of this [FlexBoxSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [FlexBoxSpecAttribute] instances for equality. + @override + List get props => [ + animated, + modifiers, + box, + flex, + ]; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('animated', animated, defaultValue: null)); + properties + .add(DiagnosticsProperty('modifiers', modifiers, defaultValue: null)); + properties.add(DiagnosticsProperty('box', box, defaultValue: null)); + properties.add(DiagnosticsProperty('flex', flex, defaultValue: null)); + } +} + +/// Utility class for configuring [FlexBoxSpec] properties. +/// +/// This class provides methods to set individual properties of a [FlexBoxSpec]. +/// Use the methods of this class to configure specific properties of a [FlexBoxSpec]. +class FlexBoxSpecUtility + extends SpecUtility { + /// Utility for defining [FlexBoxSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + /// Utility for defining [FlexBoxSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [FlexBoxSpecAttribute.box] + late final box = BoxSpecUtility((v) => only(box: v)); + + /// Utility for defining [FlexBoxSpecAttribute.box.alignment] + late final alignment = box.alignment; + + /// Utility for defining [FlexBoxSpecAttribute.box.padding] + late final padding = box.padding; + + /// Utility for defining [FlexBoxSpecAttribute.box.margin] + late final margin = box.margin; + + /// Utility for defining [FlexBoxSpecAttribute.box.constraints] + late final constraints = box.constraints; + + /// Utility for defining [FlexBoxSpecAttribute.box.constraints.minWidth] + late final minWidth = box.constraints.minWidth; + + /// Utility for defining [FlexBoxSpecAttribute.box.constraints.maxWidth] + late final maxWidth = box.constraints.maxWidth; + + /// Utility for defining [FlexBoxSpecAttribute.box.constraints.minHeight] + late final minHeight = box.constraints.minHeight; + + /// Utility for defining [FlexBoxSpecAttribute.box.constraints.maxHeight] + late final maxHeight = box.constraints.maxHeight; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration] + late final decoration = box.decoration; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.color] + late final color = box.decoration.color; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.border] + late final border = box.decoration.border; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.border.directional] + late final borderDirectional = box.decoration.border.directional; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.borderRadius] + late final borderRadius = box.decoration.borderRadius; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.borderRadius.directional] + late final borderRadiusDirectional = box.decoration.borderRadius.directional; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.gradient] + late final gradient = box.decoration.gradient; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.gradient.sweep] + late final sweepGradient = box.decoration.gradient.sweep; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.gradient.radial] + late final radialGradient = box.decoration.gradient.radial; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.gradient.linear] + late final linearGradient = box.decoration.gradient.linear; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.boxShadows] + late final shadows = box.decoration.boxShadows; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.boxShadow] + late final shadow = box.decoration.boxShadow; + + /// Utility for defining [FlexBoxSpecAttribute.box.decoration.elevation] + late final elevation = box.decoration.elevation; + + /// Utility for defining [FlexBoxSpecAttribute.box.shapeDecoration] + late final shapeDecoration = box.shapeDecoration; + + /// Utility for defining [FlexBoxSpecAttribute.box.shape] + late final shape = box.shape; + + /// Utility for defining [FlexBoxSpecAttribute.box.foregroundDecoration] + late final foregroundDecoration = box.foregroundDecoration; + + /// Utility for defining [FlexBoxSpecAttribute.box.transform] + late final transform = box.transform; + + /// Utility for defining [FlexBoxSpecAttribute.box.transformAlignment] + late final transformAlignment = box.transformAlignment; + + /// Utility for defining [FlexBoxSpecAttribute.box.clipBehavior] + late final clipBehavior = box.clipBehavior; + + /// Utility for defining [FlexBoxSpecAttribute.box.width] + late final width = box.width; + + /// Utility for defining [FlexBoxSpecAttribute.box.height] + late final height = box.height; + + /// Utility for defining [FlexBoxSpecAttribute.flex] + late final flex = FlexSpecUtility((v) => only(flex: v)); + + FlexBoxSpecUtility(super.builder, {super.mutable}); + + FlexBoxSpecUtility get chain => + FlexBoxSpecUtility(attributeBuilder, mutable: true); + + static FlexBoxSpecUtility get self => + FlexBoxSpecUtility((v) => v); + + /// Returns a new [FlexBoxSpecAttribute] with the specified properties. + @override + T only({ + AnimatedDataDto? animated, + WidgetModifiersDataDto? modifiers, + BoxSpecAttribute? box, + FlexSpecAttribute? flex, + }) { + return builder(FlexBoxSpecAttribute( + animated: animated, + modifiers: modifiers, + box: box, + flex: flex, + )); + } +} + +/// A tween that interpolates between two [FlexBoxSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [FlexBoxSpec] specifications. +class FlexBoxSpecTween extends Tween { + FlexBoxSpecTween({ + super.begin, + super.end, + }); + + @override + FlexBoxSpec lerp(double t) { + if (begin == null && end == null) { + return const FlexBoxSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} diff --git a/packages/mix/lib/src/specs/flexbox/flexbox_widget.dart b/packages/mix/lib/src/specs/flexbox/flexbox_widget.dart new file mode 100644 index 000000000..db109b4bb --- /dev/null +++ b/packages/mix/lib/src/specs/flexbox/flexbox_widget.dart @@ -0,0 +1,196 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/widgets.dart'; + +import '../../core/factory/mix_provider.dart'; +import '../../core/styled_widget.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; +import '../box/box_spec.dart'; +import '../flex/flex_spec.dart'; +import 'flexbox_spec.dart'; + +/// A flex container widget with integrated `Style` for enhanced styling. +/// +/// `FlexBox` combines the features of `StyledContainer` and `StyledFlex`, offering +/// a powerful tool for creating flexible layouts with advanced styling capabilities +/// through `Style`. It's perfect for designing complex layouts that require both +/// container and flex properties with uniform styling. +/// +/// The `direction` parameter sets the layout's orientation, while the `Style` +/// integration simplifies the process of applying consistent styles to all elements. +/// +/// Example Usage: +/// ```dart +/// FlexBox( +/// direction: Axis.horizontal, +/// style: yourStyle, +/// children: [Widget1(), Widget2()], +/// ); +/// ``` +class FlexBox extends StyledWidget { + const FlexBox({ + super.style, + super.key, + super.inherit, + required this.direction, + required this.children, + super.orderOfModifiers = const [], + }); + + final List children; + final Axis direction; + + @override + Widget build(BuildContext context) { + // TODO: the support for FlexBoxSpec using BoxSpec and FlexSpec is a temporary + // solution to not break the existing API. it should be implemented using only + // FlexBoxSpec in the next major version. + return withMix(context, (context) { + final mixData = Mix.of(context); + + final spec = + mixData.attributeOf()?.resolve(mixData); + + final boxSpec = spec?.box ?? BoxSpec.of(context); + final flexSpec = spec?.flex ?? FlexSpec.of(context); + + final newSpec = FlexBoxSpec( + animated: spec?.animated, + modifiers: spec?.modifiers, + box: boxSpec, + flex: flexSpec, + ); + + return newSpec(direction: direction, children: children); + }); + } +} + +class FlexBoxSpecWidget extends StatelessWidget { + const FlexBoxSpecWidget({ + super.key, + this.spec, + required this.children, + required this.direction, + this.orderOfModifiers = const [], + }); + + final List children; + final Axis direction; + final FlexBoxSpec? spec; + final List orderOfModifiers; + + @override + Widget build(BuildContext context) { + final spec = this.spec ?? const FlexBoxSpec(); + + // TODO: Convert to a BoxSpecWidget and a FlexSpecWidget in the next major version. + // it should be implemented following the same pattern as the others SpecWidgets. + // This code must be like this to keep the existing animation API working. + return RenderSpecModifiers( + spec: spec, + orderOfModifiers: orderOfModifiers, + child: spec.box( + orderOfModifiers: orderOfModifiers, + child: spec.flex(direction: direction, children: children), + ), + ); + } +} + +class AnimatedFlexBoxSpecWidget extends ImplicitlyAnimatedWidget { + const AnimatedFlexBoxSpecWidget({ + super.key, + required this.spec, + required this.children, + required this.direction, + this.orderOfModifiers = const [], + super.curve, + required super.duration, + super.onEnd, + }); + + final FlexBoxSpec spec; + final List children; + final Axis direction; + final List orderOfModifiers; + + @override + AnimatedFlexBoxSpecWidgetState createState() => + AnimatedFlexBoxSpecWidgetState(); +} + +class AnimatedFlexBoxSpecWidgetState + extends AnimatedWidgetBaseState { + FlexBoxSpecTween? _specTween; + + @override + // ignore: avoid-dynamic + void forEachTween(TweenVisitor visitor) { + _specTween = visitor( + _specTween, + widget.spec, + // ignore: avoid-dynamic + (dynamic value) => FlexBoxSpecTween(begin: value as FlexBoxSpec), + ) as FlexBoxSpecTween?; + } + + @override + Widget build(BuildContext context) { + return FlexBoxSpecWidget( + spec: _specTween?.evaluate(animation), + direction: widget.direction, + orderOfModifiers: widget.orderOfModifiers, + children: widget.children, + ); + } +} + +/// A horizontal flex container with `Style` for easy and consistent styling. +/// +/// `HBox` is a specialized `FlexBox` designed for horizontal layouts, simplifying +/// the process of applying horizontal alignment with advanced styling via `Style`. +/// It's an efficient way to achieve consistent styling in horizontal arrangements. +/// +/// Inherits all functionalities of `FlexBox`, optimized for horizontal layouts. +/// +/// Example Usage: +/// ```dart +/// HBox( +/// style: yourStyle, +/// children: [Widget1(), Widget2()], +/// ); +/// ``` +class HBox extends FlexBox { + const HBox({ + super.style, + super.key, + super.inherit, + super.children = const [], + }) : super(direction: Axis.horizontal); +} + +/// A vertical flex container that uses `Style` for streamlined styling. +/// +/// `VBox` is a vertical counterpart to `HBox`, utilizing `Style` for efficient +/// and consistent styling in vertical layouts. It offers an easy way to manage +/// vertical alignment and styling in a cohesive manner. +/// +/// Inherits the comprehensive styling and layout capabilities of `FlexBox`, tailored +/// for vertical orientations. +/// +/// Example Usage: +/// ```dart +/// VBox( +/// style: yourStyle, +/// children: [Widget1(), Widget2()], +/// ); +/// ``` +class VBox extends FlexBox { + const VBox({ + super.style, + super.key, + super.inherit, + super.children = const [], + }) : super(direction: Axis.vertical); +} diff --git a/packages/mix/lib/src/specs/icon/icon_widget.dart b/packages/mix/lib/src/specs/icon/icon_widget.dart index dbd8f3193..195a29f11 100644 --- a/packages/mix/lib/src/specs/icon/icon_widget.dart +++ b/packages/mix/lib/src/specs/icon/icon_widget.dart @@ -75,8 +75,8 @@ class IconSpecWidget extends StatelessWidget { @override Widget build(BuildContext context) { return RenderSpecModifiers( - orderOfModifiers: orderOfModifiers, spec: spec ?? const IconSpec(), + orderOfModifiers: orderOfModifiers, child: Icon( icon, size: spec?.size, diff --git a/packages/mix/lib/src/specs/image/image_widget.dart b/packages/mix/lib/src/specs/image/image_widget.dart index 6f25d0543..cca7620d6 100644 --- a/packages/mix/lib/src/specs/image/image_widget.dart +++ b/packages/mix/lib/src/specs/image/image_widget.dart @@ -105,8 +105,8 @@ class ImageSpecWidget extends StatelessWidget { @override Widget build(BuildContext context) { return RenderSpecModifiers( - orderOfModifiers: orderOfModifiers, spec: spec ?? const ImageSpec(), + orderOfModifiers: orderOfModifiers, child: Image( image: image, frameBuilder: frameBuilder, diff --git a/packages/mix/lib/src/specs/spec_util.dart b/packages/mix/lib/src/specs/spec_util.dart index 600ba0d82..2f5938790 100644 --- a/packages/mix/lib/src/specs/spec_util.dart +++ b/packages/mix/lib/src/specs/spec_util.dart @@ -3,6 +3,7 @@ import '../modifiers/widget_modifiers_util.dart'; import '../variants/context_variant_util/on_util.dart'; import 'box/box_spec.dart'; import 'flex/flex_spec.dart'; +import 'flexbox/flexbox_spec.dart'; import 'icon/icon_spec.dart'; import 'image/image_spec.dart'; import 'stack/stack_spec.dart'; @@ -11,6 +12,7 @@ import 'text/text_spec.dart'; const _mixUtility = MixUtilities(); BoxSpecUtility get $box => _mixUtility.box; +FlexBoxSpecUtility get $flexbox => _mixUtility.flexbox; FlexSpecUtility get $flex => _mixUtility.flex; ImageSpecUtility get $image => _mixUtility.image; IconSpecUtility get $icon => _mixUtility.icon; @@ -23,6 +25,8 @@ class MixUtilities { const MixUtilities(); BoxSpecUtility get box => BoxSpecUtility.self; FlexSpecUtility get flex => FlexSpecUtility.self; + FlexBoxSpecUtility get flexbox => + FlexBoxSpecUtility.self; ImageSpecUtility get image => ImageSpecUtility.self; IconSpecUtility get icon => IconSpecUtility.self; TextSpecUtility get text => TextSpecUtility.self; diff --git a/packages/mix/lib/src/specs/stack/stack_widget.dart b/packages/mix/lib/src/specs/stack/stack_widget.dart index 187c82173..31853eb16 100644 --- a/packages/mix/lib/src/specs/stack/stack_widget.dart +++ b/packages/mix/lib/src/specs/stack/stack_widget.dart @@ -56,8 +56,8 @@ class StackSpecWidget extends StatelessWidget { Widget build(BuildContext context) { // The Stack widget is used here, applying the resolved styles from StackSpec. return RenderSpecModifiers( - orderOfModifiers: orderOfModifiers, spec: spec ?? const StackSpec(), + orderOfModifiers: orderOfModifiers, child: Stack( alignment: spec?.alignment ?? _defaultStack.alignment, textDirection: spec?.textDirection, diff --git a/packages/mix/lib/src/specs/text/text_widget.dart b/packages/mix/lib/src/specs/text/text_widget.dart index 75de9aeb2..97db1a0be 100644 --- a/packages/mix/lib/src/specs/text/text_widget.dart +++ b/packages/mix/lib/src/specs/text/text_widget.dart @@ -92,8 +92,8 @@ class TextSpecWidget extends StatelessWidget { Widget build(BuildContext context) { // The Text widget is used here, applying the resolved styles and properties from TextSpec. return RenderSpecModifiers( - orderOfModifiers: const [], spec: spec ?? const TextSpec(), + orderOfModifiers: const [], child: Text( spec?.directive?.apply(text) ?? text, style: spec?.style, diff --git a/packages/mix/lib/src/theme/tokens/breakpoints_token.dart b/packages/mix/lib/src/theme/tokens/breakpoints_token.dart index dcad98970..22ea3d456 100644 --- a/packages/mix/lib/src/theme/tokens/breakpoints_token.dart +++ b/packages/mix/lib/src/theme/tokens/breakpoints_token.dart @@ -73,7 +73,7 @@ class BreakpointToken extends MixToken { return themeValue is BreakpointResolver ? themeValue.resolve(context) - : themeValue ?? const Breakpoint(); + : (themeValue ?? const Breakpoint()); } } diff --git a/packages/mix/lib/src/theme/tokens/color_token.dart b/packages/mix/lib/src/theme/tokens/color_token.dart index f2724083e..18a424d5b 100644 --- a/packages/mix/lib/src/theme/tokens/color_token.dart +++ b/packages/mix/lib/src/theme/tokens/color_token.dart @@ -32,7 +32,7 @@ class ColorToken extends MixToken { return themeValue is ColorResolver ? themeValue.resolve(context) - : themeValue ?? Colors.transparent; + : (themeValue ?? Colors.transparent); } } diff --git a/packages/mix/lib/src/widgets/pressable_widget.dart b/packages/mix/lib/src/widgets/pressable_widget.dart index 228a00236..ff13307b8 100644 --- a/packages/mix/lib/src/widgets/pressable_widget.dart +++ b/packages/mix/lib/src/widgets/pressable_widget.dart @@ -71,7 +71,6 @@ class PressableBox extends StatelessWidget { class Pressable extends StatefulWidget { const Pressable({ super.key, - required this.child, this.enabled = true, this.enableFeedback = false, this.onPress, @@ -89,6 +88,7 @@ class Pressable extends StatefulWidget { this.unpressDelay = kDefaultAnimationDuration, this.controller, this.actions, + required this.child, }); final Widget child; diff --git a/packages/mix/test/src/specs/flex/flex_widget_test.dart b/packages/mix/test/src/specs/flex/flex_widget_test.dart index 7d136f94b..a6e962e1b 100644 --- a/packages/mix/test/src/specs/flex/flex_widget_test.dart +++ b/packages/mix/test/src/specs/flex/flex_widget_test.dart @@ -6,6 +6,53 @@ import '../../../helpers/override_modifiers_order.dart'; import '../../../helpers/testing_utils.dart'; void main() { + testWidgets('FlexSpec properties should match Flex properties', + (WidgetTester tester) async { + const flexSpec = FlexSpec( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + clipBehavior: Clip.antiAlias, + gap: 16, + ); + + const flexKey = Key('flex'); + const flexWidget = FlexSpecWidget( + key: flexKey, + spec: flexSpec, + direction: Axis.horizontal, + children: [Text('test')], + ); + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: flexWidget, + ), + ), + ); + + final flexFinder = find.byKey(flexKey); + expect(flexFinder, findsOneWidget); + + final flex = tester.widget(find.descendant( + of: flexFinder, + matching: find.byType(Flex), + )); + + expect(flex.direction, flexSpec.direction); + expect(flex.mainAxisAlignment, flexSpec.mainAxisAlignment); + expect(flex.crossAxisAlignment, flexSpec.crossAxisAlignment); + expect(flex.mainAxisSize, flexSpec.mainAxisSize); + expect(flex.verticalDirection, flexSpec.verticalDirection); + expect(flex.textDirection, flexSpec.textDirection); + expect(flex.textBaseline, flexSpec.textBaseline); + }); + group('FlexSpecWidget', () { testWidgets('prioritizes the direction in spec', (tester) async { await tester.pumpMaterialApp( diff --git a/packages/mix/test/src/specs/flexbox/flexbox_attribute_test.dart b/packages/mix/test/src/specs/flexbox/flexbox_attribute_test.dart new file mode 100644 index 000000000..49ec6e5db --- /dev/null +++ b/packages/mix/test/src/specs/flexbox/flexbox_attribute_test.dart @@ -0,0 +1,407 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; + +import '../../../helpers/testing_utils.dart'; + +void main() { + group('FlexBoxSpecAttribute', () { + test('Constructor assigns correct properties', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + modifiers: const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ]), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + expect(flexBoxSpecAttribute.box!.alignment, Alignment.center); + expect(flexBoxSpecAttribute.box!.clipBehavior, Clip.antiAlias); + + expect( + flexBoxSpecAttribute.box!.constraints, + const BoxConstraintsDto(maxHeight: 100), + ); + expect( + flexBoxSpecAttribute.box!.decoration, + const BoxDecorationDto(color: ColorDto(Colors.blue)), + ); + + expect(flexBoxSpecAttribute.box!.height, 100); + expect( + flexBoxSpecAttribute.box!.margin, + SpacingDto.only(top: 10, bottom: 10, left: 10, right: 10), + ); + expect( + flexBoxSpecAttribute.box!.padding, + SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + ); + expect(flexBoxSpecAttribute.box!.transform, Matrix4.identity()); + expect(flexBoxSpecAttribute.box!.width, 100); + expect( + flexBoxSpecAttribute.box!.modifiers, + const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ])); + + expect(flexBoxSpecAttribute.flex!.mainAxisAlignment, + MainAxisAlignment.center); + expect(flexBoxSpecAttribute.flex!.crossAxisAlignment, + CrossAxisAlignment.center); + expect(flexBoxSpecAttribute.flex!.mainAxisSize, MainAxisSize.max); + expect( + flexBoxSpecAttribute.flex!.verticalDirection, VerticalDirection.down); + expect(flexBoxSpecAttribute.flex!.textDirection, TextDirection.ltr); + expect(flexBoxSpecAttribute.flex!.textBaseline, TextBaseline.alphabetic); + }); + + // resolve() + test('resolve() returns correct instance', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + modifiers: const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ]), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + final flexBoxSpec = flexBoxSpecAttribute.resolve(EmptyMixData); + + expect(flexBoxSpec.box.alignment, Alignment.center); + expect(flexBoxSpec.box.clipBehavior, Clip.antiAlias); + + expect( + flexBoxSpec.box.constraints, + const BoxConstraints(maxWidth: double.infinity, maxHeight: 100), + ); + expect( + flexBoxSpec.box.decoration, const BoxDecoration(color: Colors.blue)); + + expect(flexBoxSpec.box.height, 100); + expect( + flexBoxSpec.box.margin, + const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10), + ); + expect( + flexBoxSpec.box.padding, + const EdgeInsets.only(left: 20, top: 20, right: 20, bottom: 20), + ); + expect(flexBoxSpec.box.transform, Matrix4.identity()); + expect(flexBoxSpec.box.width, 100); + expect(flexBoxSpec.box.modifiers!.value, [ + const OpacityModifierSpec(0.5), + const SizedBoxModifierSpec(height: 10, width: 10), + ]); + + expect(flexBoxSpec.flex.mainAxisAlignment, MainAxisAlignment.center); + expect(flexBoxSpec.flex.crossAxisAlignment, CrossAxisAlignment.center); + expect(flexBoxSpec.flex.mainAxisSize, MainAxisSize.max); + expect(flexBoxSpec.flex.verticalDirection, VerticalDirection.down); + expect(flexBoxSpec.flex.textDirection, TextDirection.ltr); + expect(flexBoxSpec.flex.textBaseline, TextBaseline.alphabetic); + }); + + // merge() + test('merge() returns correct instance', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + modifiers: const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ]), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + final mergedFlexBoxSpecAttribute = flexBoxSpecAttribute.merge( + FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.centerLeft, + padding: SpacingDto.only(top: 30, bottom: 30, left: 30, right: 30), + margin: SpacingDto.only( + top: 20, + bottom: 20, + left: 20, + right: 20, + ), + constraints: const BoxConstraintsDto(maxHeight: 200), + decoration: const BoxDecorationDto(color: ColorDto(Colors.red)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAliasWithSaveLayer, + width: 200, + height: 200, + modifiers: const WidgetModifiersDataDto([ + SizedBoxModifierSpecAttribute(width: 20), + ]), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.up, + textDirection: TextDirection.rtl, + textBaseline: TextBaseline.ideographic, + ), + ), + ); + + expect(mergedFlexBoxSpecAttribute.box!.alignment, Alignment.centerLeft); + expect(mergedFlexBoxSpecAttribute.box!.clipBehavior, + Clip.antiAliasWithSaveLayer); + + expect( + mergedFlexBoxSpecAttribute.box!.constraints, + const BoxConstraintsDto(maxHeight: 200), + ); + expect( + mergedFlexBoxSpecAttribute.box!.decoration, + const BoxDecorationDto(color: ColorDto(Colors.red)), + ); + + expect(mergedFlexBoxSpecAttribute.box!.height, 200); + expect( + mergedFlexBoxSpecAttribute.box!.margin, + SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + ); + expect( + mergedFlexBoxSpecAttribute.box!.padding, + SpacingDto.only(top: 30, bottom: 30, left: 30, right: 30), + ); + expect(mergedFlexBoxSpecAttribute.box!.transform, Matrix4.identity()); + expect(mergedFlexBoxSpecAttribute.box!.width, 200); + expect( + mergedFlexBoxSpecAttribute.box!.modifiers, + const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 20), + ])); + + expect(mergedFlexBoxSpecAttribute.flex!.mainAxisAlignment, + MainAxisAlignment.start); + expect(mergedFlexBoxSpecAttribute.flex!.crossAxisAlignment, + CrossAxisAlignment.start); + expect(mergedFlexBoxSpecAttribute.flex!.mainAxisSize, MainAxisSize.min); + expect(mergedFlexBoxSpecAttribute.flex!.verticalDirection, + VerticalDirection.up); + expect(mergedFlexBoxSpecAttribute.flex!.textDirection, TextDirection.rtl); + expect(mergedFlexBoxSpecAttribute.flex!.textBaseline, + TextBaseline.ideographic); + }); + + // equality + test('equality', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + modifiers: const WidgetModifiersDataDto([ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ]), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + expect( + flexBoxSpecAttribute, + equals( + FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: + SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + modifiers: const WidgetModifiersDataDto( + [ + OpacityModifierSpecAttribute(opacity: 0.5), + SizedBoxModifierSpecAttribute(height: 10, width: 10), + ], + ), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ); + }); + + // not equals + test('not equals', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + expect( + flexBoxSpecAttribute, + isNot( + equals( + FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.centerLeft, + padding: SpacingDto.only( + top: 30, + bottom: 30, + left: 30, + right: 30, + ), + margin: SpacingDto.only( + top: 20, + bottom: 20, + left: 20, + right: 20, + ), + constraints: const BoxConstraintsDto(maxHeight: 200), + decoration: const BoxDecorationDto(color: ColorDto(Colors.red)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAliasWithSaveLayer, + width: 200, + height: 200, + modifiers: const WidgetModifiersDataDto( + [ + OpacityModifierSpecAttribute(opacity: 0.4), + SizedBoxModifierSpecAttribute(height: 20, width: 10), + ], + ), + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.up, + textDirection: TextDirection.rtl, + textBaseline: TextBaseline.ideographic, + ), + ), + ), + ), + ); + }); + }); +} diff --git a/packages/mix/test/src/specs/flexbox/flexbox_spec_test.dart b/packages/mix/test/src/specs/flexbox/flexbox_spec_test.dart new file mode 100644 index 000000000..6ce9d9449 --- /dev/null +++ b/packages/mix/test/src/specs/flexbox/flexbox_spec_test.dart @@ -0,0 +1,424 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; + +import '../../../helpers/testing_utils.dart'; + +void main() { + group('FlexBoxSpec', () { + test('resolve', () { + final mix = MixData.create( + MockBuildContext(), + Style( + FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 8, bottom: 16), + margin: SpacingDto.only(top: 10.0, bottom: 12.0), + constraints: + const BoxConstraintsDto(maxWidth: 300.0, minHeight: 200.0), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.translationValues(10.0, 10.0, 0.0), + clipBehavior: Clip.antiAlias, + width: 300, + height: 200, + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ); + + final spec = mix.attributeOf()!.resolve(mix); + + expect(spec.box.alignment, Alignment.center); + expect(spec.box.padding, const EdgeInsets.only(top: 8.0, bottom: 16.0)); + expect(spec.box.margin, const EdgeInsets.only(top: 10.0, bottom: 12.0)); + expect( + spec.box.constraints, + const BoxConstraints(maxWidth: 300.0, minHeight: 200.0), + ); + expect(spec.box.decoration, const BoxDecoration(color: Colors.blue)); + expect(spec.box.transform, Matrix4.translationValues(10.0, 10.0, 0.0)); + expect(spec.box.clipBehavior, Clip.antiAlias); + expect(spec.box.width, 300); + expect(spec.box.height, 200); + + expect(spec.flex.mainAxisAlignment, MainAxisAlignment.center); + expect(spec.flex.crossAxisAlignment, CrossAxisAlignment.center); + expect(spec.flex.mainAxisSize, MainAxisSize.max); + expect(spec.flex.verticalDirection, VerticalDirection.down); + expect(spec.flex.textDirection, TextDirection.ltr); + expect(spec.flex.textBaseline, TextBaseline.alphabetic); + }); + + test('copyWith', () { + final spec = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.center, + padding: const EdgeInsets.all(16.0), + margin: const EdgeInsets.only(top: 8.0, bottom: 8.0), + constraints: const BoxConstraints(maxWidth: 300.0, minHeight: 200.0), + decoration: const BoxDecoration(color: Colors.blue), + transform: Matrix4.translationValues(10.0, 10.0, 0.0), + clipBehavior: Clip.antiAlias, + width: 300, + height: 200, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + final copiedSpec = spec.copyWith( + box: spec.box.copyWith( + width: 250.0, + height: 150.0, + ), + flex: spec.flex.copyWith( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + ); + + expect(copiedSpec.box.alignment, Alignment.center); + expect(copiedSpec.box.padding, const EdgeInsets.all(16.0)); + expect( + copiedSpec.box.margin, const EdgeInsets.only(top: 8.0, bottom: 8.0)); + expect(copiedSpec.box.width, 250.0); + expect(copiedSpec.box.height, 150.0); + + expect(copiedSpec.flex.mainAxisAlignment, MainAxisAlignment.start); + expect(copiedSpec.flex.crossAxisAlignment, CrossAxisAlignment.start); + expect(copiedSpec.flex.mainAxisSize, MainAxisSize.max); + expect(copiedSpec.flex.verticalDirection, VerticalDirection.down); + expect(copiedSpec.flex.textDirection, TextDirection.ltr); + expect(copiedSpec.flex.textBaseline, TextBaseline.alphabetic); + }); + + test('lerp', () { + final spec1 = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(8.0), + margin: const EdgeInsets.only(top: 4.0), + constraints: const BoxConstraints(maxWidth: 200.0), + decoration: const BoxDecoration(color: Colors.red), + transform: Matrix4.identity(), + clipBehavior: Clip.none, + width: 300, + height: 200, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + ), + ); + + final spec2 = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.bottomRight, + padding: const EdgeInsets.all(16.0), + margin: const EdgeInsets.only(top: 8.0), + constraints: const BoxConstraints(maxWidth: 400.0), + decoration: const BoxDecoration(color: Colors.blue), + transform: Matrix4.rotationZ(0.5), + clipBehavior: Clip.antiAlias, + width: 400, + height: 300, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + ), + ); + + const t = 0.5; + final lerpedSpec = spec1.lerp(spec2, t); + + expect( + lerpedSpec.box.alignment, + Alignment.lerp(Alignment.topLeft, Alignment.bottomRight, t), + ); + expect( + lerpedSpec.box.padding, + EdgeInsets.lerp( + const EdgeInsets.all(8.0), + const EdgeInsets.all(16.0), + t, + ), + ); + expect(lerpedSpec.box.width, lerpDouble(300, 400, t)); + expect(lerpedSpec.box.height, lerpDouble(200, 300, t)); + + expect(lerpedSpec.flex.mainAxisAlignment, MainAxisAlignment.end); + expect(lerpedSpec.flex.crossAxisAlignment, CrossAxisAlignment.end); + expect(lerpedSpec.flex.mainAxisSize, MainAxisSize.max); + }); + + test('equality', () { + final spec1 = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(8.0), + margin: const EdgeInsets.only(top: 4.0), + constraints: const BoxConstraints(maxWidth: 200.0), + decoration: const BoxDecoration(color: Colors.red), + transform: Matrix4.identity(), + clipBehavior: Clip.none, + width: 300, + height: 200, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + ), + ); + + final spec2 = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(8.0), + margin: const EdgeInsets.only(top: 4.0), + constraints: const BoxConstraints(maxWidth: 200.0), + decoration: const BoxDecoration(color: Colors.red), + transform: Matrix4.identity(), + clipBehavior: Clip.none, + width: 300, + height: 200, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + ), + ); + + expect(spec1, spec2); + }); + + test('merge() returns correct instance', () { + final flexBoxSpecAttribute = FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.center, + padding: SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + margin: SpacingDto.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), + constraints: const BoxConstraintsDto(maxHeight: 100), + decoration: const BoxDecorationDto(color: ColorDto(Colors.blue)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAlias, + width: 100, + height: 100, + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + final mergedFlexBoxSpecAttribute = flexBoxSpecAttribute.merge( + FlexBoxSpecAttribute( + box: BoxSpecAttribute( + alignment: Alignment.centerLeft, + padding: SpacingDto.only(top: 30, bottom: 30, left: 30, right: 30), + margin: SpacingDto.only( + top: 20, + bottom: 20, + left: 20, + right: 20, + ), + constraints: const BoxConstraintsDto(maxHeight: 200), + decoration: const BoxDecorationDto(color: ColorDto(Colors.red)), + transform: Matrix4.identity(), + clipBehavior: Clip.antiAliasWithSaveLayer, + width: 200, + height: 200, + ), + flex: const FlexSpecAttribute( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.up, + textDirection: TextDirection.rtl, + textBaseline: TextBaseline.ideographic, + ), + ), + ); + + expect(mergedFlexBoxSpecAttribute.box!.alignment, Alignment.centerLeft); + expect(mergedFlexBoxSpecAttribute.box!.clipBehavior, + Clip.antiAliasWithSaveLayer); + expect(mergedFlexBoxSpecAttribute.box!.constraints, + const BoxConstraintsDto(maxHeight: 200)); + expect(mergedFlexBoxSpecAttribute.box!.decoration, + const BoxDecorationDto(color: ColorDto(Colors.red))); + expect(mergedFlexBoxSpecAttribute.box!.height, 200); + expect( + mergedFlexBoxSpecAttribute.box!.margin, + SpacingDto.only(top: 20, bottom: 20, left: 20, right: 20), + ); + expect( + mergedFlexBoxSpecAttribute.box!.padding, + SpacingDto.only(top: 30, bottom: 30, left: 30, right: 30), + ); + expect(mergedFlexBoxSpecAttribute.box!.transform, Matrix4.identity()); + expect(mergedFlexBoxSpecAttribute.box!.width, 200); + + expect(mergedFlexBoxSpecAttribute.flex!.mainAxisAlignment, + MainAxisAlignment.start); + expect(mergedFlexBoxSpecAttribute.flex!.crossAxisAlignment, + CrossAxisAlignment.start); + expect(mergedFlexBoxSpecAttribute.flex!.mainAxisSize, MainAxisSize.min); + expect(mergedFlexBoxSpecAttribute.flex!.verticalDirection, + VerticalDirection.up); + expect(mergedFlexBoxSpecAttribute.flex!.textDirection, TextDirection.rtl); + expect(mergedFlexBoxSpecAttribute.flex!.textBaseline, + TextBaseline.ideographic); + }); + }); + + group('FlexBoxSpecUtility fluent', () { + test('fluent behavior', () { + final flexBox = FlexBoxSpecUtility.self; + + final util = flexBox.chain + ..box.alignment.center() + ..box.padding(8) + ..flex.mainAxisAlignment.center() + ..flex.crossAxisAlignment.center(); + + final attr = util.attributeValue!; + + expect(util, isA()); + expect(attr.box!.alignment, Alignment.center); + expect(attr.box!.padding, const EdgeInsets.all(8.0).toDto()); + expect(attr.box!.margin, null); + expect(attr.flex!.mainAxisAlignment, MainAxisAlignment.center); + expect(attr.flex!.crossAxisAlignment, CrossAxisAlignment.center); + + final style = Style(util); + + final flexBoxAttribute = + style.styles.attributeOfType(); + + expect(flexBoxAttribute?.box!.alignment, Alignment.center); + expect(flexBoxAttribute?.box!.padding, const EdgeInsets.all(8.0).toDto()); + expect(flexBoxAttribute?.box!.margin, null); + expect( + flexBoxAttribute?.flex!.mainAxisAlignment, MainAxisAlignment.center); + expect(flexBoxAttribute?.flex!.crossAxisAlignment, + CrossAxisAlignment.center); + + final mixData = style.of(MockBuildContext()); + final flexBoxSpec = FlexBoxSpec.from(mixData); + + expect(flexBoxSpec.box.alignment, Alignment.center); + expect(flexBoxSpec.box.padding, const EdgeInsets.all(8.0)); + expect(flexBoxSpec.box.margin, null); + expect(flexBoxSpec.flex.mainAxisAlignment, MainAxisAlignment.center); + expect(flexBoxSpec.flex.crossAxisAlignment, CrossAxisAlignment.center); + }); + + test('Immutable behavior when having multiple flexboxes', () { + final flexBoxUtil = FlexBoxSpecUtility.self; + final flexBox1 = flexBoxUtil.chain + ..box.padding(10) + ..flex.mainAxisAlignment.start(); + final flexBox2 = flexBoxUtil.chain + ..box.padding(20) + ..flex.mainAxisAlignment.end(); + + final attr1 = flexBox1.attributeValue!; + final attr2 = flexBox2.attributeValue!; + + expect(attr1.box!.padding, const EdgeInsets.all(10.0).toDto()); + expect(attr2.box!.padding, const EdgeInsets.all(20.0).toDto()); + expect(attr1.flex!.mainAxisAlignment, MainAxisAlignment.start); + expect(attr2.flex!.mainAxisAlignment, MainAxisAlignment.end); + + final style1 = Style(flexBox1); + final style2 = Style(flexBox2); + + final flexBoxAttribute1 = + style1.styles.attributeOfType(); + final flexBoxAttribute2 = + style2.styles.attributeOfType(); + + expect( + flexBoxAttribute1?.box!.padding, const EdgeInsets.all(10.0).toDto()); + expect( + flexBoxAttribute2?.box!.padding, const EdgeInsets.all(20.0).toDto()); + expect( + flexBoxAttribute1?.flex!.mainAxisAlignment, MainAxisAlignment.start); + expect(flexBoxAttribute2?.flex!.mainAxisAlignment, MainAxisAlignment.end); + + final mixData1 = style1.of(MockBuildContext()); + final mixData2 = style2.of(MockBuildContext()); + + final flexBoxSpec1 = FlexBoxSpec.from(mixData1); + final flexBoxSpec2 = FlexBoxSpec.from(mixData2); + + expect(flexBoxSpec1.box.padding, const EdgeInsets.all(10.0)); + expect(flexBoxSpec2.box.padding, const EdgeInsets.all(20.0)); + expect(flexBoxSpec1.flex.mainAxisAlignment, MainAxisAlignment.start); + expect(flexBoxSpec2.flex.mainAxisAlignment, MainAxisAlignment.end); + }); + + test('Mutate behavior and not on same utility', () { + final flexBox = FlexBoxSpecUtility.self; + + final flexBoxValue = flexBox.chain; + flexBoxValue + ..box.padding(10) + ..box.color.red() + ..box.alignment.center() + ..flex.mainAxisAlignment.center() + ..flex.crossAxisAlignment.center(); + + final flexBoxAttribute = flexBoxValue.attributeValue!; + final flexBoxAttribute2 = flexBox.box.padding(20); + + expect(flexBoxAttribute.box!.padding, const EdgeInsets.all(10.0).toDto()); + expect( + (flexBoxAttribute.box!.decoration as BoxDecorationDto).color, + const ColorDto(Colors.red), + ); + expect(flexBoxAttribute.box!.alignment, Alignment.center); + expect( + flexBoxAttribute.flex!.mainAxisAlignment, MainAxisAlignment.center); + expect( + flexBoxAttribute.flex!.crossAxisAlignment, CrossAxisAlignment.center); + + expect( + flexBoxAttribute2.box!.padding, const EdgeInsets.all(20.0).toDto()); + expect((flexBoxAttribute2.box!.decoration as BoxDecorationDto?)?.color, + isNull); + expect(flexBoxAttribute2.box!.alignment, isNull); + }); + }); +} diff --git a/packages/mix/test/src/specs/flexbox/flexbox_util_test.dart b/packages/mix/test/src/specs/flexbox/flexbox_util_test.dart new file mode 100644 index 000000000..6f04bb1a5 --- /dev/null +++ b/packages/mix/test/src/specs/flexbox/flexbox_util_test.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; + +const $testvariant = Variant('test'); +void main() { + group('FlexBoxUtility', () { + final flexBoxUtility = FlexBoxSpecUtility.self; + + test('call() returns correct instance', () { + final flexBox = flexBoxUtility.chain + ..box.alignment.center() + ..box.padding(10) + ..box.margin(10) + ..box.constraints.maxWidth(200) + ..box.width(10) + ..box.height(10) + ..box.transform(Matrix4.identity()) + ..box.clipBehavior.antiAlias() + ..flex.mainAxisAlignment.center() + ..flex.crossAxisAlignment.center(); + + final attr = flexBox.attributeValue!; + + expect(attr.box!.alignment, Alignment.center); + expect(attr.box!.clipBehavior, Clip.antiAlias); + expect(attr.box!.constraints!.maxWidth, 200); + expect(attr.box!.height, 10); + expect(attr.box!.margin, const EdgeInsets.all(10).toDto()); + expect(attr.box!.padding, const EdgeInsets.all(10).toDto()); + expect(attr.box!.transform, Matrix4.identity()); + expect(attr.box!.width, 10); + expect(attr.flex!.mainAxisAlignment, MainAxisAlignment.center); + expect(attr.flex!.crossAxisAlignment, CrossAxisAlignment.center); + }); + + test('box alignment returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.alignment.center(); + expect(flexBox.attributeValue!.box!.alignment, Alignment.center); + }); + + test('box clipBehavior returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.clipBehavior.antiAlias(); + expect(flexBox.attributeValue!.box!.clipBehavior, Clip.antiAlias); + }); + + test('box color returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.color.blue(); + expect( + (flexBox.attributeValue!.box!.decoration as BoxDecorationDto).color, + const ColorDto(Colors.blue), + ); + }); + + test('box constraints returns correct instance', () { + expect(flexBoxUtility.box.constraints, isA()); + }); + + test('box shape returns correct instance', () { + expect(flexBoxUtility.box.shapeDecoration, isA()); + }); + + test('box height returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.height(10); + expect(flexBox.attributeValue!.box!.height, 10); + }); + + test('box margin returns correct instance', () { + expect(flexBoxUtility.box.margin, isA()); + }); + + test('box padding returns correct instance', () { + expect(flexBoxUtility.box.padding, isA()); + }); + + test('box transform returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.transform(Matrix4.identity()); + expect(flexBox.attributeValue!.box!.transform, Matrix4.identity()); + }); + + test('box width returns correct instance', () { + final flexBox = flexBoxUtility.chain..box.width(10); + expect(flexBox.attributeValue!.box!.width, 10); + }); + + test('box decoration returns correct instance', () { + final flexBox = flexBoxUtility.chain + ..box.decoration( + borderRadius: BorderRadius.circular(10), + color: Colors.amber, + ); + + final decoration = + flexBox.attributeValue!.box!.decoration as BoxDecorationDto; + expect(decoration.color, const ColorDto(Colors.amber)); + expect( + decoration.borderRadius, + BorderRadius.circular(10).toDto(), + ); + }); + + test('box foregroundDecoration returns correct instance', () { + final flexBox = flexBoxUtility.chain + ..box.foregroundDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.amber, + ); + + final foregroundDecoration = + flexBox.attributeValue!.box!.foregroundDecoration as BoxDecorationDto; + expect( + foregroundDecoration.color, + const ColorDto(Colors.amber), + reason: 'The color is not correct', + ); + expect( + foregroundDecoration.borderRadius, + BorderRadius.circular(10).toDto(), + reason: 'The BorderRadius is not correct', + ); + }); + + test('flex properties return correct instances', () { + final flexBox = flexBoxUtility.chain + ..flex.mainAxisAlignment.center() + ..flex.crossAxisAlignment.center() + ..flex.mainAxisSize.min() + ..flex.direction.horizontal() + ..flex.verticalDirection.down() + ..flex.textDirection.ltr() + ..flex.textBaseline.alphabetic() + ..flex.clipBehavior.antiAlias() + ..flex.gap(10); + + final attr = flexBox.attributeValue!.flex!; + expect(attr.mainAxisAlignment, MainAxisAlignment.center); + expect(attr.crossAxisAlignment, CrossAxisAlignment.center); + expect(attr.mainAxisSize, MainAxisSize.min); + expect(attr.direction, Axis.horizontal); + expect(attr.verticalDirection, VerticalDirection.down); + expect(attr.textDirection, TextDirection.ltr); + expect(attr.textBaseline, TextBaseline.alphabetic); + expect(attr.clipBehavior, Clip.antiAlias); + expect(attr.gap!.value, 10); + }); + }); +} diff --git a/packages/mix/test/src/specs/flexbox/flexbox_widget_test.dart b/packages/mix/test/src/specs/flexbox/flexbox_widget_test.dart new file mode 100644 index 000000000..dd629ab5f --- /dev/null +++ b/packages/mix/test/src/specs/flexbox/flexbox_widget_test.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; + +import '../../../helpers/override_modifiers_order.dart'; +import '../../../helpers/testing_utils.dart'; + +void main() { + testWidgets('FlexBox', (WidgetTester tester) async { + final paddingAttr = $box.padding(10); + final marginAttr = $box.margin(15); + final alignmentAttr = $box.alignment.center(); + final clipAttr = $box.clipBehavior.hardEdge(); + final mainAxisAttr = $flex.mainAxisAlignment.center(); + final crossAxisAttr = $flex.crossAxisAlignment.center(); + + final boxDecorationAttr = $box.decoration( + border: Border.all(color: Colors.red, width: 1, style: BorderStyle.solid), + borderRadius: BorderRadius.circular(10), + color: Colors.red, + ); + + await tester.pumpStyledWidget( + FlexBox( + style: Style( + paddingAttr, + marginAttr, + alignmentAttr, + clipAttr, + boxDecorationAttr, + mainAxisAttr, + crossAxisAttr, + ), + direction: Axis.horizontal, + children: const [], + ), + ); + + final flexFinder = find.byType(Flex); + final containerFinder = find.byType(Container); + final flexWidget = tester.widget(flexFinder); + final containerWidget = tester.widget(containerFinder); + + expect(containerWidget.padding, const EdgeInsets.all(10)); + expect(containerWidget.margin, const EdgeInsets.all(15)); + expect(containerWidget.alignment, Alignment.center); + expect(containerWidget.clipBehavior, Clip.hardEdge); + expect( + containerWidget.decoration, + BoxDecoration( + color: Colors.red, + border: + Border.all(color: Colors.red, width: 1, style: BorderStyle.solid), + borderRadius: BorderRadius.circular(10), + ), + ); + expect(flexWidget.mainAxisAlignment, MainAxisAlignment.center); + expect(flexWidget.crossAxisAlignment, CrossAxisAlignment.center); + }); + + testWidgets( + 'FlexBox should apply modifiers only once', + (tester) async { + await tester.pumpMaterialApp( + FlexBox( + style: Style( + $box.height(100), + $box.width(100), + $with.align(), + ), + direction: Axis.horizontal, + children: const [ + FlexBox( + direction: Axis.horizontal, + children: [], + ), + ], + ), + ); + + expect(find.byType(Align), findsOneWidget); + }, + ); + + testWidgets('FlexBox handles onEnd', (WidgetTester tester) async { + var countPressTime = 0; + var countOnEnd = 0; + + await tester.pumpWidget( + MaterialApp( + home: PressableBox( + onPress: () { + countPressTime++; + }, + child: FlexBox( + style: Style( + $flexbox.height(50), + $flexbox.width(50), + $flexbox.wrap.transform.scale(1), + $on.press( + $flexbox.wrap.transform.scale(1.5), + ), + ).animate( + onEnd: () { + print('onEnd'); + countOnEnd++; + }, + ), + direction: Axis.horizontal, + children: const [Box()], + ), + ), + ), + ); + + final pressableFinder = find.byType(Pressable); + await tester.tap(pressableFinder); + + await tester.pumpAndSettle(); + + expect(countPressTime, 1); + expect(countOnEnd, 1); + }); + + testWidgets('FlexBox handles onEnd #2', (WidgetTester tester) async { + var countPressTime = 0; + var countOnEnd = 0; + + await tester.pumpWidget( + MaterialApp( + home: PressableBox( + onPress: () { + countPressTime++; + }, + child: FlexBox( + style: Style( + $box.height(50), + $box.width(50), + $box.wrap.transform.scale(1), + $on.press( + $box.wrap.transform.scale(1.5), + ), + ).animate( + onEnd: () { + print('onEnd'); + countOnEnd++; + }, + ), + direction: Axis.horizontal, + children: const [Box()], + ), + ), + ), + ); + + final pressableFinder = find.byType(Pressable); + await tester.tap(pressableFinder); + + await tester.pumpAndSettle(); + + expect(countPressTime, 1); + expect(countOnEnd, 1); + }); + + testWidgets( + 'FlexBoxSpec properties should match Flex and Container properties', + (WidgetTester tester) async { + final flexBoxSpec = FlexBoxSpec( + box: BoxSpec( + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(10), + ), + foregroundDecoration: BoxDecoration( + border: Border.all(color: Colors.red, width: 2), + ), + transform: Matrix4.rotationZ(0.1), + transformAlignment: Alignment.topLeft, + clipBehavior: Clip.antiAlias, + width: 150, + height: 100, + ), + flex: const FlexSpec( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + direction: Axis.horizontal, + verticalDirection: VerticalDirection.down, + textDirection: TextDirection.ltr, + textBaseline: TextBaseline.alphabetic, + ), + ); + + const flexBoxKey = Key('flexbox'); + final flexBox = FlexBoxSpecWidget( + key: flexBoxKey, + spec: flexBoxSpec, + direction: Axis.horizontal, + children: const [], + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: flexBox, + ), + ), + ); + + final flexBoxFinder = find.byKey(flexBoxKey); + expect(flexBoxFinder, findsOneWidget); + + final containerWidget = tester.widget(find.descendant( + of: flexBoxFinder, + matching: find.byType(Container), + )); + + final flexWidget = tester.widget(find.descendant( + of: flexBoxFinder, + matching: find.byType(Flex), + )); + + expect(containerWidget.alignment, flexBoxSpec.box.alignment); + expect(containerWidget.padding, flexBoxSpec.box.padding); + expect(containerWidget.margin, flexBoxSpec.box.margin); + expect(containerWidget.decoration, flexBoxSpec.box.decoration); + expect(containerWidget.foregroundDecoration, + flexBoxSpec.box.foregroundDecoration); + expect(containerWidget.transform, flexBoxSpec.box.transform); + expect( + containerWidget.transformAlignment, flexBoxSpec.box.transformAlignment); + expect(containerWidget.clipBehavior, flexBoxSpec.box.clipBehavior); + + expect(flexWidget.mainAxisAlignment, flexBoxSpec.flex.mainAxisAlignment); + expect(flexWidget.crossAxisAlignment, flexBoxSpec.flex.crossAxisAlignment); + expect(flexWidget.mainAxisSize, flexBoxSpec.flex.mainAxisSize); + expect(flexWidget.direction, flexBoxSpec.flex.direction); + expect(flexWidget.verticalDirection, flexBoxSpec.flex.verticalDirection); + expect(flexWidget.textDirection, flexBoxSpec.flex.textDirection); + expect(flexWidget.textBaseline, flexBoxSpec.flex.textBaseline); + }); + + testWidgets( + 'Renders modifiers in the correct order with many overrides', + (tester) async { + testOverrideModifiersOrder( + tester, + widgetBuilder: (style, orderOfModifiers) { + return FlexBox( + style: style, + orderOfModifiers: orderOfModifiers, + direction: Axis.horizontal, + children: const [], + ); + }, + ); + }, + ); +} diff --git a/packages/mix_generator/lib/src/helpers/type_ref_repository.dart b/packages/mix_generator/lib/src/helpers/type_ref_repository.dart index 54f033372..07dc4f8f3 100644 --- a/packages/mix_generator/lib/src/helpers/type_ref_repository.dart +++ b/packages/mix_generator/lib/src/helpers/type_ref_repository.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/element/type.dart'; + import 'builder_utils.dart'; import 'helpers.dart'; @@ -44,6 +45,7 @@ class TypeRefRepository { 'TextSpec': 'TextSpecAttribute', 'ImageSpec': 'ImageSpecAttribute', 'IconSpec': 'IconSpecAttribute', + 'FlexBoxSpec': 'FlexBoxSpecAttribute', 'StackSpec': 'StackSpecAttribute', }; diff --git a/packages/mix_lint/lib/src/utils/rule_config.dart b/packages/mix_lint/lib/src/utils/rule_config.dart index 982e876c4..c04e5f14d 100644 --- a/packages/mix_lint/lib/src/utils/rule_config.dart +++ b/packages/mix_lint/lib/src/utils/rule_config.dart @@ -6,7 +6,7 @@ typedef RuleParameterParser = T Function(Map json); typedef RuleProblemFactory = String Function(T value); -class RuleConfig { +class RuleConfig { final String name; final ErrorSeverity errorSeverity; final String? correctionMessage; diff --git a/packages/remix/lib/src/app/remix_app.dart b/packages/remix/lib/src/app/remix_app.dart index b25f7c2ef..0f52184da 100644 --- a/packages/remix/lib/src/app/remix_app.dart +++ b/packages/remix/lib/src/app/remix_app.dart @@ -129,7 +129,7 @@ class _RemixAppState extends State { return widget.builder!(context, child); }, ) - : child ?? const SizedBox.shrink(), + : (child ?? const SizedBox.shrink()), ); } diff --git a/packages/remix/lib/src/components/button/button.dart b/packages/remix/lib/src/components/button/button.dart index 486d3e131..1b4e633c2 100644 --- a/packages/remix/lib/src/components/button/button.dart +++ b/packages/remix/lib/src/components/button/button.dart @@ -16,8 +16,7 @@ part 'button_widget.dart'; @MixableSpec() class ButtonSpec extends Spec with _$ButtonSpec, Diagnosticable { - final FlexSpec flex; - final BoxSpec container; + final FlexBoxSpec flexbox; final IconSpec icon; final TextSpec label; @@ -30,15 +29,13 @@ class ButtonSpec extends Spec with _$ButtonSpec, Diagnosticable { static const from = _$ButtonSpec.from; const ButtonSpec({ - BoxSpec? container, - FlexSpec? flex, + FlexBoxSpec? flexbox, IconSpec? icon, TextSpec? label, super.modifiers, SpinnerSpec? spinner, super.animated, - }) : flex = flex ?? const FlexSpec(), - container = container ?? const BoxSpec(), + }) : flexbox = flexbox ?? const FlexBoxSpec(), icon = icon ?? const IconSpec(), label = label ?? const TextSpec(), spinner = spinner ?? const SpinnerSpec(); @@ -55,6 +52,7 @@ class ButtonSpec extends Spec with _$ButtonSpec, Diagnosticable { }) { return ButtonSpecWidget( key: key, + spec: this, label: label, disabled: disabled, loading: loading, @@ -62,7 +60,6 @@ class ButtonSpec extends Spec with _$ButtonSpec, Diagnosticable { iconRight: iconRight, spinnerBuilder: spinnerBuilder, onPressed: onPressed, - spec: this, ); } diff --git a/packages/remix/lib/src/components/button/button.g.dart b/packages/remix/lib/src/components/button/button.g.dart index 27b9cd3e2..c6b475596 100644 --- a/packages/remix/lib/src/components/button/button.g.dart +++ b/packages/remix/lib/src/components/button/button.g.dart @@ -33,8 +33,7 @@ mixin _$ButtonSpec on Spec { /// replaced with the new values. @override ButtonSpec copyWith({ - BoxSpec? container, - FlexSpec? flex, + FlexBoxSpec? flexbox, IconSpec? icon, TextSpec? label, WidgetModifiersData? modifiers, @@ -42,8 +41,7 @@ mixin _$ButtonSpec on Spec { AnimatedData? animated, }) { return ButtonSpec( - container: container ?? _$this.container, - flex: flex ?? _$this.flex, + flexbox: flexbox ?? _$this.flexbox, icon: icon ?? _$this.icon, label: label ?? _$this.label, modifiers: modifiers ?? _$this.modifiers, @@ -63,8 +61,7 @@ mixin _$ButtonSpec on Spec { /// The interpolation is performed on each property of the [ButtonSpec] using the appropriate /// interpolation method: /// - /// - [BoxSpec.lerp] for [container]. - /// - [FlexSpec.lerp] for [flex]. + /// - [FlexBoxSpec.lerp] for [flexbox]. /// - [IconSpec.lerp] for [icon]. /// - [TextSpec.lerp] for [label]. @@ -79,8 +76,7 @@ mixin _$ButtonSpec on Spec { if (other == null) return _$this; return ButtonSpec( - container: _$this.container.lerp(other.container, t), - flex: _$this.flex.lerp(other.flex, t), + flexbox: _$this.flexbox.lerp(other.flexbox, t), icon: _$this.icon.lerp(other.icon, t), label: _$this.label.lerp(other.label, t), modifiers: other.modifiers, @@ -95,8 +91,7 @@ mixin _$ButtonSpec on Spec { /// compare two [ButtonSpec] instances for equality. @override List get props => [ - _$this.container, - _$this.flex, + _$this.flexbox, _$this.icon, _$this.label, _$this.modifiers, @@ -108,9 +103,7 @@ mixin _$ButtonSpec on Spec { void _debugFillProperties(DiagnosticPropertiesBuilder properties) { properties.add( - DiagnosticsProperty('container', _$this.container, defaultValue: null)); - properties - .add(DiagnosticsProperty('flex', _$this.flex, defaultValue: null)); + DiagnosticsProperty('flexbox', _$this.flexbox, defaultValue: null)); properties .add(DiagnosticsProperty('icon', _$this.icon, defaultValue: null)); properties @@ -133,15 +126,13 @@ mixin _$ButtonSpec on Spec { /// the [ButtonSpec] constructor. class ButtonSpecAttribute extends SpecAttribute with Diagnosticable { - final BoxSpecAttribute? container; - final FlexSpecAttribute? flex; + final FlexBoxSpecAttribute? flexbox; final IconSpecAttribute? icon; final TextSpecAttribute? label; final SpinnerSpecAttribute? spinner; const ButtonSpecAttribute({ - this.container, - this.flex, + this.flexbox, this.icon, this.label, super.modifiers, @@ -160,8 +151,7 @@ class ButtonSpecAttribute extends SpecAttribute @override ButtonSpec resolve(MixData mix) { return ButtonSpec( - container: container?.resolve(mix), - flex: flex?.resolve(mix), + flexbox: flexbox?.resolve(mix), icon: icon?.resolve(mix), label: label?.resolve(mix), modifiers: modifiers?.resolve(mix), @@ -183,8 +173,7 @@ class ButtonSpecAttribute extends SpecAttribute if (other == null) return this; return ButtonSpecAttribute( - container: container?.merge(other.container) ?? other.container, - flex: flex?.merge(other.flex) ?? other.flex, + flexbox: flexbox?.merge(other.flexbox) ?? other.flexbox, icon: icon?.merge(other.icon) ?? other.icon, label: label?.merge(other.label) ?? other.label, modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, @@ -199,8 +188,7 @@ class ButtonSpecAttribute extends SpecAttribute /// compare two [ButtonSpecAttribute] instances for equality. @override List get props => [ - container, - flex, + flexbox, icon, label, modifiers, @@ -211,9 +199,7 @@ class ButtonSpecAttribute extends SpecAttribute @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('container', container, defaultValue: null)); - properties.add(DiagnosticsProperty('flex', flex, defaultValue: null)); + properties.add(DiagnosticsProperty('flexbox', flexbox, defaultValue: null)); properties.add(DiagnosticsProperty('icon', icon, defaultValue: null)); properties.add(DiagnosticsProperty('label', label, defaultValue: null)); properties @@ -230,11 +216,8 @@ class ButtonSpecAttribute extends SpecAttribute /// Use the methods of this class to configure specific properties of a [ButtonSpec]. class ButtonSpecUtility extends SpecUtility { - /// Utility for defining [ButtonSpecAttribute.container] - late final container = BoxSpecUtility((v) => only(container: v)); - - /// Utility for defining [ButtonSpecAttribute.flex] - late final flex = FlexSpecUtility((v) => only(flex: v)); + /// Utility for defining [ButtonSpecAttribute.flexbox] + late final flexbox = FlexBoxSpecUtility((v) => only(flexbox: v)); /// Utility for defining [ButtonSpecAttribute.icon] late final icon = IconSpecUtility((v) => only(icon: v)); @@ -262,8 +245,7 @@ class ButtonSpecUtility /// Returns a new [ButtonSpecAttribute] with the specified properties. @override T only({ - BoxSpecAttribute? container, - FlexSpecAttribute? flex, + FlexBoxSpecAttribute? flexbox, IconSpecAttribute? icon, TextSpecAttribute? label, WidgetModifiersDataDto? modifiers, @@ -271,8 +253,7 @@ class ButtonSpecUtility AnimatedDataDto? animated, }) { return builder(ButtonSpecAttribute( - container: container, - flex: flex, + flexbox: flexbox, icon: icon, label: label, modifiers: modifiers, diff --git a/packages/remix/lib/src/components/button/button_style.dart b/packages/remix/lib/src/components/button/button_style.dart index 9d8c69f97..68b2cfb6f 100644 --- a/packages/remix/lib/src/components/button/button_style.dart +++ b/packages/remix/lib/src/components/button/button_style.dart @@ -7,14 +7,6 @@ class ButtonStyle extends SpecStyle { Style makeStyle(SpecConfiguration spec) { final $ = spec.utilities; - final flexStyle = [ - $.flex.chain - ..mainAxisAlignment.center() - ..crossAxisAlignment.center() - ..mainAxisSize.min() - ..gap(8), - ]; - final iconStyle = [ $.icon.chain ..size(24) @@ -38,20 +30,23 @@ class ButtonStyle extends SpecStyle { ..color.white(), ]; - final containerStyle = [ - $.container.chain + final flexboxStyle = [ + $.flexbox.chain ..borderRadius(6) ..color.black() ..padding.vertical(8) - ..padding.horizontal(12), - spec.on.disabled($.container.color.grey.shade400()), + ..padding.horizontal(12) + ..flex.mainAxisAlignment.center() + ..flex.crossAxisAlignment.center() + ..flex.mainAxisSize.min() + ..flex.gap(8), + spec.on.disabled($.flexbox.color.grey.shade400()), ]; return Style.create([ - ...flexStyle, + ...flexboxStyle, ...iconStyle, ...labelStyle, - ...containerStyle, ...spinnerStyle, ]); } @@ -66,7 +61,7 @@ class ButtonDarkStyle extends ButtonStyle { return Style.create([ super.makeStyle(spec).call(), - $.container.color.white(), + $.flexbox.color.white(), $.label.style.color.black(), ]); } diff --git a/packages/remix/lib/src/components/button/button_theme.dart b/packages/remix/lib/src/components/button/button_theme.dart index a4f63570c..25ba09924 100644 --- a/packages/remix/lib/src/components/button/button_theme.dart +++ b/packages/remix/lib/src/components/button/button_theme.dart @@ -17,17 +17,17 @@ class FortalezaButtonStyle extends ButtonStyle { final baseStyle = super.makeStyle(spec); final baseOverrides = Style( baseStyle(), - $.container.chain + $.flexbox.chain ..padding.vertical.$space(2) - ..padding.horizontal.$space(3), - $.flex.gap.$space(2), + ..padding.horizontal.$space(3) + ..flex.gap.$space(2), $.label.style.$text(2), $.icon.size(14), $.spinner.size(14), ); final onDisabledForeground = $on.disabled( - $.container.color.$neutral(7), + $.flexbox.color.$neutral(7), $.label.style.color.$neutral(8), $.icon.color.$neutral(8), $.spinner.color.$neutral(7), @@ -36,25 +36,25 @@ class FortalezaButtonStyle extends ButtonStyle { final spinnerDisabled = $.spinner.color.$neutralAlpha(7); final solidVariant = Style( - $.container.color.$accent(), + $.flexbox.color.$accent(), $.label.style.color.white(), $.spinner.color.white(), $.icon.color.white(), - spec.on.hover($.container.color.$accent(10)), - spec.on.disabled($.container.color.$neutralAlpha(3), spinnerDisabled), + spec.on.hover($.flexbox.color.$accent(10)), + spec.on.disabled($.flexbox.color.$neutralAlpha(3), spinnerDisabled), ); final softVariant = Style( - $.container.color.$accentAlpha(3), + $.flexbox.color.$accentAlpha(3), $.label.style.color.$accentAlpha(11), $.spinner.color.$accentAlpha(11), $.icon.color.$accentAlpha(11), - spec.on.hover($.container.color.$accentAlpha(4)), - spec.on.disabled($.container.color.$neutralAlpha(3)), + spec.on.hover($.flexbox.color.$accentAlpha(4)), + spec.on.disabled($.flexbox.color.$neutralAlpha(3)), ); final outlineVariant = Style( - $.container.chain + $.flexbox.chain ..color.transparent() ..border.width(1) ..border.strokeAlign(0) @@ -62,9 +62,9 @@ class FortalezaButtonStyle extends ButtonStyle { $.spinner.color.$accentAlpha(11), $.icon.color.$accentAlpha(11), $.label.style.color.$accentAlpha(11), - spec.on.hover($.container.color.$accentAlpha(2)), + spec.on.hover($.flexbox.color.$accentAlpha(2)), spec.on.disabled( - $.container.chain + $.flexbox.chain ..border.color.$neutralAlpha(8) ..color.transparent(), ), @@ -72,22 +72,22 @@ class FortalezaButtonStyle extends ButtonStyle { final surfaceVariant = Style( outlineVariant(), - $.container.color.$accentAlpha(3), + $.flexbox.color.$accentAlpha(3), spec.on.hover( - $.container.color.$accentAlpha(4), - $.container.border.color.$accentAlpha(8), + $.flexbox.color.$accentAlpha(4), + $.flexbox.border.color.$accentAlpha(8), ), - spec.on.disabled($.container.color.$neutral(1)), + spec.on.disabled($.flexbox.color.$neutral(1)), ); final ghostVariant = Style( - $.container.border.style.none(), - $.container.color.transparent(), + $.flexbox.border.style.none(), + $.flexbox.color.transparent(), $.spinner.color.$accentAlpha(11), $.icon.color.$accentAlpha(11), $.label.style.color.$accentAlpha(11), - spec.on.hover($.container.color.$accentAlpha(3)), - spec.on.disabled($.container.color.transparent()), + spec.on.hover($.flexbox.color.$accentAlpha(3)), + spec.on.disabled($.flexbox.color.transparent()), ); return Style.create( diff --git a/packages/remix/lib/src/components/button/button_widget.dart b/packages/remix/lib/src/components/button/button_widget.dart index 1c397ede1..c322587e5 100644 --- a/packages/remix/lib/src/components/button/button_widget.dart +++ b/packages/remix/lib/src/components/button/button_widget.dart @@ -62,6 +62,7 @@ class Button extends StatelessWidget { ]), builder: (context) { return ButtonSpecWidget( + spec: ButtonSpec.of(context), label: label, disabled: disabled, loading: loading, @@ -69,7 +70,6 @@ class Button extends StatelessWidget { iconRight: iconRight, spinnerBuilder: spinnerBuilder, onPressed: onPressed, - spec: ButtonSpec.of(context), ); }, ), @@ -113,7 +113,7 @@ class ButtonSpecWidget extends StatelessWidget { } Widget _buildChildren(ButtonSpec spec) { - final flexWidget = spec.flex( + final flexboxWidget = spec.flexbox( direction: Axis.horizontal, children: [ if (iconLeft != null) spec.icon(iconLeft), @@ -123,13 +123,13 @@ class ButtonSpecWidget extends StatelessWidget { ], ); - return loading ? _buildLoadingOverlay(spec, flexWidget) : flexWidget; + return loading ? _buildLoadingOverlay(spec, flexboxWidget) : flexboxWidget; } @override Widget build(BuildContext context) { final spec = this.spec ?? const ButtonSpec(); - return spec.container(child: _buildChildren(spec)); + return _buildChildren(spec); } } diff --git a/packages/remix/lib/src/components/checkbox/checkbox_widget.dart b/packages/remix/lib/src/components/checkbox/checkbox_widget.dart index a1500ef68..ebe41b1c0 100644 --- a/packages/remix/lib/src/components/checkbox/checkbox_widget.dart +++ b/packages/remix/lib/src/components/checkbox/checkbox_widget.dart @@ -89,7 +89,7 @@ class _CheckboxState extends State { child: IconWidget( widget.value ? widget.iconChecked - : widget.iconUnchecked ?? widget.iconChecked, + : (widget.iconUnchecked ?? widget.iconChecked), ), ), if (widget.label != null) spec.label(widget.label!), diff --git a/packages/remix/lib/src/components/toast/toast_layer.dart b/packages/remix/lib/src/components/toast/toast_layer.dart index ebe58804a..f2066592d 100644 --- a/packages/remix/lib/src/components/toast/toast_layer.dart +++ b/packages/remix/lib/src/components/toast/toast_layer.dart @@ -54,7 +54,7 @@ class ToastLayerState extends State implements ToastActions { Widget build(BuildContext context) { final toast = currentToast; final alignment = currentToast?.alignment ?? Alignment.bottomCenter; - + final toastWidget = KeyedSubtree( key: UniqueKey(), child: Align( diff --git a/packages/remix/lib/src/theme/remix_theme.dart b/packages/remix/lib/src/theme/remix_theme.dart index e9622a6ba..3384b8941 100644 --- a/packages/remix/lib/src/theme/remix_theme.dart +++ b/packages/remix/lib/src/theme/remix_theme.dart @@ -293,16 +293,16 @@ class RemixTheme extends StatelessWidget { RemixThemeData _defineRemixThemeData(BuildContext context) { if (themeMode != null) { return themeMode == ThemeMode.dark - ? darkTheme ?? _defaultThemeDark - : theme ?? _defaultThemeLight; + ? (darkTheme ?? _defaultThemeDark) + : (theme ?? _defaultThemeLight); } final brightness = MediaQuery.platformBrightnessOf(context); final isDark = brightness == Brightness.dark; return isDark - ? darkTheme ?? _defaultThemeDark - : theme ?? _defaultThemeLight; + ? (darkTheme ?? _defaultThemeDark) + : (theme ?? _defaultThemeLight); } @override diff --git a/packages/remix/test/components/button/button_widget_test.dart b/packages/remix/test/components/button/button_widget_test.dart index 155ae50fe..af7e082f6 100644 --- a/packages/remix/test/components/button/button_widget_test.dart +++ b/packages/remix/test/components/button/button_widget_test.dart @@ -170,7 +170,7 @@ class FakeButtonStyle extends ButtonStyle { final baseStyle = super.makeStyle(spec); return Style.create([ baseStyle(), - $.container.color(color), + $.flexbox.color(color), ]); } }