From 434fb73bb36095adcff7948fe1228ff4f031390c Mon Sep 17 00:00:00 2001 From: Harry Sild <46851868+Kypsis@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:43:38 +0200 Subject: [PATCH] feat: [MDS-431] Create Switch widget (#77) --- example/lib/src/storybook/stories/switch.dart | 137 +++++ example/lib/src/storybook/storybook.dart | 2 + lib/moon_design.dart | 2 + lib/src/theme/opacity.dart | 2 +- lib/src/theme/switch/switch_colors.dart | 68 +++ lib/src/theme/switch/switch_properties.dart | 51 ++ lib/src/theme/switch/switch_shadows.dart | 46 ++ .../theme/switch/switch_size_properties.dart | 87 +++ lib/src/theme/switch/switch_sizes.dart | 62 +++ lib/src/theme/switch/switch_theme.dart | 81 +++ lib/src/theme/theme.dart | 31 +- lib/src/widgets/buttons/button.dart | 2 - lib/src/widgets/chips/chip.dart | 2 - .../widgets/common/animated_icon_theme.dart | 10 +- lib/src/widgets/common/base_control.dart | 45 +- lib/src/widgets/switch/switch.dart | 496 ++++++++++++++++++ lib/src/widgets/tag/tag.dart | 1 + lib/src/widgets/tooltip/tooltip.dart | 74 +-- 18 files changed, 1124 insertions(+), 75 deletions(-) create mode 100644 example/lib/src/storybook/stories/switch.dart create mode 100644 lib/src/theme/switch/switch_colors.dart create mode 100644 lib/src/theme/switch/switch_properties.dart create mode 100644 lib/src/theme/switch/switch_shadows.dart create mode 100644 lib/src/theme/switch/switch_size_properties.dart create mode 100644 lib/src/theme/switch/switch_sizes.dart create mode 100644 lib/src/theme/switch/switch_theme.dart create mode 100644 lib/src/widgets/switch/switch.dart diff --git a/example/lib/src/storybook/stories/switch.dart b/example/lib/src/storybook/stories/switch.dart new file mode 100644 index 00000000..a3902d7e --- /dev/null +++ b/example/lib/src/storybook/stories/switch.dart @@ -0,0 +1,137 @@ +import 'package:example/src/storybook/common/options.dart'; +import 'package:example/src/storybook/common/widgets/text_divider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +bool value = false; + +class SwitchStory extends Story { + SwitchStory() + : super( + name: "Switch", + builder: (context) { + final switchSizesKnob = context.knobs.options( + label: "MoonSwitchSize", + description: "Switch size variants.", + initial: MoonSwitchSize.xs, + options: const [ + Option(label: "x2s", value: MoonSwitchSize.x2s), + Option(label: "xs", value: MoonSwitchSize.xs), + Option(label: "sm", value: MoonSwitchSize.sm), + ], + ); + + final thumbColorsKnob = context.knobs.options( + label: "thumbColor", + description: "MoonColors variants for the Switch thumb.", + initial: 7, // goten + options: colorOptions, + ); + + final thumbColor = colorTable(context)[thumbColorsKnob]; + + final activeTrackColorsKnob = context.knobs.options( + label: "activeTrackColor", + description: "MoonColors variants for the active Switch track.", + initial: 0, // piccolo + options: colorOptions, + ); + + final activeTrackColor = colorTable(context)[activeTrackColorsKnob]; + + final inactiveTrackColorsKnob = context.knobs.options( + label: "inactiveTrackColor", + description: "MoonColors variants for the active Switch track.", + initial: 2, // beerus + options: colorOptions, + ); + + final inactiveTrackColor = colorTable(context)[inactiveTrackColorsKnob]; + + final isDisabled = context.knobs.boolean( + label: "Disabled", + description: "onChanged() is null.", + ); + + final setRtlModeKnob = context.knobs.boolean( + label: "RTL mode", + description: "Switch between LTR and RTL modes.", + ); + + return Directionality( + textDirection: setRtlModeKnob ? TextDirection.rtl : TextDirection.ltr, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + const TextDivider(text: "Customisable Switch"), + const SizedBox(height: 32), + StatefulBuilder( + builder: (context, setState) { + return MoonSwitch( + switchSize: switchSizesKnob, + thumbColor: thumbColor, + activeTrackColor: activeTrackColor, + inactiveTrackColor: inactiveTrackColor, + value: value, + onChanged: isDisabled ? null : (newValue) => setState(() => value = newValue), + ); + }, + ), + const SizedBox(height: 40), + const TextDivider(text: "Switches with custom children"), + const SizedBox(height: 32), + StatefulBuilder( + builder: (context, setState) { + return MoonSwitch( + activeThumbWidget: const Icon( + MoonIconsGeneric.check_alternative24, + size: 14, + ), + inactiveThumbWidget: const Icon( + MoonIconsControls.close24, + size: 12, + ), + activeTrackWidget: const Text( + "ON", + style: TextStyle(fontSize: 8), + textAlign: TextAlign.center, + ), + inactiveTrackWidget: const Text( + "OFF", + style: TextStyle(fontSize: 8), + textAlign: TextAlign.center, + ), + value: value, + onChanged: (newValue) => setState(() => value = newValue), + ); + }, + ), + const SizedBox(height: 32), + StatefulBuilder( + builder: (context, setState) { + return MoonSwitch( + activeTrackWidget: const Icon( + MoonIconsGeneric.check_alternative24, + size: 14, + ), + inactiveTrackWidget: const Icon( + MoonIconsControls.close24, + size: 12, + ), + value: value, + onChanged: (newValue) => setState(() => value = newValue), + ); + }, + ), + const SizedBox(height: 64), + ], + ), + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index 6650fe36..91cbde29 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -8,6 +8,7 @@ import 'package:example/src/storybook/stories/icons.dart'; import 'package:example/src/storybook/stories/linear_loader.dart'; import 'package:example/src/storybook/stories/linear_progress.dart'; import 'package:example/src/storybook/stories/popover.dart'; +import 'package:example/src/storybook/stories/switch.dart'; import 'package:example/src/storybook/stories/tag.dart'; import 'package:example/src/storybook/stories/tooltip.dart'; import 'package:flutter/material.dart'; @@ -73,6 +74,7 @@ class StorybookPage extends StatelessWidget { LinearLoaderStory(), LinearProgressStory(), PopoverStory(), + SwitchStory(), TagStory(), TooltipStory(), ], diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 181b7cb0..8b81991c 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -14,6 +14,7 @@ export 'package:moon_design/src/theme/progress/circular_progress/circular_progre export 'package:moon_design/src/theme/progress/linear_progress/linear_progress_theme.dart'; export 'package:moon_design/src/theme/shadows.dart'; export 'package:moon_design/src/theme/sizes.dart'; +export 'package:moon_design/src/theme/switch/switch_theme.dart'; export 'package:moon_design/src/theme/theme.dart'; export 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; export 'package:moon_design/src/theme/typography/text_colors.dart'; @@ -44,5 +45,6 @@ export 'package:moon_design/src/widgets/loaders/linear_loader.dart'; export 'package:moon_design/src/widgets/popover/popover.dart'; export 'package:moon_design/src/widgets/progress/circular_progress.dart'; export 'package:moon_design/src/widgets/progress/linear_progress.dart'; +export 'package:moon_design/src/widgets/switch/switch.dart'; export 'package:moon_design/src/widgets/tag/tag.dart'; export 'package:moon_design/src/widgets/tooltip/tooltip.dart'; diff --git a/lib/src/theme/opacity.dart b/lib/src/theme/opacity.dart index 49f343cc..2d277107 100644 --- a/lib/src/theme/opacity.dart +++ b/lib/src/theme/opacity.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; @immutable class MoonOpacity extends ThemeExtension with DiagnosticableTreeMixin { - static const opacities = MoonOpacity(disabled: 0.68); + static const opacities = MoonOpacity(disabled: 0.32); /// Disabled opacity value. final double disabled; diff --git a/lib/src/theme/switch/switch_colors.dart b/lib/src/theme/switch/switch_colors.dart new file mode 100644 index 00000000..7a71a679 --- /dev/null +++ b/lib/src/theme/switch/switch_colors.dart @@ -0,0 +1,68 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; + +@immutable +class MoonSwitchColors extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonSwitchColors( + activeTrackColor: MoonColors.light.piccolo, + inactiveTrackColor: MoonColors.light.beerus, + thumbColor: MoonColors.light.goten, + ); + + static final dark = MoonSwitchColors( + activeTrackColor: MoonColors.dark.piccolo, + inactiveTrackColor: MoonColors.dark.beerus, + thumbColor: MoonColors.dark.goten, + ); + + /// Switch active track color. + final Color activeTrackColor; + + /// Switch inactive track color. + final Color inactiveTrackColor; + + /// Switch thumbcolor. + final Color thumbColor; + + const MoonSwitchColors({ + required this.activeTrackColor, + required this.inactiveTrackColor, + required this.thumbColor, + }); + + @override + MoonSwitchColors copyWith({ + Color? activeTrackColor, + Color? inactiveTrackColor, + Color? thumbColor, + }) { + return MoonSwitchColors( + activeTrackColor: activeTrackColor ?? this.activeTrackColor, + inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor, + thumbColor: thumbColor ?? this.thumbColor, + ); + } + + @override + MoonSwitchColors lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchColors) return this; + + return MoonSwitchColors( + activeTrackColor: Color.lerp(activeTrackColor, other.activeTrackColor, t)!, + inactiveTrackColor: Color.lerp(inactiveTrackColor, other.inactiveTrackColor, t)!, + thumbColor: Color.lerp(thumbColor, other.thumbColor, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonSwitchColors")) + ..add(ColorProperty("activeTrackColor", activeTrackColor)) + ..add(ColorProperty("inactiveTrackColor", inactiveTrackColor)) + ..add(ColorProperty("thumbColor", thumbColor)); + } +} diff --git a/lib/src/theme/switch/switch_properties.dart b/lib/src/theme/switch/switch_properties.dart new file mode 100644 index 00000000..92168549 --- /dev/null +++ b/lib/src/theme/switch/switch_properties.dart @@ -0,0 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +@immutable +class MoonSwitchProperties extends ThemeExtension with DiagnosticableTreeMixin { + static const properties = MoonSwitchProperties( + transitionDuration: Duration(milliseconds: 200), + transitionCurve: Curves.easeInOutCubic, + ); + + /// Switch transition duration. + final Duration transitionDuration; + + /// Switch transition curve. + final Curve transitionCurve; + + const MoonSwitchProperties({ + required this.transitionDuration, + required this.transitionCurve, + }); + + @override + MoonSwitchProperties copyWith({ + Duration? transitionDuration, + Curve? transitionCurve, + }) { + return MoonSwitchProperties( + transitionDuration: transitionDuration ?? this.transitionDuration, + transitionCurve: transitionCurve ?? this.transitionCurve, + ); + } + + @override + MoonSwitchProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchProperties) return this; + + return MoonSwitchProperties( + transitionDuration: lerpDuration(transitionDuration, other.transitionDuration, t), + transitionCurve: other.transitionCurve, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonSwitchProperties")) + ..add(DiagnosticsProperty("transitionDuration", transitionDuration)) + ..add(DiagnosticsProperty("transitionCurve", transitionCurve)); + } +} diff --git a/lib/src/theme/switch/switch_shadows.dart b/lib/src/theme/switch/switch_shadows.dart new file mode 100644 index 00000000..2430da72 --- /dev/null +++ b/lib/src/theme/switch/switch_shadows.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/shadows.dart'; + +@immutable +class MoonSwitchShadows extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonSwitchShadows( + thumbShadows: MoonShadows.light.sm, + ); + + static final dark = MoonSwitchShadows( + thumbShadows: MoonShadows.dark.sm, + ); + + /// Switch thumb shadows. + final List thumbShadows; + + const MoonSwitchShadows({ + required this.thumbShadows, + }); + + @override + MoonSwitchShadows copyWith({List? thumbShadows}) { + return MoonSwitchShadows( + thumbShadows: thumbShadows ?? this.thumbShadows, + ); + } + + @override + MoonSwitchShadows lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchShadows) return this; + + return MoonSwitchShadows( + thumbShadows: BoxShadow.lerpList(thumbShadows, other.thumbShadows, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonSwitchShadows")) + ..add(DiagnosticsProperty>("shadows", thumbShadows)); + } +} diff --git a/lib/src/theme/switch/switch_size_properties.dart b/lib/src/theme/switch/switch_size_properties.dart new file mode 100644 index 00000000..e7b681c5 --- /dev/null +++ b/lib/src/theme/switch/switch_size_properties.dart @@ -0,0 +1,87 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/sizes.dart'; + +@immutable +class MoonSwitchSizeProperties extends ThemeExtension with DiagnosticableTreeMixin { + static final x2s = MoonSwitchSizeProperties( + width: 2 * MoonSizes.sizes.x3s + 3 * MoonSizes.sizes.x6s, + height: MoonSizes.sizes.x2s, + thumbSizeValue: MoonSizes.sizes.x3s, + padding: EdgeInsets.all(MoonSizes.sizes.x6s), + ); + + static final xs = MoonSwitchSizeProperties( + width: 2 * MoonSizes.sizes.x2s + 3 * MoonSizes.sizes.x5s, + height: MoonSizes.sizes.xs, + thumbSizeValue: MoonSizes.sizes.x2s, + padding: EdgeInsets.all(MoonSizes.sizes.x5s), + ); + + static final sm = MoonSwitchSizeProperties( + width: 2 * MoonSizes.sizes.xs + 3 * MoonSizes.sizes.x5s, + height: MoonSizes.sizes.sm, + thumbSizeValue: MoonSizes.sizes.xs, + padding: EdgeInsets.all(MoonSizes.sizes.x5s), + ); + + /// Switch width. + final double width; + + /// Switch height. + final double height; + + /// Switch thumb size. + final double thumbSizeValue; + + /// Switch track padding. + final EdgeInsets padding; + + const MoonSwitchSizeProperties({ + required this.width, + required this.height, + required this.thumbSizeValue, + required this.padding, + }); + + @override + MoonSwitchSizeProperties copyWith({ + double? width, + double? height, + double? thumbSizeValue, + EdgeInsets? padding, + }) { + return MoonSwitchSizeProperties( + width: width ?? this.width, + height: height ?? this.height, + thumbSizeValue: thumbSizeValue ?? this.thumbSizeValue, + padding: padding ?? this.padding, + ); + } + + @override + MoonSwitchSizeProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchSizeProperties) return this; + + return MoonSwitchSizeProperties( + width: lerpDouble(width, other.width, t)!, + height: lerpDouble(height, other.height, t)!, + thumbSizeValue: lerpDouble(thumbSizeValue, other.thumbSizeValue, t)!, + padding: EdgeInsets.lerp(padding, other.padding, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonSwitchSizeProperties")) + ..add(DoubleProperty("width", width)) + ..add(DoubleProperty("height", height)) + ..add(DoubleProperty("thumbSizeValue", thumbSizeValue)) + ..add(DiagnosticsProperty("padding", padding)); + } +} diff --git a/lib/src/theme/switch/switch_sizes.dart b/lib/src/theme/switch/switch_sizes.dart new file mode 100644 index 00000000..8e7ba1cc --- /dev/null +++ b/lib/src/theme/switch/switch_sizes.dart @@ -0,0 +1,62 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/switch/switch_size_properties.dart'; + +@immutable +class MoonSwitchSizes extends ThemeExtension with DiagnosticableTreeMixin { + static final sizes = MoonSwitchSizes( + x2s: MoonSwitchSizeProperties.x2s, + xs: MoonSwitchSizeProperties.xs, + sm: MoonSwitchSizeProperties.sm, + ); + + /// (2x) Extra small switch properties. + final MoonSwitchSizeProperties x2s; + + /// Extra small switch properties. + final MoonSwitchSizeProperties xs; + + /// Small switch properties. + final MoonSwitchSizeProperties sm; + + const MoonSwitchSizes({ + required this.x2s, + required this.xs, + required this.sm, + }); + + @override + MoonSwitchSizes copyWith({ + MoonSwitchSizeProperties? x2s, + MoonSwitchSizeProperties? xs, + MoonSwitchSizeProperties? sm, + }) { + return MoonSwitchSizes( + x2s: x2s ?? this.x2s, + xs: xs ?? this.xs, + sm: sm ?? this.sm, + ); + } + + @override + MoonSwitchSizes lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchSizes) return this; + + return MoonSwitchSizes( + x2s: x2s.lerp(other.x2s, t), + xs: xs.lerp(other.xs, t), + sm: sm.lerp(other.sm, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonSwitchSizes")) + ..add(DiagnosticsProperty("x2s", x2s)) + ..add(DiagnosticsProperty("xs", xs)) + ..add(DiagnosticsProperty("sm", sm)); + } +} diff --git a/lib/src/theme/switch/switch_theme.dart b/lib/src/theme/switch/switch_theme.dart new file mode 100644 index 00000000..713ac92e --- /dev/null +++ b/lib/src/theme/switch/switch_theme.dart @@ -0,0 +1,81 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/switch/switch_colors.dart'; +import 'package:moon_design/src/theme/switch/switch_properties.dart'; +import 'package:moon_design/src/theme/switch/switch_shadows.dart'; +import 'package:moon_design/src/theme/switch/switch_sizes.dart'; + +@immutable +class MoonSwitchTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonSwitchTheme( + colors: MoonSwitchColors.light, + properties: MoonSwitchProperties.properties, + shadows: MoonSwitchShadows.light, + sizes: MoonSwitchSizes.sizes, + ); + + static final dark = MoonSwitchTheme( + colors: MoonSwitchColors.dark, + properties: MoonSwitchProperties.properties, + shadows: MoonSwitchShadows.dark, + sizes: MoonSwitchSizes.sizes, + ); + + /// Switch colors. + final MoonSwitchColors colors; + + /// Switch properties. + final MoonSwitchProperties properties; + + /// Switch shadows. + final MoonSwitchShadows shadows; + + /// Switch sizes. + final MoonSwitchSizes sizes; + + const MoonSwitchTheme({ + required this.colors, + required this.properties, + required this.shadows, + required this.sizes, + }); + + @override + MoonSwitchTheme copyWith({ + MoonSwitchColors? colors, + MoonSwitchProperties? properties, + MoonSwitchShadows? shadows, + MoonSwitchSizes? sizes, + }) { + return MoonSwitchTheme( + colors: colors ?? this.colors, + properties: properties ?? this.properties, + shadows: shadows ?? this.shadows, + sizes: sizes ?? this.sizes, + ); + } + + @override + MoonSwitchTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonSwitchTheme) return this; + + return MoonSwitchTheme( + colors: colors.lerp(other.colors, t), + properties: properties.lerp(other.properties, t), + shadows: shadows.lerp(other.shadows, t), + sizes: sizes.lerp(other.sizes, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder diagnosticProperties) { + super.debugFillProperties(diagnosticProperties); + diagnosticProperties + ..add(DiagnosticsProperty("type", "MoonSwitchTheme")) + ..add(DiagnosticsProperty("colors", colors)) + ..add(DiagnosticsProperty("properties", properties)) + ..add(DiagnosticsProperty("shadows", shadows)) + ..add(DiagnosticsProperty("sizes", sizes)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index 1bfa0e19..851eb04a 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -15,6 +15,7 @@ import 'package:moon_design/src/theme/progress/circular_progress/circular_progre import 'package:moon_design/src/theme/progress/linear_progress/linear_progress_theme.dart'; import 'package:moon_design/src/theme/shadows.dart'; import 'package:moon_design/src/theme/sizes.dart'; +import 'package:moon_design/src/theme/switch/switch_theme.dart'; import 'package:moon_design/src/theme/tag/tag_theme.dart'; import 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; import 'package:moon_design/src/theme/typography/typography.dart'; @@ -36,6 +37,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { popover: MoonPopoverTheme.light, shadows: MoonShadows.light, sizes: MoonSizes.sizes, + switchTheme: MoonSwitchTheme.light, tag: MoonTagTheme.light, tooltip: MoonTooltipTheme.light, typography: MoonTypography.light, @@ -56,27 +58,28 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { popover: MoonPopoverTheme.dark, shadows: MoonShadows.dark, sizes: MoonSizes.sizes, + switchTheme: MoonSwitchTheme.dark, tag: MoonTagTheme.dark, tooltip: MoonTooltipTheme.dark, typography: MoonTypography.dark, ); - /// Moon Design System avatar theming. + /// Moon Design System MoonAvatar widget theming. final MoonAvatarTheme avatar; /// Moon Design System borders. final MoonBorders borders; - /// Moon Design System buttons theming. + /// Moon Design System MoonButton widgets theming. final MoonButtonTheme button; - /// Moon Design System chip theming. + /// Moon Design System MoonChip widgets theming. final MoonChipTheme chip; - /// Moon Design System circular loader theming. + /// Moon Design System MoonCircularLoader widget theming. final MoonCircularLoaderTheme circularLoader; - /// Moon Design System circular progress theming. + /// Moon Design System MoonCircularProgress widget theming. final MoonCircularProgressTheme circularProgress; /// Moon Design System colors. @@ -85,16 +88,16 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System effects. final MoonEffects effects; - /// Moon Design System linear loader theming. + /// Moon Design System MoonLinearLoader widget theming. final MoonLinearLoaderTheme linearLoader; - /// Moon Design System linear progress theming. + /// Moon Design System MoonLinearProgress widget theming. final MoonLinearProgressTheme linearProgress; /// Moon Design System opacities. final MoonOpacity opacity; - /// Moon Design System popover theming. + /// Moon Design System MoonPopover widget theming. final MoonPopoverTheme popover; /// Moon Design System shadows. @@ -103,10 +106,13 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System sizes. final MoonSizes sizes; - /// Moon Design System tag theming. + /// Moon Design System MoonSwitch widget theming. + final MoonSwitchTheme switchTheme; + + /// Moon Design System MoonTag widget theming. final MoonTagTheme tag; - /// Moon Design System tooltip theming. + /// Moon Design System MoonTooltip widget theming. final MoonTooltipTheme tooltip; /// Moon Design System typography. @@ -127,6 +133,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.popover, required this.shadows, required this.sizes, + required this.switchTheme, required this.tag, required this.tooltip, required this.typography, @@ -148,6 +155,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonPopoverTheme? popover, MoonShadows? shadows, MoonSizes? sizes, + MoonSwitchTheme? switchTheme, MoonTagTheme? tag, MoonTooltipTheme? tooltip, MoonTypography? typography, @@ -167,6 +175,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { popover: popover ?? this.popover, shadows: shadows ?? this.shadows, sizes: sizes ?? this.sizes, + switchTheme: switchTheme ?? this.switchTheme, tag: tag ?? this.tag, tooltip: tooltip ?? this.tooltip, typography: typography ?? this.typography, @@ -192,6 +201,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { popover: popover.lerp(other.popover, t), shadows: shadows.lerp(other.shadows, t), sizes: sizes.lerp(other.sizes, t), + switchTheme: switchTheme.lerp(other.switchTheme, t), tag: tag.lerp(other.tag, t), tooltip: tooltip.lerp(other.tooltip, t), typography: typography.lerp(other.typography, t), @@ -217,6 +227,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonPopoverTheme", popover)) ..add(DiagnosticsProperty("MoonShadows", shadows)) ..add(DiagnosticsProperty("MoonSizes", sizes)) + ..add(DiagnosticsProperty("MoonSwitchTheme", switchTheme)) ..add(DiagnosticsProperty("MoonTagTheme", tag)) ..add(DiagnosticsProperty("MoonTooltipTheme", tooltip)) ..add(DiagnosticsProperty("MoonTypography", typography)); diff --git a/lib/src/widgets/buttons/button.dart b/lib/src/widgets/buttons/button.dart index 67c0acfa..23cfa636 100644 --- a/lib/src/widgets/buttons/button.dart +++ b/lib/src/widgets/buttons/button.dart @@ -442,13 +442,11 @@ class MoonButton extends StatelessWidget { padding: isFullWidth ? EdgeInsets.zero : correctedPadding, child: AnimatedIconTheme( duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, color: effectiveTextColor, size: effectiveMoonButtonSize.iconSizeValue, child: AnimatedDefaultTextStyle( style: TextStyle(color: effectiveTextColor, fontSize: effectiveMoonButtonSize.textStyle.fontSize), duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, child: isFullWidth ? Stack( fit: StackFit.expand, diff --git a/lib/src/widgets/chips/chip.dart b/lib/src/widgets/chips/chip.dart index 0ca15d61..f1bef78e 100644 --- a/lib/src/widgets/chips/chip.dart +++ b/lib/src/widgets/chips/chip.dart @@ -303,12 +303,10 @@ class MoonChip extends StatelessWidget { child: AnimatedIconTheme( duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, color: effectiveTextColor, size: effectiveMoonChipSize.iconSizeValue, child: AnimatedDefaultTextStyle( duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, style: TextStyle(fontSize: effectiveMoonChipSize.textStyle.fontSize, color: effectiveTextColor), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/src/widgets/common/animated_icon_theme.dart b/lib/src/widgets/common/animated_icon_theme.dart index 4013c9be..6ca8a4b6 100644 --- a/lib/src/widgets/common/animated_icon_theme.dart +++ b/lib/src/widgets/common/animated_icon_theme.dart @@ -7,14 +7,14 @@ class AnimatedIconTheme extends ImplicitlyAnimatedWidget { /// The color must not be null. /// /// When this property is changed, the icon color will be animated over [duration] time. - final Color color; + final Color? color; /// The target size for icon. /// /// The size must not be null. /// /// When this property is changed, the icon size will be animated over [duration] time. - final double size; + final double? size; /// The widget below this widget in the tree. /// @@ -30,8 +30,8 @@ class AnimatedIconTheme extends ImplicitlyAnimatedWidget { super.onEnd, super.curve, required super.duration, - required this.color, - required this.size, + this.color, + this.size, required this.child, }); @@ -53,7 +53,7 @@ class _AnimatedIconThemeState extends AnimatedWidgetBaseState @override void forEachTween(TweenVisitor visitor) { _color = visitor(_color, widget.color, (dynamic value) => ColorTween(begin: value as Color)) as ColorTween?; - _size = visitor(_size, Size(widget.size, widget.size), (dynamic value) => SizeTween(begin: value as Size)) + _size = visitor(_size, Size(widget.size ?? 0, widget.size ?? 0), (dynamic value) => SizeTween(begin: value as Size)) as SizeTween?; } diff --git a/lib/src/widgets/common/base_control.dart b/lib/src/widgets/common/base_control.dart index cf10279c..c63cd3b4 100644 --- a/lib/src/widgets/common/base_control.dart +++ b/lib/src/widgets/common/base_control.dart @@ -313,7 +313,7 @@ class _MoonBaseControlState extends State { final double effectiveFocusEffectExtent = widget.focusEffectExtent ?? context.moonEffects?.controlFocusEffect.effectExtent ?? - MoonFocusEffects.darkFocusEffect.effectExtent; + MoonFocusEffects.lightFocusEffect.effectExtent; final Curve effectiveFocusEffectCurve = widget.focusEffectCurve ?? context.moonEffects?.controlFocusEffect.effectCurve ?? @@ -374,27 +374,28 @@ class _MoonBaseControlState extends State { enabled: _isEnabled, focusable: _isEnabled, focused: _isFocused, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _handleTap, - onTapDown: _handleTapDown, - onTapUp: _handleTapUp, - onLongPress: _handleLongPress, - onLongPressStart: _handleLongPressStart, - onLongPressUp: _handleLongPressUp, - onHorizontalDragStart: _handleHorizontalDragStart, - onHorizontalDragEnd: _handleHorizontalDragEnd, - onVerticalDragStart: _handleVerticalDragStart, - onVerticalDragEnd: _handleVerticalDragEnd, - child: FocusableActionDetector( - enabled: _isEnabled && widget.isFocusable, - actions: _actions, - mouseCursor: _cursor, - focusNode: _effectiveFocusNode, - autofocus: _isEnabled && widget.autofocus, - onFocusChange: _handleFocusChange, - onShowFocusHighlight: _handleFocus, - onShowHoverHighlight: _handleHover, + child: FocusableActionDetector( + enabled: _isEnabled && widget.isFocusable, + actions: _actions, + mouseCursor: _cursor, + focusNode: _effectiveFocusNode, + autofocus: _isEnabled && widget.autofocus, + onFocusChange: _handleFocusChange, + onShowFocusHighlight: _handleFocus, + onShowHoverHighlight: _handleHover, + child: GestureDetector( + excludeFromSemantics: true, + behavior: HitTestBehavior.opaque, + onTap: _handleTap, + onTapDown: _handleTapDown, + onTapUp: _handleTapUp, + onLongPress: _handleLongPress, + onLongPressStart: _handleLongPressStart, + onLongPressUp: _handleLongPressUp, + onHorizontalDragStart: _handleHorizontalDragStart, + onHorizontalDragEnd: _handleHorizontalDragEnd, + onVerticalDragStart: _handleVerticalDragStart, + onVerticalDragEnd: _handleVerticalDragEnd, child: TouchTargetPadding( minSize: widget.ensureMinimalTouchTargetSize ? Size(widget.minTouchTargetSize, widget.minTouchTargetSize) diff --git a/lib/src/widgets/switch/switch.dart b/lib/src/widgets/switch/switch.dart new file mode 100644 index 00000000..f51ad9dd --- /dev/null +++ b/lib/src/widgets/switch/switch.dart @@ -0,0 +1,496 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/effects/focus_effects.dart'; +import 'package:moon_design/src/theme/opacity.dart'; +import 'package:moon_design/src/theme/shadows.dart'; +import 'package:moon_design/src/theme/switch/switch_size_properties.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/widgets/common/animated_icon_theme.dart'; +import 'package:moon_design/src/widgets/common/effects/focus_effect.dart'; + +enum MoonSwitchSize { + x2s, + xs, + sm, +} + +class MoonSwitch extends StatefulWidget { + /// Determines if the switch is on or off. + final bool value; + + /// Callback when the switch is toggled on or off. + final ValueChanged? onChanged; + + /// The size of the switch. + final MoonSwitchSize? switchSize; + + /// Whether the switch has haptic feedback (vibration) when toggled. + final bool hasHapticFeedback; + + /// The width of the switch. + final double? width; + + /// The height of the switch. + final double? height; + + /// The size of the thumb. + final double? thumbSizeValue; + + /// The color to use on the switch when the switch is on. + final Color? activeTrackColor; + + /// The color to use on the switch when the switch is off. + final Color? inactiveTrackColor; + + /// The color of the thumb. + final Color? thumbColor; + + /// The padding of the switch. + final EdgeInsets? padding; + + /// The duration for the switch animation. + final Duration? duration; + + /// The curve for the switch animation. + final Curve? curve; + + /// The focus node for the switch. + final FocusNode? focusNode; + + /// The widget to display when the switch is on (left slot). + final Widget? activeTrackWidget; + + /// The widget to display when the switch is off (right slot). + final Widget? inactiveTrackWidget; + + /// The widget inside the thumb when switch is on. + final Widget? activeThumbWidget; + + /// The widget inside the thumb when the switch is off. + final Widget? inactiveThumbWidget; + + const MoonSwitch({ + super.key, + required this.value, + this.onChanged, + this.switchSize, + this.width, + this.height, + this.thumbSizeValue, + this.hasHapticFeedback = true, + this.activeTrackColor, + this.inactiveTrackColor, + this.thumbColor, + this.padding, + this.duration, + this.curve, + this.focusNode, + this.activeTrackWidget, + this.inactiveTrackWidget, + this.activeThumbWidget, + this.inactiveThumbWidget, + }); + + @override + _MoonSwitchState createState() => _MoonSwitchState(); +} + +class _MoonSwitchState extends State with SingleTickerProviderStateMixin { + AnimationController? _animationController; + CurvedAnimation? _curvedAnimation; + CurvedAnimation? _curvedAnimationWithOvershoot; + Animation? _thumbFadeAnimation; + Animation? _activeTrackWidgetFadeAnimation; + Animation? _inactiveTrackWidgetFadeAnimation; + + late Animation? _alignmentAnimation; + late Animation? _trackDecorationAnimation; + + late final Map> _actions = { + ActivateIntent: CallbackAction(onInvoke: (_) => _handleTap()) + }; + + FocusNode? _focusNode; + + // A non-null boolean value that changes to true at the end of a drag if the + // switch must be animated to the _curvedAnimationWithOvershoot indicated by the widget's value. + bool _needsPositionAnimation = false; + + bool _isFocused = false; + + FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode()); + bool get _isInteractive => widget.onChanged != null; + + MoonSwitchSizeProperties _getMoonSwitchSize(BuildContext context, MoonSwitchSize? moonSwitchSize) { + switch (moonSwitchSize) { + case MoonSwitchSize.x2s: + return context.moonTheme?.switchTheme.sizes.x2s ?? MoonSwitchSizeProperties.x2s; + case MoonSwitchSize.xs: + return context.moonTheme?.switchTheme.sizes.xs ?? MoonSwitchSizeProperties.xs; + case MoonSwitchSize.sm: + return context.moonTheme?.switchTheme.sizes.sm ?? MoonSwitchSizeProperties.sm; + default: + return context.moonTheme?.switchTheme.sizes.xs ?? MoonSwitchSizeProperties.xs; + } + } + + Color _getTextOrIconColor({required Color backgroundColor}) { + final backgroundLuminance = backgroundColor.computeLuminance(); + + if (backgroundLuminance > 0.5) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; + } + } + + // `isLinear` must be true if the _curvedAnimationWithOvershoot animation is trying to move the + // thumb to the closest end after the most recent drag animation, so the curve + // does not change when the controller's value is not 0 or 1. + // + // It can be set to false when it's an implicit animation triggered by + // widget.value changes. + void _resumePositionAnimation({bool isLinear = true}) { + _needsPositionAnimation = false; + + _curvedAnimationWithOvershoot! + ..curve = isLinear ? Curves.linear : Curves.ease + ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped; + + if (widget.value) { + _animationController!.forward(); + } else { + _animationController!.reverse(); + } + } + + void _handleFocus(bool focus) { + if (focus != _isFocused && mounted) { + setState(() => _isFocused = focus); + } + } + + void _handleFocusChange(bool hasFocus) { + setState(() { + _isFocused = hasFocus; + }); + } + + void _handleTapDown(TapDownDetails details) { + if (_isInteractive) { + _needsPositionAnimation = false; + } + } + + void _handleTap() { + if (_isInteractive) { + widget.onChanged!(!widget.value); + _emitVibration(); + } + } + + void _handleTapUp(TapUpDetails details) { + if (_isInteractive) { + _needsPositionAnimation = false; + } + } + + void _handleDragStart(DragStartDetails details) { + if (_isInteractive) { + _needsPositionAnimation = false; + _emitVibration(); + } + } + + void _handleDragUpdate({ + required DragUpdateDetails details, + required double switchWidth, + required double thumbSizeValue, + required EdgeInsets padding, + }) { + if (_isInteractive) { + _curvedAnimationWithOvershoot! + ..curve = Curves.linear + ..reverseCurve = Curves.linear; + + switch (Directionality.of(context)) { + case TextDirection.rtl: + _animationController!.value += + -details.primaryDelta! / (switchWidth - (thumbSizeValue + padding.right + padding.left)); + break; + case TextDirection.ltr: + _animationController!.value += + details.primaryDelta! / (switchWidth - (thumbSizeValue + padding.right + padding.left)); + break; + } + } + } + + void _handleDragEnd(DragEndDetails details) { + // Deferring the animation to the next build phase. + setState(() { + _needsPositionAnimation = true; + }); + // Call onChanged when the user's intent to change value is clear. + if (_curvedAnimationWithOvershoot!.value >= 0.5 != widget.value) { + widget.onChanged!(!widget.value); + } + } + + void _emitVibration() { + if (widget.hasHapticFeedback) { + HapticFeedback.lightImpact(); + } + } + + @override + void didUpdateWidget(MoonSwitch oldWidget) { + super.didUpdateWidget(oldWidget); + + if (_needsPositionAnimation || oldWidget.value != widget.value) { + _resumePositionAnimation(isLinear: _needsPositionAnimation); + } + + if (_curvedAnimationWithOvershoot!.value == 0.0 || _curvedAnimationWithOvershoot!.value == 1.0) { + _curvedAnimationWithOvershoot! + ..curve = Curves.easeOutBack + ..reverseCurve = Curves.easeOutBack.flipped; + } + } + + @override + void dispose() { + _animationController!.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_needsPositionAnimation) { + _resumePositionAnimation(); + } + + final MoonSwitchSizeProperties effectiveMoonSwitchSize = _getMoonSwitchSize(context, widget.switchSize); + + final double effectiveWidth = widget.width ?? effectiveMoonSwitchSize.width; + final double effectiveHeight = widget.height ?? effectiveMoonSwitchSize.height; + final double effectiveThumbSizeValue = widget.thumbSizeValue ?? effectiveMoonSwitchSize.thumbSizeValue; + final EdgeInsets effectivePadding = widget.padding ?? effectiveMoonSwitchSize.padding; + final BorderRadius effectiveBorderRadius = BorderRadius.circular(effectiveThumbSizeValue / 2); + + final Color effectiveActiveTrackColor = + widget.activeTrackColor ?? context.moonTheme?.switchTheme.colors.activeTrackColor ?? MoonColors.light.piccolo; + + final Color effectiveInactiveTrackColor = widget.inactiveTrackColor ?? + context.moonTheme?.switchTheme.colors.inactiveTrackColor ?? + MoonColors.light.beerus; + + final Color effectiveThumbColor = + widget.thumbColor ?? context.moonTheme?.switchTheme.colors.thumbColor ?? MoonColors.light.goten; + + final List effectiveThumbShadow = + context.moonTheme?.switchTheme.shadows.thumbShadows ?? MoonShadows.light.sm; + + final double effectiveDisabledOpacity = context.moonTheme?.opacity.disabled ?? MoonOpacity.opacities.disabled; + + final Duration effectiveDuration = widget.duration ?? + context.moonTheme?.switchTheme.properties.transitionDuration ?? + const Duration(milliseconds: 200); + + final Curve effectiveCurve = + widget.curve ?? context.moonTheme?.switchTheme.properties.transitionCurve ?? Curves.easeInOutCubic; + + final Color effectiveFocusEffectColor = + context.moonEffects?.controlFocusEffect.effectColor ?? MoonFocusEffects.lightFocusEffect.effectColor; + + final Curve effectiveFocusEffectCurve = + context.moonEffects?.controlFocusEffect.effectCurve ?? MoonFocusEffects.lightFocusEffect.effectCurve; + + final Duration effectiveFocusEffectDuration = + context.moonEffects?.controlFocusEffect.effectDuration ?? MoonFocusEffects.lightFocusEffect.effectDuration; + + _animationController ??= AnimationController( + vsync: this, + value: widget.value ? 1.0 : 0.0, + duration: effectiveDuration, + ); + + _curvedAnimation ??= CurvedAnimation( + parent: _animationController!, + curve: effectiveCurve, + ); + + _curvedAnimationWithOvershoot ??= CurvedAnimation( + parent: _animationController!, + curve: effectiveCurve, + ); + + _alignmentAnimation = AlignmentTween( + begin: Directionality.of(context) == TextDirection.ltr ? Alignment.centerLeft : Alignment.centerRight, + end: Directionality.of(context) == TextDirection.ltr ? Alignment.centerRight : Alignment.centerLeft, + ).animate(_curvedAnimationWithOvershoot!); + + _trackDecorationAnimation = DecorationTween( + begin: BoxDecoration( + borderRadius: BorderRadius.circular(effectiveHeight / 2), + color: effectiveInactiveTrackColor, + ), + end: BoxDecoration( + borderRadius: BorderRadius.circular(effectiveHeight / 2), + color: effectiveActiveTrackColor, + ), + ).animate(_curvedAnimation!); + + _thumbFadeAnimation ??= TweenSequence([ + TweenSequenceItem( + tween: Tween(begin: 1.0, end: 0.0), + weight: 50.0, + ), + TweenSequenceItem( + tween: Tween(begin: 0.0, end: 1.0), + weight: 50.0, + ), + ]).animate(_curvedAnimation!); + + _activeTrackWidgetFadeAnimation ??= Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController!, + curve: const Interval(0.7, 1.0), + ), + ); + + _inactiveTrackWidgetFadeAnimation ??= Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation( + parent: _animationController!, + curve: const Interval(0.0, 0.3), + ), + ); + + final Color iconColor = _getTextOrIconColor(backgroundColor: effectiveThumbColor); + final Color activeTextColor = _getTextOrIconColor(backgroundColor: effectiveActiveTrackColor); + final Color inactiveTextColor = _getTextOrIconColor(backgroundColor: effectiveInactiveTrackColor); + + return Semantics( + toggled: widget.value, + child: FocusableActionDetector( + enabled: _isInteractive, + actions: _actions, + focusNode: _effectiveFocusNode, + onFocusChange: _handleFocusChange, + onShowFocusHighlight: _handleFocus, + mouseCursor: _isInteractive ? MouseCursor.defer : SystemMouseCursors.forbidden, + child: GestureDetector( + excludeFromSemantics: true, + onTap: _handleTap, + onTapDown: _handleTapDown, + onTapUp: _handleTapUp, + onHorizontalDragStart: _handleDragStart, + onHorizontalDragUpdate: (DragUpdateDetails details) => _handleDragUpdate( + details: details, + switchWidth: effectiveWidth, + thumbSizeValue: effectiveThumbSizeValue, + padding: effectivePadding, + ), + onHorizontalDragEnd: _handleDragEnd, + child: RepaintBoundary( + child: AnimatedBuilder( + animation: _animationController!, + builder: (context, child) { + return AnimatedOpacity( + opacity: _isInteractive ? 1 : effectiveDisabledOpacity, + duration: effectiveDuration, + curve: effectiveCurve, + child: SizedBox( + width: effectiveWidth, + height: effectiveHeight, + child: DecoratedBoxTransition( + decoration: _trackDecorationAnimation!, + child: Padding( + padding: effectivePadding, + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconTheme( + data: IconThemeData(color: activeTextColor), + child: AnimatedDefaultTextStyle( + style: TextStyle(color: activeTextColor), + duration: effectiveDuration, + child: Expanded( + child: FadeTransition( + opacity: _activeTrackWidgetFadeAnimation!, + child: widget.activeTrackWidget ?? const SizedBox.shrink(), + ), + ), + ), + ), + SizedBox(width: effectivePadding.left), + IconTheme( + data: IconThemeData(color: inactiveTextColor), + child: AnimatedDefaultTextStyle( + style: TextStyle(color: inactiveTextColor), + duration: effectiveDuration, + child: Expanded( + child: FadeTransition( + opacity: _inactiveTrackWidgetFadeAnimation!, + child: widget.inactiveTrackWidget ?? const SizedBox.shrink(), + ), + ), + ), + ) + ], + ), + Align( + alignment: _alignmentAnimation!.value, + child: AnimatedIconTheme( + color: iconColor, + duration: effectiveDuration, + size: effectiveThumbSizeValue, + child: AnimatedDefaultTextStyle( + style: TextStyle(color: inactiveTextColor), + duration: effectiveDuration, + child: MoonFocusEffect( + show: _isFocused, + effectExtent: effectivePadding.top * 1.5, + effectColor: effectiveFocusEffectColor, + effectCurve: effectiveFocusEffectCurve, + effectDuration: effectiveFocusEffectDuration, + childBorderRadius: effectiveBorderRadius, + child: Container( + width: effectiveThumbSizeValue, + height: effectiveThumbSizeValue, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: effectiveBorderRadius, + color: effectiveThumbColor, + boxShadow: effectiveThumbShadow, + ), + child: FadeTransition( + opacity: _thumbFadeAnimation!, + child: _curvedAnimation!.value > 0.5 + ? widget.activeThumbWidget + : widget.inactiveThumbWidget, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/widgets/tag/tag.dart b/lib/src/widgets/tag/tag.dart index ac9c788c..87a14777 100644 --- a/lib/src/widgets/tag/tag.dart +++ b/lib/src/widgets/tag/tag.dart @@ -128,6 +128,7 @@ class MoonTag extends StatelessWidget { button: false, focusable: false, child: GestureDetector( + excludeFromSemantics: true, onTap: onTap, onLongPress: onLongPress, child: Container( diff --git a/lib/src/widgets/tooltip/tooltip.dart b/lib/src/widgets/tooltip/tooltip.dart index 2497e3a1..041da610 100644 --- a/lib/src/widgets/tooltip/tooltip.dart +++ b/lib/src/widgets/tooltip/tooltip.dart @@ -93,6 +93,9 @@ class MoonTooltip extends StatefulWidget { /// `RouteObserver` used to listen for route changes that will hide the tooltip when the widget's route is not active. final RouteObserver>? routeObserver; + /// The semantic label for the tooltip. + final String? semanticLabel; + /// The widget that its placed inside the tooltip and functions as its content. final Widget content; @@ -124,6 +127,7 @@ class MoonTooltip extends StatefulWidget { this.transitionCurve, this.tooltipShadows, this.routeObserver, + this.semanticLabel, required this.content, required this.child, }); @@ -473,41 +477,45 @@ class MoonTooltipState extends State with RouteAware, SingleTickerP tooltipTargetGlobalRight: tooltipTargetGlobalRight.dx, ); - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTapDown: _handleTap, - child: UnconstrainedBox( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: tooltipPositionParameters.offset, - followerAnchor: tooltipPositionParameters.followerAnchor, - targetAnchor: tooltipPositionParameters.targetAnchor, - child: RepaintBoundary( - child: FadeTransition( - opacity: _curvedAnimation!, - child: DefaultTextStyle( - style: effectiveTextStyle, - child: Container( - key: _tooltipKey, - constraints: BoxConstraints(maxWidth: tooltipPositionParameters.tooltipMaxWidth), - padding: effectiveContentPadding, - decoration: ShapeDecoration( - color: effectiveBackgroundColor, - shadows: effectiveTooltipShadows, - shape: TooltipShape( - arrowBaseWidth: effectiveArrowBaseWidth, - arrowLength: effectiveArrowLength, - arrowOffset: widget.arrowOffsetValue, - arrowTipDistance: effectiveArrowTipDistance, - borderColor: widget.borderColor, - borderRadius: effectiveBorderRadius, - borderWidth: widget.borderWidth, - childWidth: targetRenderBox.size.width, - tooltipPosition: tooltipPosition, + return Semantics( + label: widget.semanticLabel, + child: GestureDetector( + excludeFromSemantics: true, + behavior: HitTestBehavior.translucent, + onTapDown: _handleTap, + child: UnconstrainedBox( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: tooltipPositionParameters.offset, + followerAnchor: tooltipPositionParameters.followerAnchor, + targetAnchor: tooltipPositionParameters.targetAnchor, + child: RepaintBoundary( + child: FadeTransition( + opacity: _curvedAnimation!, + child: DefaultTextStyle( + style: effectiveTextStyle, + child: Container( + key: _tooltipKey, + constraints: BoxConstraints(maxWidth: tooltipPositionParameters.tooltipMaxWidth), + padding: effectiveContentPadding, + decoration: ShapeDecoration( + color: effectiveBackgroundColor, + shadows: effectiveTooltipShadows, + shape: TooltipShape( + arrowBaseWidth: effectiveArrowBaseWidth, + arrowLength: effectiveArrowLength, + arrowOffset: widget.arrowOffsetValue, + arrowTipDistance: effectiveArrowTipDistance, + borderColor: widget.borderColor, + borderRadius: effectiveBorderRadius, + borderWidth: widget.borderWidth, + childWidth: targetRenderBox.size.width, + tooltipPosition: tooltipPosition, + ), ), + child: widget.content, ), - child: widget.content, ), ), ),