Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 23 additions & 25 deletions example/lib/src/storybook/stories/primitives/radio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class _RadioStoryState extends State<RadioStory> {
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
Expand All @@ -37,7 +37,7 @@ class _RadioStoryState extends State<RadioStory> {

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
Expand Down Expand Up @@ -82,29 +82,27 @@ class _RadioStoryState extends State<RadioStory> {
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 : (_) {},
),
);
},
),
],
),
Expand Down
189 changes: 84 additions & 105 deletions lib/src/widgets/radio/radio.dart
Original file line number Diff line number Diff line change
@@ -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<T> extends StatefulWidget {
Expand Down Expand Up @@ -86,128 +88,105 @@ class MoonRadio<T> extends StatefulWidget {
required this.onChanged,
});

bool get _selected => value == groupValue;

@override
State<MoonRadio<T>> createState() => _RadioState<T>();
}

class _RadioState<T> extends State<MoonRadio<T>>
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<T> extends State<MoonRadio<T>> {
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<T> 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<bool?>? 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<MouseCursor> effectiveMouseCursor =
WidgetStateProperty.resolveWith<MouseCursor>((Set<WidgetState> 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,
),
),
);
}
}