diff --git a/lib/src/specs/container/box_widget.dart b/lib/src/specs/container/box_widget.dart index 6ab962a3e..1d178ae26 100644 --- a/lib/src/specs/container/box_widget.dart +++ b/lib/src/specs/container/box_widget.dart @@ -73,22 +73,22 @@ class MixedBox extends StatelessWidget { const MixedBox({required this.spec, super.key, this.child}); final Widget? child; - final BoxSpec spec; + final BoxSpec? spec; @override Widget build(BuildContext context) { return Container( - alignment: spec.alignment, - padding: spec.padding, - decoration: spec.decoration, - foregroundDecoration: spec.foregroundDecoration, - width: spec.width, - height: spec.height, - constraints: spec.constraints, - margin: spec.margin, - transform: spec.transform, - transformAlignment: spec.transformAlignment, - clipBehavior: spec.clipBehavior ?? Clip.none, + alignment: spec?.alignment, + padding: spec?.padding, + decoration: spec?.decoration, + foregroundDecoration: spec?.foregroundDecoration, + width: spec?.width, + height: spec?.height, + constraints: spec?.constraints, + margin: spec?.margin, + transform: spec?.transform, + transformAlignment: spec?.transformAlignment, + clipBehavior: spec?.clipBehavior ?? Clip.none, child: child, ); } @@ -131,19 +131,6 @@ class _AnimatedBoxSpecWidgetState Widget build(BuildContext context) { final spec = _boxSpec?.evaluate(animation); - return Container( - alignment: spec?.alignment, - padding: spec?.padding, - decoration: spec?.decoration, - foregroundDecoration: spec?.foregroundDecoration, - width: spec?.width, - height: spec?.height, - constraints: spec?.constraints, - margin: spec?.margin, - transform: spec?.transform, - transformAlignment: spec?.transformAlignment, - clipBehavior: spec?.clipBehavior ?? Clip.none, - child: widget.child, - ); + return MixedBox(spec: spec, child: widget.child); } } diff --git a/lib/src/specs/icon/icon_spec.dart b/lib/src/specs/icon/icon_spec.dart index beb8b84dd..b9eed7d93 100644 --- a/lib/src/specs/icon/icon_spec.dart +++ b/lib/src/specs/icon/icon_spec.dart @@ -1,5 +1,7 @@ import 'dart:ui'; +import 'package:flutter/material.dart'; + import '../../core/attribute.dart'; import '../../factory/mix_provider_data.dart'; import 'icon_attribute.dart'; @@ -97,3 +99,11 @@ class IconSpec extends Spec { fill, ]; } + +class IconSpecTween extends Tween { + IconSpecTween({IconSpec? begin, IconSpec? end}) + : super(begin: begin, end: end); + + @override + IconSpec lerp(double t) => begin!.lerp(end, t); +} diff --git a/lib/src/specs/icon/icon_widget.dart b/lib/src/specs/icon/icon_widget.dart index 6740e4e24..fd11b935d 100644 --- a/lib/src/specs/icon/icon_widget.dart +++ b/lib/src/specs/icon/icon_widget.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import '../../core/styled_widget.dart'; -import '../../factory/mix_provider.dart'; -import '../../factory/mix_provider_data.dart'; import 'icon_spec.dart'; class StyledIcon extends StyledWidget { @@ -23,11 +21,23 @@ class StyledIcon extends StyledWidget { @override Widget build(BuildContext context) { return withMix(context, (mix) { - return MixedIcon( - icon, - semanticLabel: semanticLabel, - textDirection: textDirection, - ); + final spec = IconSpec.of(mix); + + return mix.isAnimated + ? AnimatedMixedIcon( + icon: icon, + spec: spec, + semanticLabel: semanticLabel, + textDirection: textDirection, + curve: mix.animation!.curve, + duration: mix.animation!.duration, + ) + : MixedIcon( + icon, + spec: spec, + semanticLabel: semanticLabel, + textDirection: textDirection, + ); }); } } @@ -35,7 +45,7 @@ class StyledIcon extends StyledWidget { class MixedIcon extends StatelessWidget { const MixedIcon( this.icon, { - this.mix, + this.spec, this.semanticLabel, super.key, this.textDirection, @@ -43,25 +53,22 @@ class MixedIcon extends StatelessWidget { }); final IconData? icon; - final MixData? mix; + final IconSpec? spec; final String? semanticLabel; final TextDirection? textDirection; final List decoratorOrder; @override Widget build(BuildContext context) { - final mix = this.mix ?? MixProvider.of(context); - final spec = IconSpec.of(mix); - return Icon( icon, - size: spec.size, - fill: spec.fill, - weight: spec.weight, - grade: spec.grade, - opticalSize: spec.opticalSize, - color: spec.color, - shadows: spec.shadows, + size: spec?.size, + fill: spec?.fill, + weight: spec?.weight, + grade: spec?.grade, + opticalSize: spec?.opticalSize, + color: spec?.color, + shadows: spec?.shadows, semanticLabel: semanticLabel, textDirection: textDirection, ); @@ -101,3 +108,55 @@ class AnimatedStyledIcon extends StyledWidget { }); } } + +class AnimatedMixedIcon extends ImplicitlyAnimatedWidget { + const AnimatedMixedIcon({ + required this.icon, + required this.spec, + super.key, + this.semanticLabel, + this.textDirection, + this.decoratorOrder = const [], + Curve curve = Curves.linear, + required Duration duration, + VoidCallback? onEnd, + }) : super(curve: curve, duration: duration, onEnd: onEnd); + + final IconData? icon; + final IconSpec spec; + final String? semanticLabel; + final TextDirection? textDirection; + final List decoratorOrder; + + @override + // ignore: library_private_types_in_public_api + _AnimatedMixedIconState createState() => _AnimatedMixedIconState(); +} + +class _AnimatedMixedIconState + extends AnimatedWidgetBaseState { + IconSpecTween? _spec; + + @override + // ignore: avoid-dynamic + void forEachTween(TweenVisitor visitor) { + _spec = visitor( + _spec, + widget.spec, + // ignore: avoid-dynamic + (dynamic value) => IconSpecTween(begin: value as IconSpec?), + ) as IconSpecTween?; + } + + @override + Widget build(BuildContext context) { + final spec = _spec?.evaluate(animation); + + return MixedIcon( + widget.icon, + spec: spec, + semanticLabel: widget.semanticLabel, + textDirection: widget.textDirection, + ); + } +} diff --git a/lib/src/specs/image/image_spec.dart b/lib/src/specs/image/image_spec.dart index 4a3663d3a..88454bdf5 100644 --- a/lib/src/specs/image/image_spec.dart +++ b/lib/src/specs/image/image_spec.dart @@ -101,3 +101,11 @@ class ImageSpec extends Spec { colorBlendMode, ]; } + +class ImageSpecTween extends Tween { + ImageSpecTween({ImageSpec? begin, ImageSpec? end}) + : super(begin: begin, end: end); + + @override + ImageSpec lerp(double t) => begin!.lerp(end, t); +} diff --git a/lib/src/specs/image/image_widget.dart b/lib/src/specs/image/image_widget.dart index 65f89528e..3c0fafa64 100644 --- a/lib/src/specs/image/image_widget.dart +++ b/lib/src/specs/image/image_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../../core/styled_widget.dart'; -import '../../factory/mix_provider_data.dart'; +import '../../helpers/constants.dart'; import 'image_spec.dart'; class StyledImage extends StyledWidget { @@ -28,15 +28,29 @@ class StyledImage extends StyledWidget { @override Widget build(BuildContext context) { return withMix(context, (mix) { - return MixedImage( - mix: mix, - image: image, - frameBuilder: frameBuilder, - loadingBuilder: loadingBuilder, - errorBuilder: errorBuilder, - semanticLabel: semanticLabel, - excludeFromSemantics: excludeFromSemantics, - ); + final spec = ImageSpec.of(mix); + + return mix.isAnimated + ? AnimatedMixedImage( + spec: spec, + image: image, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + duration: mix.animation!.duration, + curve: mix.animation!.curve, + ) + : MixedImage( + spec: spec, + image: image, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + ); }); } } @@ -45,7 +59,7 @@ class MixedImage extends StatelessWidget { const MixedImage({ super.key, this.decoratorOrder = const [], - required this.mix, + this.spec, required this.image, this.frameBuilder, this.loadingBuilder, @@ -54,7 +68,7 @@ class MixedImage extends StatelessWidget { this.excludeFromSemantics = false, }); - final MixData mix; + final ImageSpec? spec; final ImageProvider image; final ImageFrameBuilder? frameBuilder; final ImageLoadingBuilder? loadingBuilder; @@ -65,8 +79,6 @@ class MixedImage extends StatelessWidget { @override Widget build(BuildContext context) { - final spec = ImageSpec.of(mix); - return Image( image: image, frameBuilder: frameBuilder, @@ -74,15 +86,75 @@ class MixedImage extends StatelessWidget { errorBuilder: errorBuilder, semanticLabel: semanticLabel, excludeFromSemantics: excludeFromSemantics, - width: spec.width, - height: spec.height, - color: spec.color, - colorBlendMode: spec.colorBlendMode ?? BlendMode.clear, - fit: spec.fit, - alignment: spec.alignment ?? Alignment.center, - repeat: spec.repeat ?? ImageRepeat.noRepeat, - centerSlice: spec.centerSlice, - filterQuality: spec.filterQuality ?? FilterQuality.low, + width: spec?.width, + height: spec?.height, + color: spec?.color, + colorBlendMode: spec?.colorBlendMode ?? BlendMode.clear, + fit: spec?.fit, + alignment: spec?.alignment ?? Alignment.center, + repeat: spec?.repeat ?? ImageRepeat.noRepeat, + centerSlice: spec?.centerSlice, + filterQuality: spec?.filterQuality ?? FilterQuality.low, + ); + } +} + +class AnimatedMixedImage extends ImplicitlyAnimatedWidget { + const AnimatedMixedImage({ + this.spec, + required this.image, + this.frameBuilder, + this.loadingBuilder, + this.errorBuilder, + this.semanticLabel, + this.excludeFromSemantics = false, + super.key, + super.duration = kDefaultAnimationDuration, + super.curve = Curves.linear, + super.onEnd, + }); + + final ImageSpec? spec; + final ImageProvider image; + final ImageFrameBuilder? frameBuilder; + final ImageLoadingBuilder? loadingBuilder; + final ImageErrorWidgetBuilder? errorBuilder; + final String? semanticLabel; + final bool excludeFromSemantics; + + @override + AnimatedWidgetBaseState createState() => + _AnimatedMixedImageState(); +} + +class _AnimatedMixedImageState + extends AnimatedWidgetBaseState { + ImageSpecTween? _spec; + + // forEachTween + @override + // ignore: avoid-dynamic + void forEachTween(TweenVisitor visitor) { + _spec = visitor( + _spec, + widget.spec, + // ignore: avoid-dynamic + (dynamic value) => ImageSpecTween(begin: value as ImageSpec?), + ) as ImageSpecTween?; + } + + @override + Widget build(BuildContext context) { + final spec = _spec?.evaluate(animation); + + return MixedImage( + spec: spec, + image: widget.image, + frameBuilder: widget.frameBuilder, + loadingBuilder: widget.loadingBuilder, + errorBuilder: widget.errorBuilder, + semanticLabel: widget.semanticLabel, + excludeFromSemantics: widget.excludeFromSemantics, ); } } diff --git a/lib/src/specs/stack/stack_widget.dart b/lib/src/specs/stack/stack_widget.dart index 0227682d7..57107c847 100644 --- a/lib/src/specs/stack/stack_widget.dart +++ b/lib/src/specs/stack/stack_widget.dart @@ -40,19 +40,19 @@ class StyledStack extends StyledWidget { } class MixedStack extends StatelessWidget { - const MixedStack({required this.spec, super.key, this.children}); + const MixedStack({this.spec, super.key, this.children}); final List? children; - final StackSpec spec; + final StackSpec? spec; @override Widget build(BuildContext context) { // The Stack widget is used here, applying the resolved styles from StackSpec. return Stack( - alignment: spec.alignment ?? _defaultStack.alignment, - textDirection: spec.textDirection, - fit: spec.fit ?? _defaultStack.fit, - clipBehavior: spec.clipBehavior ?? _defaultStack.clipBehavior, + alignment: spec?.alignment ?? _defaultStack.alignment, + textDirection: spec?.textDirection, + fit: spec?.fit ?? _defaultStack.fit, + clipBehavior: spec?.clipBehavior ?? _defaultStack.clipBehavior, children: children ?? const [], ); } diff --git a/lib/src/specs/text/text_widget.dart b/lib/src/specs/text/text_widget.dart index 9d5d0cbdb..44601dab4 100644 --- a/lib/src/specs/text/text_widget.dart +++ b/lib/src/specs/text/text_widget.dart @@ -109,7 +109,7 @@ class MixedText extends StatelessWidget { class AnimatedMixedText extends ImplicitlyAnimatedWidget { const AnimatedMixedText({ required this.text, - required this.spec, + this.spec, this.semanticsLabel, this.locale, super.key, @@ -120,7 +120,7 @@ class AnimatedMixedText extends ImplicitlyAnimatedWidget { final String text; final String? semanticsLabel; final Locale? locale; - final TextSpec spec; + final TextSpec? spec; @override AnimatedWidgetBaseState createState() => @@ -146,20 +146,11 @@ class _AnimatedMixedTextState Widget build(BuildContext context) { final spec = _textSpecTween!.evaluate(animation); - return Text( - spec.directive?.apply(widget.text) ?? widget.text, - style: spec.style, - strutStyle: spec.strutStyle, - textAlign: spec.textAlign, - textDirection: spec.textDirection, - locale: widget.locale, - softWrap: spec.softWrap, - overflow: spec.overflow, - textScaleFactor: spec.textScaleFactor, - maxLines: spec.maxLines, + return MixedText( + text: widget.text, + spec: spec, semanticsLabel: widget.semanticsLabel, - textWidthBasis: spec.textWidthBasis, - textHeightBehavior: spec.textHeightBehavior, + locale: widget.locale, ); } }