diff --git a/packages/mix/lib/src/core/styled_widget.dart b/packages/mix/lib/src/core/styled_widget.dart index 1fa95e541..c7545bb55 100644 --- a/packages/mix/lib/src/core/styled_widget.dart +++ b/packages/mix/lib/src/core/styled_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; -import '../modifiers/modifiers.dart'; +import '../modifiers/internal/render_widget_modifier.dart'; import 'core.dart'; /// An abstract widget for applying custom styles. diff --git a/packages/mix/lib/src/modifiers/render_widget_modifier.dart b/packages/mix/lib/src/modifiers/internal/render_widget_modifier.dart similarity index 77% rename from packages/mix/lib/src/modifiers/render_widget_modifier.dart rename to packages/mix/lib/src/modifiers/internal/render_widget_modifier.dart index b093236ab..8cc6f0f03 100644 --- a/packages/mix/lib/src/modifiers/render_widget_modifier.dart +++ b/packages/mix/lib/src/modifiers/internal/render_widget_modifier.dart @@ -1,11 +1,11 @@ // ignore_for_file: avoid-dynamic +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../core/factory/mix_data.dart'; -import '../core/modifier.dart'; -import '../core/spec.dart'; -import 'modifiers.dart'; +import '../../core/core.dart'; +import '../../theme/theme.dart'; +import '../modifiers.dart'; const _defaultOrder = [ // 1. FlexibleModifier: When the widget is used inside a Row, Column, or Flex widget, it will @@ -89,11 +89,24 @@ class RenderModifiers extends StatelessWidget { @override Widget build(BuildContext context) { - var current = child; + return _RenderModifiers( + modifiers: _combineModifiers(mix, modifiers, orderOfModifiers).reversed, + child: child, + ); + } +} - final orderedSpecs = _combineModifiers(mix, modifiers, orderOfModifiers); +class _RenderModifiers extends StatelessWidget { + const _RenderModifiers({required this.child, required this.modifiers}); - for (final spec in orderedSpecs.reversed) { + final Widget child; + final Iterable> modifiers; + + @override + Widget build(BuildContext context) { + var current = child; + + for (final spec in modifiers) { current = spec.build(current); } @@ -101,52 +114,91 @@ class RenderModifiers extends StatelessWidget { } } -class RenderAnimatedModifiers extends ImplicitlyAnimatedWidget { - RenderAnimatedModifiers({ +class RenderAnimatedModifiers extends StatelessWidget { + const RenderAnimatedModifiers({ + super.key, //TODO Should be required in the next version this.modifiers = const [], required this.child, - required super.duration, + required this.duration, @Deprecated("Use modifiers parameter") this.mix, required this.orderOfModifiers, - super.key, - super.curve = Curves.linear, - super.onEnd, - }) : _appliedModifiers = _combineModifiers(mix, modifiers, orderOfModifiers); + this.curve = Curves.linear, + this.onEnd, + }); final Widget child; final MixData? mix; final List orderOfModifiers; final List> modifiers; - final List> _appliedModifiers; + final Duration duration; + final Curve curve; + final VoidCallback? onEnd; @override - RenderAnimatedModifiersState createState() => RenderAnimatedModifiersState(); + Widget build(BuildContext context) { + return _RenderAnimatedModifiers( + modifiers: _combineModifiers( + mix, + modifiers, + orderOfModifiers, + defaultOrder: MixTheme.maybeOf(context)?.defaultOrderOfModifiers, + ).reversed.toList(), + duration: duration, + curve: curve, + onEnd: onEnd, + child: child, + ); + } } -class RenderAnimatedModifiersState - extends AnimatedWidgetBaseState { +class _RenderAnimatedModifiers extends ImplicitlyAnimatedWidget { + const _RenderAnimatedModifiers({ + //TODO Should be required in the next version + this.modifiers = const [], + required this.child, + required super.duration, + super.curve = Curves.linear, + super.onEnd, + }); + + final Widget child; + final List> modifiers; + + @override + _RenderAnimatedModifiersState createState() => + _RenderAnimatedModifiersState(); +} + +class _RenderAnimatedModifiersState + extends AnimatedWidgetBaseState<_RenderAnimatedModifiers> { final Map _specs = {}; + Iterable _typeOfModifiers = []; + + @override + void initState() { + super.initState(); + updateTypeOfAppliedModifiers(); + } + @override - void didUpdateWidget(covariant RenderAnimatedModifiers oldWidget) { + void didUpdateWidget(covariant _RenderAnimatedModifiers oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.modifiers != widget.modifiers || - oldWidget.mix != widget.mix || - oldWidget.orderOfModifiers != widget.orderOfModifiers) { + if (!listEquals(oldWidget.modifiers, widget.modifiers)) { + updateTypeOfAppliedModifiers(); cleanUpSpecs(); } } - @override - void forEachTween(TweenVisitor visitor) { - updateModifiersSpecs(visitor); + updateTypeOfAppliedModifiers() { + _typeOfModifiers = widget.modifiers.map((e) => e.runtimeType); } Map cleanUpSpecs() { final difference = _specs.keys .toSet() - .difference(widget._appliedModifiers.map((e) => e.runtimeType).toSet()); + .difference(widget.modifiers.map((e) => e.runtimeType).toSet()); if (difference.isNotEmpty) { for (var e in difference) { @@ -157,8 +209,13 @@ class RenderAnimatedModifiersState return _specs; } + @override + void forEachTween(TweenVisitor visitor) { + updateModifiersSpecs(visitor); + } + void updateModifiersSpecs(TweenVisitor visitor) { - for (final spec in widget._appliedModifiers.reversed) { + for (final spec in widget.modifiers) { final specType = spec.runtimeType; final previousSpec = _specs[specType]; _specs[specType] = visitor( @@ -174,8 +231,8 @@ class RenderAnimatedModifiersState Widget build(BuildContext context) { var current = widget.child; - for (final spec in _specs.keys) { - final evaluatedSpec = _specs[spec]!.evaluate(animation); + for (final modifier in _typeOfModifiers) { + final evaluatedSpec = _specs[modifier]!.evaluate(animation); current = evaluatedSpec.build(current); } @@ -183,41 +240,6 @@ class RenderAnimatedModifiersState } } -@visibleForTesting -List> resolveModifierSpecs( - List orderOfModifiers, - MixData mix, -) { - return orderModifiers(orderOfModifiers, mix.modifiers); -} - -@visibleForTesting -List> orderModifiers( - List orderOfModifiers, - List> modifiers, -) { - final listOfModifiers = ({ - // Prioritize the order of modifiers provided by the user. - ...orderOfModifiers, - // Add the default order of modifiers. - ..._defaultOrder, - // Add any remaining modifiers that were not included in the order. - ...modifiers.map((e) => e.type), - }).toList(); - - final specs = >[]; - - for (final modifierType in listOfModifiers) { - // Resolve the modifier and add it to the list of specs. - final modifier = modifiers.where((e) => e.type == modifierType).firstOrNull; - if (modifier == null) continue; - // ignore: avoid-unnecessary-type-casts - specs.add(modifier as WidgetModifierSpec>); - } - - return specs; -} - class RenderSpecModifiers extends StatelessWidget { const RenderSpecModifiers({ required this.orderOfModifiers, @@ -287,8 +309,9 @@ List _normalizeOrderedTypes(MixData? mix, List? orderedTypes) { List> _combineModifiers( MixData? mix, List> modifiers, - List orderOfModifiers, -) { + List orderOfModifiers, { + List? defaultOrder, +}) { final normalizedModifiers = _normalizeOrderedTypes(mix, orderOfModifiers); final mergedModifiers = [...modifiers]; @@ -297,5 +320,37 @@ List> _combineModifiers( mergedModifiers.addAll(mix.modifiers); } - return orderModifiers(normalizedModifiers, mergedModifiers); + return orderModifiers( + normalizedModifiers, + mergedModifiers, + defaultOrder: defaultOrder, + ); +} + +@visibleForTesting +List> orderModifiers( + List orderOfModifiers, + List> modifiers, { + List? defaultOrder, +}) { + final listOfModifiers = ({ + // Prioritize the order of modifiers provided by the user. + ...orderOfModifiers, + // Add the default order of modifiers. + ...defaultOrder ?? _defaultOrder, + // Add any remaining modifiers that were not included in the order. + ...modifiers.map((e) => e.type), + }).toList(); + + final specs = >[]; + + for (final modifierType in listOfModifiers) { + // Resolve the modifier and add it to the list of specs. + final modifier = modifiers.where((e) => e.type == modifierType).firstOrNull; + if (modifier == null) continue; + // ignore: avoid-unnecessary-type-casts + specs.add(modifier as WidgetModifierSpec>); + } + + return specs; } diff --git a/packages/mix/lib/src/modifiers/modifiers.dart b/packages/mix/lib/src/modifiers/modifiers.dart index 84fcf005f..69cf50c97 100644 --- a/packages/mix/lib/src/modifiers/modifiers.dart +++ b/packages/mix/lib/src/modifiers/modifiers.dart @@ -13,7 +13,6 @@ export 'intrinsic_widget_modifier.dart'; export 'modifiers.dart'; export 'opacity_widget_modifier.dart'; export 'padding_widget_modifier.dart'; -export 'render_widget_modifier.dart'; export 'rotated_box_widget_modifier.dart'; export 'sized_box_widget_modifier.dart'; export 'transform_widget_modifier.dart'; diff --git a/packages/mix/lib/src/specs/box/box_widget.dart b/packages/mix/lib/src/specs/box/box_widget.dart index 81cf14743..d5e8ab6f9 100644 --- a/packages/mix/lib/src/specs/box/box_widget.dart +++ b/packages/mix/lib/src/specs/box/box_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import '../../core/factory/mix_provider.dart'; import '../../core/styled_widget.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import 'box_spec.dart'; /// A [Container] equivalent widget for applying styles using Mix. diff --git a/packages/mix/lib/src/specs/flex/flex_widget.dart b/packages/mix/lib/src/specs/flex/flex_widget.dart index 83ad00938..a83d04145 100644 --- a/packages/mix/lib/src/specs/flex/flex_widget.dart +++ b/packages/mix/lib/src/specs/flex/flex_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import '../../core/styled_widget.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import '../box/box_spec.dart'; import '../box/box_widget.dart'; import 'flex_spec.dart'; diff --git a/packages/mix/lib/src/specs/icon/icon_widget.dart b/packages/mix/lib/src/specs/icon/icon_widget.dart index 3de0b5134..dbd8f3193 100644 --- a/packages/mix/lib/src/specs/icon/icon_widget.dart +++ b/packages/mix/lib/src/specs/icon/icon_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../../core/styled_widget.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import 'icon_spec.dart'; class StyledIcon extends StyledWidget { diff --git a/packages/mix/lib/src/specs/image/image_widget.dart b/packages/mix/lib/src/specs/image/image_widget.dart index e23d535f4..6f25d0543 100644 --- a/packages/mix/lib/src/specs/image/image_widget.dart +++ b/packages/mix/lib/src/specs/image/image_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import '../../core/styled_widget.dart'; import '../../internal/constants.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import 'image_spec.dart'; class StyledImage extends StyledWidget { diff --git a/packages/mix/lib/src/specs/stack/stack_widget.dart b/packages/mix/lib/src/specs/stack/stack_widget.dart index 55e9d36d0..0e164b40d 100644 --- a/packages/mix/lib/src/specs/stack/stack_widget.dart +++ b/packages/mix/lib/src/specs/stack/stack_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import '../../core/styled_widget.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import '../box/box_spec.dart'; import '../box/box_widget.dart'; import 'stack_spec.dart'; diff --git a/packages/mix/lib/src/specs/text/text_widget.dart b/packages/mix/lib/src/specs/text/text_widget.dart index e39755286..75de9aeb2 100644 --- a/packages/mix/lib/src/specs/text/text_widget.dart +++ b/packages/mix/lib/src/specs/text/text_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../../core/styled_widget.dart'; -import '../../modifiers/render_widget_modifier.dart'; +import '../../modifiers/internal/render_widget_modifier.dart'; import 'text_spec.dart'; /// [StyledText] - A styled widget for displaying text with a mix of styles. diff --git a/packages/mix/lib/src/theme/mix/mix_theme.dart b/packages/mix/lib/src/theme/mix/mix_theme.dart index 4be4ae810..3c74586e9 100644 --- a/packages/mix/lib/src/theme/mix/mix_theme.dart +++ b/packages/mix/lib/src/theme/mix/mix_theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../internal/iterable_ext.dart'; import '../material/material_theme.dart'; import '../tokens/breakpoints_token.dart'; import '../tokens/color_token.dart'; @@ -37,6 +38,7 @@ class MixThemeData { final StyledTokens textStyles; final StyledTokens breakpoints; final StyledTokens spaces; + final List? defaultOrderOfModifiers; const MixThemeData.raw({ required this.textStyles, @@ -44,6 +46,7 @@ class MixThemeData { required this.breakpoints, required this.radii, required this.spaces, + this.defaultOrderOfModifiers, }); const MixThemeData.empty() @@ -53,6 +56,7 @@ class MixThemeData { breakpoints: const StyledTokens.empty(), radii: const StyledTokens.empty(), spaces: const StyledTokens.empty(), + defaultOrderOfModifiers: null, ); factory MixThemeData({ @@ -61,6 +65,7 @@ class MixThemeData { Map? spaces, Map? textStyles, Map? radii, + List? defaultOrderOfModifiers, }) { return MixThemeData.raw( textStyles: StyledTokens(textStyles ?? const {}), @@ -69,6 +74,7 @@ class MixThemeData { _breakpointTokenMap.merge(StyledTokens(breakpoints ?? const {})), radii: StyledTokens(radii ?? const {}), spaces: StyledTokens(spaces ?? const {}), + defaultOrderOfModifiers: defaultOrderOfModifiers, ); } @@ -78,6 +84,7 @@ class MixThemeData { Map? spaces, Map? textStyles, Map? radii, + List? defaultOrderOfModifiers, }) { return materialMixTheme.merge( MixThemeData( @@ -86,6 +93,7 @@ class MixThemeData { spaces: spaces, textStyles: textStyles, radii: radii, + defaultOrderOfModifiers: defaultOrderOfModifiers, ), ); } @@ -96,6 +104,7 @@ class MixThemeData { Map? spaces, Map? textStyles, Map? radii, + List? defaultOrderOfModifiers, }) { return MixThemeData.raw( textStyles: @@ -105,6 +114,8 @@ class MixThemeData { breakpoints == null ? this.breakpoints : StyledTokens(breakpoints), radii: radii == null ? this.radii : StyledTokens(radii), spaces: spaces == null ? this.spaces : StyledTokens(spaces), + defaultOrderOfModifiers: + this.defaultOrderOfModifiers ?? defaultOrderOfModifiers, ); } @@ -115,6 +126,8 @@ class MixThemeData { breakpoints: breakpoints.merge(other.breakpoints), radii: radii.merge(other.radii), spaces: spaces.merge(other.spaces), + defaultOrderOfModifiers: + (defaultOrderOfModifiers ?? []).merge(other.defaultOrderOfModifiers), ); } @@ -127,7 +140,8 @@ class MixThemeData { other.colors == colors && other.breakpoints == breakpoints && other.radii == radii && - other.spaces == spaces; + other.spaces == spaces && + other.defaultOrderOfModifiers == defaultOrderOfModifiers; } @override @@ -136,7 +150,8 @@ class MixThemeData { colors.hashCode ^ breakpoints.hashCode ^ radii.hashCode ^ - spaces.hashCode; + spaces.hashCode ^ + defaultOrderOfModifiers.hashCode; } } diff --git a/packages/mix/test/deprecated/widget_modifier_widget_test.dart b/packages/mix/test/deprecated/widget_modifier_widget_test.dart index bf279e6e5..c33182a23 100644 --- a/packages/mix/test/deprecated/widget_modifier_widget_test.dart +++ b/packages/mix/test/deprecated/widget_modifier_widget_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../helpers/testing_utils.dart'; @@ -370,44 +371,4 @@ void main() { }, ); }); - - group('resolveModifierSpecs', () { - test('Returns empty set when no modifiers are provided', () { - final result = resolveModifierSpecs(const [], EmptyMixData); - expect(result, isEmpty); - }); - - test('Returns resolved modifier specs in the correct order', () { - final style = Style( - $with.scale(2.0), - $with.opacity(0.5), - $with.visibility.on(), - $with.clipOval(), - $with.aspectRatio(2.0), - ); - - final mix = style.of(MockBuildContext()); - final result = resolveModifierSpecs( - [ - ClipOvalModifierAttribute, - AspectRatioModifierAttribute, - TransformModifierAttribute, - OpacityModifierAttribute, - VisibilityModifierAttribute, - ], - mix, - ); - - expect(result, { - const VisibilityModifierSpec(true), - const OpacityModifierSpec(0.5), - TransformModifierSpec( - transform: Matrix4.diagonal3Values(2.0, 2.0, 1.0), - alignment: Alignment.center, - ), - const AspectRatioModifierSpec(2.0), - const ClipOvalModifierSpec(), - }); - }); - }); } diff --git a/packages/mix/test/deprecated/widget_modifiers_util_test.dart b/packages/mix/test/deprecated/widget_modifiers_util_test.dart index d1dd975f6..7cc0c2344 100644 --- a/packages/mix/test/deprecated/widget_modifiers_util_test.dart +++ b/packages/mix/test/deprecated/widget_modifiers_util_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../empty_widget.dart'; import '../helpers/testing_utils.dart'; diff --git a/packages/mix/test/modifiers/render_widget_modifier_test.dart b/packages/mix/test/modifiers/render_widget_modifier_test.dart index 05508d59e..cc351046f 100644 --- a/packages/mix/test/modifiers/render_widget_modifier_test.dart +++ b/packages/mix/test/modifiers/render_widget_modifier_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; void main() { group('orderSpecs', () { diff --git a/packages/mix/test/src/modifiers/widget_modifier_widget_test.dart b/packages/mix/test/src/modifiers/widget_modifier_widget_test.dart index b06b17eaf..07cc70b8e 100644 --- a/packages/mix/test/src/modifiers/widget_modifier_widget_test.dart +++ b/packages/mix/test/src/modifiers/widget_modifier_widget_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../../helpers/testing_utils.dart'; @@ -355,6 +356,43 @@ void main() { expect(find.byType(Transform), findsNothing); await tester.pumpAndSettle(const Duration(milliseconds: 200)); }); + + testWidgets('override default orderOfModifiers using MixTheme', + (tester) async { + await tester.pumpWithMixTheme( + Box( + style: Style( + $with.scale(2), + $with.clipRect(), + $with.sizedBox.square(100), + ).animate(), + ), + theme: MixThemeData( + defaultOrderOfModifiers: const [ + SizedBoxModifierSpec, + ClipRectModifierSpec, + TransformModifierSpec, + ], + ), + ); + + final firstFinder = find.byType(SizedBox); + expect(firstFinder, findsOneWidget); + + final secondFinder = find.descendant( + of: firstFinder, + matching: find.byType(ClipRect), + ); + + expect(secondFinder, findsOneWidget); + + final thirdFinder = find.descendant( + of: secondFinder, + matching: find.byType(Transform), + ); + + expect(thirdFinder, findsOneWidget); + }); }); group('Modifiers attributes', () { @@ -450,46 +488,6 @@ void main() { }, ); }); - - group('resolveModifierSpecs', () { - test('Returns empty set when no modifiers are provided', () { - final result = resolveModifierSpecs(const [], EmptyMixData); - expect(result, isEmpty); - }); - - test('Returns resolved modifier specs in the correct order', () { - final style = Style( - $with.scale(2.0), - $with.opacity(0.5), - $with.visibility.on(), - $with.clipOval(), - $with.aspectRatio(2.0), - ); - - final mix = style.of(MockBuildContext()); - final result = resolveModifierSpecs( - [ - ClipOvalModifierAttribute, - AspectRatioModifierAttribute, - TransformModifierAttribute, - OpacityModifierAttribute, - VisibilityModifierAttribute, - ], - mix, - ); - - expect(result, { - const VisibilityModifierSpec(true), - const OpacityModifierSpec(0.5), - TransformModifierSpec( - transform: Matrix4.diagonal3Values(2.0, 2.0, 1.0), - alignment: Alignment.center, - ), - const AspectRatioModifierSpec(2.0), - const ClipOvalModifierSpec(), - }); - }); - }); } class _TestableAnimatedModifiers extends StatefulWidget { diff --git a/packages/mix/test/src/modifiers/widget_modifiers_util_test.dart b/packages/mix/test/src/modifiers/widget_modifiers_util_test.dart index 2c87c29c4..64432134b 100644 --- a/packages/mix/test/src/modifiers/widget_modifiers_util_test.dart +++ b/packages/mix/test/src/modifiers/widget_modifiers_util_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../../empty_widget.dart'; import '../../helpers/testing_utils.dart'; diff --git a/packages/mix/test/src/widgets/box/box_test.dart b/packages/mix/test/src/widgets/box/box_test.dart index 6369a5953..d2ff87cb2 100644 --- a/packages/mix/test/src/widgets/box/box_test.dart +++ b/packages/mix/test/src/widgets/box/box_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../../../helpers/testing_utils.dart'; diff --git a/packages/mix/test/src/widgets/styled_icon_test.dart b/packages/mix/test/src/widgets/styled_icon_test.dart index a5a52724c..0cb7d443f 100644 --- a/packages/mix/test/src/widgets/styled_icon_test.dart +++ b/packages/mix/test/src/widgets/styled_icon_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mix/mix.dart'; +import 'package:mix/src/modifiers/internal/render_widget_modifier.dart'; import '../../helpers/testing_utils.dart';