From 7f81e3870f273ad2f162872fc0863e104b3a46ab Mon Sep 17 00:00:00 2001 From: BirgittMajas <79840500+BirgittMajas@users.noreply.github.com> Date: Fri, 11 Aug 2023 14:44:32 +0300 Subject: [PATCH] feat: [MDS-629] Add pin for linear progress (#240) --- .../storybook/stories/linear_progress.dart | 56 ++++++++- lib/moon_design.dart | 4 + .../circular_progress_theme.dart | 2 +- .../linear_progress_theme.dart | 2 +- .../progress_pin/progress_pin_colors.dart | 72 ++++++++++++ .../progress_pin/progress_pin_properties.dart | 80 +++++++++++++ .../progress_pin/progress_pin_theme.dart | 74 ++++++++++++ lib/src/theme/theme.dart | 10 ++ .../widgets/progress/circular_progress.dart | 2 +- lib/src/widgets/progress/linear_progress.dart | 39 +++++-- lib/src/widgets/progress_pin/pin_style.dart | 50 ++++++++ .../widgets/progress_pin/progress_pin.dart | 82 ++++++++++++++ .../progress_pin/progress_pin_painter.dart | 107 ++++++++++++++++++ 13 files changed, 567 insertions(+), 13 deletions(-) create mode 100644 lib/src/theme/progress_pin/progress_pin_colors.dart create mode 100644 lib/src/theme/progress_pin/progress_pin_properties.dart create mode 100644 lib/src/theme/progress_pin/progress_pin_theme.dart create mode 100644 lib/src/widgets/progress_pin/pin_style.dart create mode 100644 lib/src/widgets/progress_pin/progress_pin.dart create mode 100644 lib/src/widgets/progress_pin/progress_pin_painter.dart diff --git a/example/lib/src/storybook/stories/linear_progress.dart b/example/lib/src/storybook/stories/linear_progress.dart index e6033dac..42531e53 100644 --- a/example/lib/src/storybook/stories/linear_progress.dart +++ b/example/lib/src/storybook/stories/linear_progress.dart @@ -24,7 +24,7 @@ class LinearProgressStory extends Story { final progressColorKnob = context.knobs.nullable.options( label: "color", - description: "MoonColors variants for LinearProgress color.", + description: "MoonColors variants for LinearProgress progress.", enabled: false, initial: 0, // piccolo @@ -44,6 +44,39 @@ class LinearProgressStory extends Story { final backgroundColor = colorTable(context)[progressBackgroundColorKnob ?? 40]; + final pinColorKnob = context.knobs.nullable.options( + label: "pinColor", + description: "MoonColors variants for LinearProgress pin.", + enabled: false, + initial: 0, + // piccolo + options: colorOptions, + ); + + final pinColor = colorTable(context)[pinColorKnob ?? 40]; + + final pinBorderColorKnob = context.knobs.nullable.options( + label: "pinBorderColor", + description: "MoonColors variants for LinearProgress pin border.", + enabled: false, + initial: 0, + // piccolo + options: colorOptions, + ); + + final pinBorderColor = colorTable(context)[pinBorderColorKnob ?? 40]; + + final thumbColorKnob = context.knobs.nullable.options( + label: "thumbColor", + description: "MoonColors variants for LinearProgress thumb.", + enabled: false, + initial: 0, + // piccolo + options: colorOptions, + ); + + final thumbColor = colorTable(context)[thumbColorKnob ?? 40]; + final borderRadiusKnob = context.knobs.nullable.sliderInt( label: "borderRadius", description: "Border radius for LinearProgress.", @@ -58,10 +91,29 @@ class LinearProgressStory extends Story { initial: 0.5, ); + final showPinKnob = context.knobs.boolean( + label: "showPin", + description: "Show pin for LinearProgress", + initial: true, + ); + + final showPinShadowKnob = context.knobs.boolean( + label: "showPinShadow", + description: "Show pin shadow for LinearProgress", + initial: true, + ); + return Center( child: Padding( - padding: const EdgeInsets.symmetric(vertical: 64), + padding: const EdgeInsets.symmetric(vertical: 64, horizontal: 8), child: MoonLinearProgress( + showPin: showPinKnob, + pinStyle: PinStyle( + pinColor: pinColor, + pinBorderColor: pinBorderColor, + thumbColor: thumbColor, + showShadow: showPinShadowKnob, + ), value: linearProgressValueKnob, linearProgressSize: progressSizeKnob, color: color, diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 62764d84..e9422dd4 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -15,10 +15,12 @@ export 'package:moon_design/src/theme/drawer/drawer_theme.dart'; export 'package:moon_design/src/theme/effects/effects_theme.dart'; export 'package:moon_design/src/theme/loaders/circular_loader/circular_loader_theme.dart'; export 'package:moon_design/src/theme/loaders/linear_loader/linear_loader_theme.dart'; +export 'package:moon_design/src/theme/menu_item/menu_item_theme.dart'; export 'package:moon_design/src/theme/modal/modal_theme.dart'; export 'package:moon_design/src/theme/popover/popover_theme.dart'; export 'package:moon_design/src/theme/progress/circular_progress/circular_progress_theme.dart'; export 'package:moon_design/src/theme/progress/linear_progress/linear_progress_theme.dart'; +export 'package:moon_design/src/theme/progress_pin/progress_pin_theme.dart'; export 'package:moon_design/src/theme/radio/radio_theme.dart'; export 'package:moon_design/src/theme/segmented_control/segmented_control_theme.dart'; export 'package:moon_design/src/theme/switch/switch_theme.dart'; @@ -77,6 +79,8 @@ export 'package:moon_design/src/widgets/modal/modal.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/progress_pin/pin_style.dart'; +export 'package:moon_design/src/widgets/progress_pin/progress_pin.dart'; export 'package:moon_design/src/widgets/radio/radio.dart'; export 'package:moon_design/src/widgets/segmented_control/segment.dart'; export 'package:moon_design/src/widgets/segmented_control/segment_style.dart'; diff --git a/lib/src/theme/progress/circular_progress/circular_progress_theme.dart b/lib/src/theme/progress/circular_progress/circular_progress_theme.dart index 17266645..dced5d40 100644 --- a/lib/src/theme/progress/circular_progress/circular_progress_theme.dart +++ b/lib/src/theme/progress/circular_progress/circular_progress_theme.dart @@ -23,7 +23,7 @@ class MoonCircularProgressTheme extends ThemeExtension wi }) : colors = colors ?? MoonLinearProgressColors( color: tokens.colors.piccolo, - backgroundColor: tokens.colors.trunks, + backgroundColor: tokens.colors.beerus, ), sizes = sizes ?? MoonLinearProgressSizes(tokens: tokens); diff --git a/lib/src/theme/progress_pin/progress_pin_colors.dart b/lib/src/theme/progress_pin/progress_pin_colors.dart new file mode 100644 index 00000000..757b36bf --- /dev/null +++ b/lib/src/theme/progress_pin/progress_pin_colors.dart @@ -0,0 +1,72 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/utils/color_premul_lerp.dart'; + +@immutable +class MoonProgressPinColors extends ThemeExtension with DiagnosticableTreeMixin { + /// Progress pin color. + final Color pinColor; + + /// Progress pin border color. + final Color pinBorderColor; + + /// Progress pin thumb color. + final Color thumbColor; + + /// Progress pin shadow color. + final Color shadowColor; + + /// Progress pin text color. + final Color textColor; + + const MoonProgressPinColors({ + required this.pinColor, + required this.pinBorderColor, + required this.thumbColor, + required this.shadowColor, + required this.textColor, + }); + + @override + MoonProgressPinColors copyWith({ + Color? pinColor, + Color? pinBorderColor, + Color? thumbColor, + Color? shadowColor, + Color? textColor, + }) { + return MoonProgressPinColors( + pinColor: pinColor ?? this.pinColor, + pinBorderColor: pinBorderColor ?? this.pinBorderColor, + thumbColor: thumbColor ?? this.thumbColor, + shadowColor: shadowColor ?? this.shadowColor, + textColor: textColor ?? this.textColor, + ); + } + + @override + MoonProgressPinColors lerp(ThemeExtension? other, double t) { + if (other is! MoonProgressPinColors) return this; + + return MoonProgressPinColors( + pinColor: colorPremulLerp(pinColor, other.pinColor, t)!, + pinBorderColor: colorPremulLerp(pinBorderColor, other.pinBorderColor, t)!, + thumbColor: colorPremulLerp(thumbColor, other.thumbColor, t)!, + shadowColor: colorPremulLerp(shadowColor, other.shadowColor, t)!, + textColor: colorPremulLerp(textColor, other.textColor, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonProgressPinColors")) + ..add(ColorProperty("pinColor", pinColor)) + ..add(ColorProperty("pinBorderColor", pinBorderColor)) + ..add(ColorProperty("thumbColor", thumbColor)) + ..add(ColorProperty("shadowColor", shadowColor)) + ..add(ColorProperty("textColor", textColor)); + } +} diff --git a/lib/src/theme/progress_pin/progress_pin_properties.dart b/lib/src/theme/progress_pin/progress_pin_properties.dart new file mode 100644 index 00000000..0ebacba0 --- /dev/null +++ b/lib/src/theme/progress_pin/progress_pin_properties.dart @@ -0,0 +1,80 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +@immutable +class MoonProgressPinProperties extends ThemeExtension with DiagnosticableTreeMixin { + /// Progress pin shadow elevation. + final double shadowElevation; + + /// Vertical space between pin and linear progress. + final double pinDistance; + + /// Progress pin width. + final double pinWidth; + + /// Progress pin border width. + final double pinBorderWidth; + + /// Thumb width multiplier for linear progress. + final double thumbWidthMultiplier; + + /// Progress pin text style. + final TextStyle textStyle; + + const MoonProgressPinProperties({ + required this.shadowElevation, + required this.pinDistance, + required this.pinWidth, + required this.pinBorderWidth, + required this.thumbWidthMultiplier, + required this.textStyle, + }); + + @override + MoonProgressPinProperties copyWith({ + double? shadowElevation, + double? pinDistance, + double? pinWidth, + double? pinBorderWidth, + double? thumbWidthMultiplier, + TextStyle? textStyle, + }) { + return MoonProgressPinProperties( + shadowElevation: shadowElevation ?? this.shadowElevation, + pinDistance: pinDistance ?? this.pinDistance, + pinWidth: pinWidth ?? this.pinWidth, + pinBorderWidth: pinBorderWidth ?? this.pinBorderWidth, + thumbWidthMultiplier: thumbWidthMultiplier ?? this.thumbWidthMultiplier, + textStyle: textStyle ?? this.textStyle, + ); + } + + @override + MoonProgressPinProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonProgressPinProperties) return this; + + return MoonProgressPinProperties( + shadowElevation: lerpDouble(shadowElevation, other.shadowElevation, t)!, + pinDistance: lerpDouble(pinDistance, other.pinDistance, t)!, + pinWidth: lerpDouble(pinWidth, other.pinWidth, t)!, + pinBorderWidth: lerpDouble(pinBorderWidth, other.pinBorderWidth, t)!, + thumbWidthMultiplier: lerpDouble(thumbWidthMultiplier, other.thumbWidthMultiplier, t)!, + textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonProgressPinProperties")) + ..add(DoubleProperty("shadowElevation", shadowElevation)) + ..add(DoubleProperty("pinDistance", pinDistance)) + ..add(DoubleProperty("pinWidth", pinWidth)) + ..add(DoubleProperty("pinBorderWidth", pinBorderWidth)) + ..add(DoubleProperty("thumbWidthMultiplier", thumbWidthMultiplier)) + ..add(DiagnosticsProperty("textStyle", textStyle)); + } +} diff --git a/lib/src/theme/progress_pin/progress_pin_theme.dart b/lib/src/theme/progress_pin/progress_pin_theme.dart new file mode 100644 index 00000000..27d4d445 --- /dev/null +++ b/lib/src/theme/progress_pin/progress_pin_theme.dart @@ -0,0 +1,74 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/progress_pin/progress_pin_colors.dart'; +import 'package:moon_design/src/theme/progress_pin/progress_pin_properties.dart'; +import 'package:moon_design/src/theme/tokens/tokens.dart'; + +@immutable +class MoonProgressPinTheme extends ThemeExtension with DiagnosticableTreeMixin { + /// MDS tokens. + final MoonTokens tokens; + + /// Progress pin colors. + final MoonProgressPinColors colors; + + /// Progress pin properties. + final MoonProgressPinProperties properties; + + MoonProgressPinTheme({ + required this.tokens, + MoonProgressPinColors? colors, + MoonProgressPinProperties? properties, + }) : colors = colors ?? + MoonProgressPinColors( + pinColor: tokens.colors.popo, + pinBorderColor: tokens.colors.goten, + thumbColor: tokens.colors.goten, + shadowColor: tokens.colors.popo, + textColor: tokens.colors.goten, + ), + properties = properties ?? + MoonProgressPinProperties( + pinDistance: 6, + pinWidth: 36, + pinBorderWidth: 2, + thumbWidthMultiplier: 1.5, + shadowElevation: 6, + textStyle: tokens.typography.caption.text10.copyWith(letterSpacing: 0.5), + ); + + @override + MoonProgressPinTheme copyWith({ + MoonTokens? tokens, + MoonProgressPinColors? colors, + MoonProgressPinProperties? properties, + }) { + return MoonProgressPinTheme( + tokens: tokens ?? this.tokens, + colors: colors ?? this.colors, + properties: properties ?? this.properties, + ); + } + + @override + MoonProgressPinTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonProgressPinTheme) return this; + + return MoonProgressPinTheme( + tokens: tokens.lerp(other.tokens, t), + colors: colors.lerp(other.colors, t), + properties: properties.lerp(other.properties, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder diagnosticProperties) { + super.debugFillProperties(diagnosticProperties); + diagnosticProperties + ..add(DiagnosticsProperty("type", "MoonProgressPinTheme")) + ..add(DiagnosticsProperty("tokens", tokens)) + ..add(DiagnosticsProperty("colors", colors)) + ..add(DiagnosticsProperty("properties", properties)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index 38779301..0074624c 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -20,6 +20,7 @@ import 'package:moon_design/src/theme/modal/modal_theme.dart'; import 'package:moon_design/src/theme/popover/popover_theme.dart'; import 'package:moon_design/src/theme/progress/circular_progress/circular_progress_theme.dart'; import 'package:moon_design/src/theme/progress/linear_progress/linear_progress_theme.dart'; +import 'package:moon_design/src/theme/progress_pin/progress_pin_theme.dart'; import 'package:moon_design/src/theme/radio/radio_theme.dart'; import 'package:moon_design/src/theme/segmented_control/segmented_control_theme.dart'; import 'package:moon_design/src/theme/switch/switch_theme.dart'; @@ -100,6 +101,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System MoonPopover widget theming. final MoonPopoverTheme popoverTheme; + /// Moon Design System MoonProgressPin widget theming. + final MoonProgressPinTheme progressPinTheme; + /// Moon Design System MoonRadio widget theming. final MoonRadioTheme radioTheme; @@ -148,6 +152,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonMenuItemTheme? menuItemTheme, MoonModalTheme? modalTheme, MoonPopoverTheme? popoverTheme, + MoonProgressPinTheme? progressPinTheme, MoonRadioTheme? radioTheme, MoonSegmentedControlTheme? segmentedControlTheme, MoonSwitchTheme? switchTheme, @@ -176,6 +181,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { menuItemTheme = menuItemTheme ?? MoonMenuItemTheme(tokens: tokens), modalTheme = modalTheme ?? MoonModalTheme(tokens: tokens), popoverTheme = popoverTheme ?? MoonPopoverTheme(tokens: tokens), + progressPinTheme = progressPinTheme ?? MoonProgressPinTheme(tokens: tokens), radioTheme = radioTheme ?? MoonRadioTheme(tokens: tokens), segmentedControlTheme = segmentedControlTheme ?? MoonSegmentedControlTheme(tokens: tokens), switchTheme = switchTheme ?? MoonSwitchTheme(tokens: tokens), @@ -208,6 +214,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonMenuItemTheme? menuItemTheme, MoonModalTheme? modalTheme, MoonPopoverTheme? popoverTheme, + MoonProgressPinTheme? progressPinTheme, MoonRadioTheme? radioTheme, MoonSegmentedControlTheme? segmentedControlTheme, MoonSwitchTheme? switchTheme, @@ -239,6 +246,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { menuItemTheme: menuItemTheme ?? this.menuItemTheme, modalTheme: modalTheme ?? this.modalTheme, popoverTheme: popoverTheme ?? this.popoverTheme, + progressPinTheme: progressPinTheme ?? this.progressPinTheme, radioTheme: radioTheme ?? this.radioTheme, segmentedControlTheme: segmentedControlTheme ?? this.segmentedControlTheme, switchTheme: switchTheme ?? this.switchTheme, @@ -276,6 +284,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { menuItemTheme: menuItemTheme.lerp(other.menuItemTheme, t), modalTheme: modalTheme.lerp(other.modalTheme, t), popoverTheme: popoverTheme.lerp(other.popoverTheme, t), + progressPinTheme: progressPinTheme.lerp(other.progressPinTheme, t), radioTheme: radioTheme.lerp(other.radioTheme, t), segmentedControlTheme: segmentedControlTheme.lerp(other.segmentedControlTheme, t), switchTheme: switchTheme.lerp(other.switchTheme, t), @@ -312,6 +321,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonMenuItemTheme", menuItemTheme)) ..add(DiagnosticsProperty("MoonModalTheme", modalTheme)) ..add(DiagnosticsProperty("MoonPopoverTheme", popoverTheme)) + ..add(DiagnosticsProperty("MoonProgressPinTheme", progressPinTheme)) ..add(DiagnosticsProperty("MoonRadioTheme", radioTheme)) ..add(DiagnosticsProperty("MoonSegmentedControlTheme", segmentedControlTheme)) ..add(DiagnosticsProperty("MoonSwitchTheme", switchTheme)) diff --git a/lib/src/widgets/progress/circular_progress.dart b/lib/src/widgets/progress/circular_progress.dart index 4ec6eed2..15b72877 100644 --- a/lib/src/widgets/progress/circular_progress.dart +++ b/lib/src/widgets/progress/circular_progress.dart @@ -85,7 +85,7 @@ class MoonCircularProgress extends StatelessWidget { color ?? context.moonTheme?.circularProgressTheme.colors.color ?? MoonColors.light.piccolo; final Color effectiveBackgroundColor = - backgroundColor ?? context.moonTheme?.circularProgressTheme.colors.backgroundColor ?? MoonColors.light.trunks; + backgroundColor ?? context.moonTheme?.circularProgressTheme.colors.backgroundColor ?? MoonColors.light.beerus; final MoonCircularProgressSizeProperties effectiveMoonCircularProgressSize = _getMoonCircularProgressSize(context, circularProgressSize); diff --git a/lib/src/widgets/progress/linear_progress.dart b/lib/src/widgets/progress/linear_progress.dart index 9b960944..d5887900 100644 --- a/lib/src/widgets/progress/linear_progress.dart +++ b/lib/src/widgets/progress/linear_progress.dart @@ -6,6 +6,8 @@ import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/theme/tokens/colors.dart'; import 'package:moon_design/src/theme/tokens/tokens.dart'; import 'package:moon_design/src/widgets/common/progress_indicators/linear_progress_indicator.dart'; +import 'package:moon_design/src/widgets/progress_pin/pin_style.dart'; +import 'package:moon_design/src/widgets/progress_pin/progress_pin.dart'; enum MoonLinearProgressSize { x6s, @@ -16,6 +18,9 @@ enum MoonLinearProgressSize { } class MoonLinearProgress extends StatelessWidget { + /// Show linear progress with thumb and pin. + final bool showPin; + /// Border radius value of the linear progress widget. final BorderRadiusGeometry? borderRadius; @@ -34,18 +39,23 @@ class MoonLinearProgress extends StatelessWidget { /// Size of the linear progress widget. final MoonLinearProgressSize? linearProgressSize; + /// Style for the linear progress pin. + final PinStyle? pinStyle; + /// The semantic label for the linear progress widget. final String? semanticLabel; /// MDS linear progress widget. const MoonLinearProgress({ super.key, + this.showPin = false, this.borderRadius, this.color, this.backgroundColor, this.height, required this.value, this.linearProgressSize, + this.pinStyle, this.semanticLabel, }); @@ -85,20 +95,33 @@ class MoonLinearProgress extends StatelessWidget { color ?? context.moonTheme?.linearProgressTheme.colors.color ?? MoonColors.light.piccolo; final Color effectiveBackgroundColor = - backgroundColor ?? context.moonTheme?.linearProgressTheme.colors.backgroundColor ?? MoonColors.light.trunks; + backgroundColor ?? context.moonTheme?.linearProgressTheme.colors.backgroundColor ?? MoonColors.light.beerus; final double effectiveHeight = height ?? effectiveProgressSize.progressHeight; return Semantics( label: semanticLabel, value: "${value * 100}%", - child: MoonLinearProgressIndicator( - borderRadius: effectiveBorderRadius, - value: value, - color: effectiveColor, - backgroundColor: effectiveBackgroundColor, - minHeight: effectiveHeight, - ), + child: showPin + ? MoonProgressPin( + progressValue: value, + progressLabel: '${(value * 100).round()}%', + pinStyle: pinStyle, + child: MoonLinearProgressIndicator( + value: value, + color: effectiveColor, + backgroundColor: effectiveBackgroundColor, + borderRadius: effectiveBorderRadius, + minHeight: effectiveHeight, + ), + ) + : MoonLinearProgressIndicator( + value: value, + color: effectiveColor, + backgroundColor: effectiveBackgroundColor, + borderRadius: effectiveBorderRadius, + minHeight: effectiveHeight, + ), ); } } diff --git a/lib/src/widgets/progress_pin/pin_style.dart b/lib/src/widgets/progress_pin/pin_style.dart new file mode 100644 index 00000000..11c3c8f8 --- /dev/null +++ b/lib/src/widgets/progress_pin/pin_style.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class PinStyle { + /// Whether to show shadow around progress pin. Defaults to true. + final bool? showShadow; + + /// The color of the pin. + final Color? pinColor; + + /// The color of the pin border. + final Color? pinBorderColor; + + /// The color of the thumb. + final Color? thumbColor; + + /// The color of the pin shadow. + final Color? shadowColor; + + /// The width of the thumb. + final double? thumbWidth; + + /// The width of the pin. + final double? pinWidth; + + /// The border width of the pin. + final double? pinBorderWidth; + + /// The vertical distance between pin and linear progress. + final double? pinDistance; + + /// The pin shadow elevation. + final double? shadowElevation; + + /// The text style of the pin. + final TextStyle? textStyle; + + const PinStyle({ + this.showShadow = true, + this.pinColor, + this.pinBorderColor, + this.thumbColor, + this.shadowColor, + this.thumbWidth, + this.pinWidth, + this.pinBorderWidth, + this.pinDistance, + this.shadowElevation, + this.textStyle, + }); +} diff --git a/lib/src/widgets/progress_pin/progress_pin.dart b/lib/src/widgets/progress_pin/progress_pin.dart new file mode 100644 index 00000000..96444378 --- /dev/null +++ b/lib/src/widgets/progress_pin/progress_pin.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/theme/tokens/colors.dart'; +import 'package:moon_design/src/theme/tokens/typography/typography.dart'; +import 'package:moon_design/src/widgets/progress_pin/pin_style.dart'; +import 'package:moon_design/src/widgets/progress_pin/progress_pin_painter.dart'; + +class MoonProgressPin extends StatelessWidget { + final double progressValue; + final PinStyle? pinStyle; + final String progressLabel; + final Widget child; + + const MoonProgressPin({ + super.key, + required this.progressValue, + required this.child, + this.pinStyle, + required this.progressLabel, + }); + + @override + Widget build(BuildContext context) { + final Color effectivePinColor = + pinStyle?.pinColor ?? context.moonTheme?.progressPinTheme.colors.pinColor ?? MoonColors.light.popo; + + final Color effectivePinBorderColor = + pinStyle?.pinBorderColor ?? context.moonTheme?.progressPinTheme.colors.pinBorderColor ?? MoonColors.light.goten; + + final Color effectiveKnobColor = + pinStyle?.thumbColor ?? context.moonTheme?.progressPinTheme.colors.thumbColor ?? MoonColors.light.goten; + + final Color effectiveShadowColor = + pinStyle?.shadowColor ?? context.moonTheme?.progressPinTheme.colors.shadowColor ?? MoonColors.light.popo; + + final Color effectiveTextColor = + pinStyle?.textStyle?.color ?? context.moonTheme?.progressPinTheme.colors.textColor ?? MoonColors.light.goten; + + final TextStyle effectiveTextStyle = pinStyle?.textStyle ?? + context.moonTheme?.progressPinTheme.properties.textStyle ?? + MoonTypography.typography.caption.text10.copyWith(letterSpacing: 0.5); + + final double effectiveKnobWidthMultiplier = + context.moonTheme?.progressPinTheme.properties.thumbWidthMultiplier ?? 1.5; + + final double effectivePinWidth = + pinStyle?.pinWidth ?? context.moonTheme?.progressPinTheme.properties.pinWidth ?? 36; + + final double effectivePinBorderWidth = + pinStyle?.pinBorderWidth ?? context.moonTheme?.progressPinTheme.properties.pinBorderWidth ?? 2; + + final double effectivePinDistance = + pinStyle?.pinDistance ?? context.moonTheme?.progressPinTheme.properties.pinDistance ?? 6; + + final double effectiveShadowElevation = + pinStyle?.shadowElevation ?? context.moonTheme?.progressPinTheme.properties.shadowElevation ?? 6; + + final TextDirection effectiveTextDirection = Directionality.of(context); + + return CustomPaint( + foregroundPainter: ProgressPinPainter( + showShadow: pinStyle?.showShadow ?? true, + pinColor: effectivePinColor, + thumbColor: effectiveKnobColor, + shadowColor: effectiveShadowColor, + pinBorderColor: effectivePinBorderColor, + pinBorderWidth: effectivePinBorderWidth, + pinDistance: effectivePinDistance, + pinWidth: effectivePinWidth, + thumbWidth: pinStyle?.thumbWidth, + thumbWidthMultiplier: effectiveKnobWidthMultiplier, + progressValue: progressValue, + shadowElevation: effectiveShadowElevation, + labelText: progressLabel, + textDirection: effectiveTextDirection, + textStyle: effectiveTextStyle.copyWith(color: effectiveTextColor), + ), + child: child, + ); + } +} diff --git a/lib/src/widgets/progress_pin/progress_pin_painter.dart b/lib/src/widgets/progress_pin/progress_pin_painter.dart new file mode 100644 index 00000000..5d558f32 --- /dev/null +++ b/lib/src/widgets/progress_pin/progress_pin_painter.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; + +class ProgressPinPainter extends CustomPainter { + final bool showShadow; + final Color pinColor; + final Color thumbColor; + final Color shadowColor; + final Color pinBorderColor; + final double pinBorderWidth; + final double pinDistance; + final double pinWidth; + final double? thumbWidth; + final double thumbWidthMultiplier; + final double progressValue; + final double shadowElevation; + final String labelText; + final TextStyle textStyle; + final TextDirection textDirection; + + const ProgressPinPainter({ + required this.showShadow, + required this.pinColor, + required this.thumbColor, + required this.shadowColor, + required this.pinBorderColor, + required this.pinBorderWidth, + required this.pinDistance, + required this.pinWidth, + this.thumbWidth, + required this.thumbWidthMultiplier, + required this.progressValue, + required this.shadowElevation, + required this.labelText, + required this.textStyle, + required this.textDirection, + }); + + @override + void paint(Canvas canvas, Size size) { + final double radius = pinWidth / 2; + final double arrowHeight = radius / 3; + final double arrowWidth = radius / 2; + final double offsetY = -(radius + arrowHeight + pinDistance); + final double thumbSizeWidth = thumbWidth ?? size.height / thumbWidthMultiplier; + + // Offset based on directionality + double offsetX = progressValue * size.width; + + switch (textDirection) { + case TextDirection.rtl: + offsetX = size.width - progressValue * size.width; + case TextDirection.ltr: + offsetX = progressValue * size.width; + } + + // Assign colors + final Paint outerCirclePaint = Paint()..color = pinBorderColor; + final Paint innerCirclePaint = Paint()..color = pinColor; + final Paint thumbCirclePaint = Paint()..color = thumbColor; + + // Create outer circle with triangle path + final Path path = Path() + ..addOval(Rect.fromCircle(center: Offset(offsetX, offsetY), radius: radius)) + ..addOval(Rect.fromCircle(center: Offset(offsetX, offsetY), radius: radius)) + ..moveTo(offsetX - arrowWidth / 2, offsetY + (radius * 0.9)) + ..lineTo(offsetX, offsetY + radius + arrowHeight) + ..lineTo(offsetX + arrowWidth / 2, offsetY + (radius * 0.9)) + ..close(); + + // Draw shadow around outer path + if (showShadow) canvas.drawShadow(path, shadowColor, shadowElevation, false); + + // Draw outer path + canvas.drawPath(path, outerCirclePaint); + + // Draw inner circle + canvas.drawCircle(Offset(offsetX, offsetY), radius - pinBorderWidth, innerCirclePaint); + + // Draw thumb + canvas.drawCircle(Offset(offsetX, size.height / 2), thumbSizeWidth, thumbCirclePaint); + + // Draw text + final TextPainter textPainter = TextPainter( + text: TextSpan(text: labelText, style: textStyle), + textDirection: TextDirection.ltr, + ); + + textPainter.layout(); + textPainter.paint(canvas, Offset(offsetX - textPainter.width / 2, offsetY - textPainter.height / 2)); + } + + @override + bool shouldRepaint(ProgressPinPainter oldPainter) { + return oldPainter.progressValue != progressValue || + oldPainter.pinWidth != pinWidth || + oldPainter.pinBorderWidth != pinBorderWidth || + oldPainter.pinColor != pinColor || + oldPainter.pinBorderColor != pinBorderColor || + oldPainter.pinDistance != pinDistance || + oldPainter.thumbColor != thumbColor || + oldPainter.thumbWidth != thumbWidth || + oldPainter.showShadow != showShadow || + oldPainter.shadowColor != shadowColor || + oldPainter.shadowElevation != shadowElevation || + oldPainter.textDirection != textDirection; + } +}