From dcda9dc522aad99f99db18de8a6364d74e980a14 Mon Sep 17 00:00:00 2001 From: GittHub-d Date: Thu, 6 Feb 2025 15:42:38 +0200 Subject: [PATCH] [MDS-1593] Radio component --- .../storybook/stories/primitives/radio.dart | 48 +++-- lib/src/widgets/radio/radio.dart | 189 ++++++++---------- 2 files changed, 107 insertions(+), 130 deletions(-) diff --git a/example/lib/src/storybook/stories/primitives/radio.dart b/example/lib/src/storybook/stories/primitives/radio.dart index 7adeee16..bf44f54b 100644 --- a/example/lib/src/storybook/stories/primitives/radio.dart +++ b/example/lib/src/storybook/stories/primitives/radio.dart @@ -26,7 +26,7 @@ class _RadioStoryState extends State { Widget build(BuildContext context) { final activeColorKnob = context.knobs.nullable.options( label: "activeColor", - description: "MoonColors variants for checked MoonRadio.", + description: "MoonColors variants for selected MoonRadio.", enabled: false, initial: 0, // piccolo @@ -37,7 +37,7 @@ class _RadioStoryState extends State { final inactiveColorKnob = context.knobs.nullable.options( label: "inactiveColor", - description: "MoonColors variants for unchecked MoonRadio.", + description: "MoonColors variants for unselected MoonRadio.", enabled: false, initial: 0, // piccolo @@ -82,29 +82,27 @@ class _RadioStoryState extends State { const TextDivider(text: "MoonRadio with label"), ...List.generate( 2, - (int index) => MoonMenuItem( - absorbGestures: true, - onTap: isDisabledKnob - ? null - : () => setState( - () { - if (isToggleableKnob && - valueLabel == ChoiceLabel.values[index]) { - valueLabel = null; - } else { - valueLabel = ChoiceLabel.values[index]; - } - }, - ), - label: Text("With label #${index + 1}"), - trailing: MoonRadio( - value: ChoiceLabel.values[index], - groupValue: valueLabel, - toggleable: isToggleableKnob, - tapAreaSizeValue: 0, - onChanged: isDisabledKnob ? null : (_) {}, - ), - ), + (int index) { + final ChoiceLabel value = ChoiceLabel.values[index]; + final shouldReset = isToggleableKnob && valueLabel == value; + + return MoonMenuItem( + absorbGestures: true, + onTap: isDisabledKnob + ? null + : () => setState( + () => valueLabel = shouldReset ? null : value, + ), + label: Text("With label #${index + 1}"), + trailing: MoonRadio( + value: value, + groupValue: valueLabel, + toggleable: isToggleableKnob, + tapAreaSizeValue: 0, + onChanged: isDisabledKnob ? null : (_) {}, + ), + ); + }, ), ], ), diff --git a/lib/src/widgets/radio/radio.dart b/lib/src/widgets/radio/radio.dart index 1a67d89c..02cc0de2 100644 --- a/lib/src/widgets/radio/radio.dart +++ b/lib/src/widgets/radio/radio.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; +import 'package:moon_core/moon_core.dart'; + import 'package:moon_design/src/theme/effects/effects_theme.dart'; +import 'package:moon_design/src/theme/effects/focus_effect.dart'; import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/theme/tokens/opacities.dart'; import 'package:moon_design/src/theme/tokens/tokens.dart'; -import 'package:moon_design/src/utils/touch_target_padding.dart'; -import 'package:moon_design/src/widgets/common/effects/focus_effect.dart'; -import 'package:moon_design/src/widgets/radio/radio_painter.dart'; + import 'package:moon_tokens/moon_tokens.dart'; class MoonRadio extends StatefulWidget { @@ -86,128 +88,105 @@ class MoonRadio extends StatefulWidget { required this.onChanged, }); - bool get _selected => value == groupValue; - @override State> createState() => _RadioState(); } -class _RadioState extends State> - with TickerProviderStateMixin, ToggleableStateMixin { - final MoonRadioPainter _painter = MoonRadioPainter(); - - void _handleChanged(bool? selected) { - if (selected == null) { - widget.onChanged!(null); - - return; - } - if (selected) { - widget.onChanged!(widget.value); - } - } +class _RadioState extends State> { + bool get _selected => widget.value == widget.groupValue; + + ShapeDecorationWithPremultipliedAlpha _getFocusDecoration( + double width, + Color color, + ) => + ShapeDecorationWithPremultipliedAlpha( + shape: CircleBorder( + side: BorderSide( + width: width, + color: color, + strokeAlign: BorderSide.strokeAlignOutside, + ), + ), + ); @override - void didUpdateWidget(MoonRadio oldWidget) { - super.didUpdateWidget(oldWidget); + Widget build(BuildContext context) { + const double sizeValue = 16; + const double dotSizeValue = (sizeValue - 1) / 2; - if (widget._selected != oldWidget._selected) animateToValue(); - } + final MoonFocusEffect focusEffect = + MoonEffectsTheme(tokens: MoonTokens.light).controlFocusEffect; - @override - void dispose() { - _painter.dispose(); + final Color effectiveActiveColor = + widget.activeColor ?? MoonColors.light.piccolo; - super.dispose(); - } + final Color effectiveInactiveColor = + widget.inactiveColor ?? MoonColors.light.trunks; - @override - ValueChanged? get onChanged => - widget.onChanged != null ? _handleChanged : null; + final Color effectiveFocusEffectColor = focusEffect.effectColor; - @override - bool get tristate => widget.toggleable; + final double effectiveFocusEffectExtent = focusEffect.effectExtent; - @override - bool? get value => widget._selected; + final Duration effectiveFocusEffectDuration = focusEffect.effectDuration; - @override - Widget build(BuildContext context) { - const Size size = Size(16, 16); - - final Color effectiveActiveColor = widget.activeColor ?? - context.moonTheme?.radioTheme.colors.activeColor ?? - MoonColors.light.piccolo; - - final Color effectiveInactiveColor = widget.inactiveColor ?? - context.moonTheme?.radioTheme.colors.inactiveColor ?? - MoonColors.light.trunks; - - final Color effectiveFocusEffectColor = - context.moonEffects?.controlFocusEffect.effectColor ?? - MoonEffectsTheme(tokens: MoonTokens.light) - .controlFocusEffect - .effectColor; - - final double effectiveFocusEffectExtent = - context.moonEffects?.controlFocusEffect.effectExtent ?? - MoonEffectsTheme(tokens: MoonTokens.light) - .controlFocusEffect - .effectExtent; - - final Duration effectiveFocusEffectDuration = - context.moonEffects?.controlFocusEffect.effectDuration ?? - MoonEffectsTheme(tokens: MoonTokens.light) - .controlFocusEffect - .effectDuration; - - final Curve effectiveFocusEffectCurve = - context.moonEffects?.controlFocusEffect.effectCurve ?? - MoonEffectsTheme(tokens: MoonTokens.light) - .controlFocusEffect - .effectCurve; + final Curve effectiveFocusEffectCurve = focusEffect.effectCurve; final double effectiveDisabledOpacityValue = context.moonOpacities?.disabled ?? MoonOpacities.opacities.disabled; - final WidgetStateProperty effectiveMouseCursor = - WidgetStateProperty.resolveWith((Set states) { - return WidgetStateMouseCursor.clickable.resolve(states); - }); - - return Semantics( - label: widget.semanticLabel, - inMutuallyExclusiveGroup: true, - checked: widget._selected, - child: TouchTargetPadding( - minSize: Size(widget.tapAreaSizeValue, widget.tapAreaSizeValue), - child: MoonFocusEffect( - show: states.contains(WidgetState.focused), - effectExtent: effectiveFocusEffectExtent, - childBorderRadius: BorderRadius.circular(8), - effectColor: effectiveFocusEffectColor, - effectCurve: effectiveFocusEffectCurve, - effectDuration: effectiveFocusEffectDuration, - child: RepaintBoundary( - child: AnimatedOpacity( - opacity: states.contains(WidgetState.disabled) - ? effectiveDisabledOpacityValue - : 1, - duration: effectiveFocusEffectDuration, - child: buildToggleable( - focusNode: widget.focusNode, - autofocus: widget.autofocus, - mouseCursor: effectiveMouseCursor, - size: size, - painter: _painter - ..position = position - ..activeColor = effectiveActiveColor - ..inactiveColor = effectiveInactiveColor, - ), - ), + final Style dotStyle = Style( + $box.chain + ..width(_selected ? dotSizeValue : 0) + ..height(_selected ? dotSizeValue : 0) + ..color(effectiveActiveColor) + ..shape.circle(), + ).animate(duration: effectiveFocusEffectDuration); + + final Style baseStyle = Style( + $box.chain + ..height(sizeValue) + ..width(sizeValue) + ..border + .color(_selected ? effectiveActiveColor : effectiveInactiveColor) + ..alignment.center() + ..shape.circle(), + ).animate(duration: effectiveFocusEffectDuration); + + final Style effectsStyle = Style( + $box.shapeDecoration.as(_getFocusDecoration(0, Colors.transparent)), + $with.animatedOpacity( + opacity: widget.onChanged == null ? effectiveDisabledOpacityValue : 1, + duration: effectiveFocusEffectDuration, + ), + $on.focus( + $box.shapeDecoration.as( + _getFocusDecoration( + effectiveFocusEffectExtent, + effectiveFocusEffectColor, ), ), ), + ).animate( + duration: effectiveFocusEffectDuration, + curve: effectiveFocusEffectCurve, + ); + + return MoonBaseSingleSelectWidget( + value: widget.value, + groupValue: widget.groupValue, + toggleable: widget.toggleable, + focusNode: widget.focusNode, + autofocus: widget.autofocus, + semanticLabel: widget.semanticLabel, + tapAreaSizeValue: widget.tapAreaSizeValue, + style: effectsStyle, + onChanged: widget.onChanged, + child: Box( + style: baseStyle, + child: Box( + style: dotStyle, + ), + ), ); } }