From 03d88323e758e612d6834743d24bea91226360d5 Mon Sep 17 00:00:00 2001 From: Harry Sild <46851868+Kypsis@users.noreply.github.com> Date: Thu, 23 Feb 2023 23:01:50 +0200 Subject: [PATCH] feat: [MDS-405] Create CircularProgress widget (#58) --- .../storybook/stories/circular_progress.dart | 65 +++++++++++++++ example/lib/src/storybook/stories/loader.dart | 2 +- example/lib/src/storybook/storybook.dart | 2 + lib/moon_design.dart | 3 + .../circular_progress_sizes.dart | 75 +++++++++++++++++ .../circular_progress_theme.dart | 80 +++++++++++++++++++ lib/src/theme/loader/loader_theme.dart | 10 +-- lib/src/theme/theme.dart | 12 +++ .../circular_progress/circular_progress.dart | 73 +++++++++++++++++ 9 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 example/lib/src/storybook/stories/circular_progress.dart create mode 100644 lib/src/theme/circular_progress/circular_progress_sizes.dart create mode 100644 lib/src/theme/circular_progress/circular_progress_theme.dart create mode 100644 lib/src/widgets/circular_progress/circular_progress.dart diff --git a/example/lib/src/storybook/stories/circular_progress.dart b/example/lib/src/storybook/stories/circular_progress.dart new file mode 100644 index 00000000..a22c48e8 --- /dev/null +++ b/example/lib/src/storybook/stories/circular_progress.dart @@ -0,0 +1,65 @@ +import 'package:example/src/storybook/common/options.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +class CircularProgressStory extends Story { + CircularProgressStory() + : super( + name: "CircularProgress", + builder: (context) { + final circularProgressSizesKnob = context.knobs.options( + label: "MoonCircularProgressSize", + description: "CircularProgress size variants.", + initial: MoonCircularProgressSize.md, + options: const [ + Option(label: "x2s", value: MoonCircularProgressSize.x2s), + Option(label: "xs", value: MoonCircularProgressSize.xs), + Option(label: "sm", value: MoonCircularProgressSize.sm), + Option(label: "md", value: MoonCircularProgressSize.md), + Option(label: "lg", value: MoonCircularProgressSize.lg), + ], + ); + + final circularProgressColorKnob = context.knobs.options( + label: "color", + description: "MoonColors variants for CircularProgress color.", + initial: 0, // piccolo + options: colorOptions, + ); + + final color = colorTable(context)[circularProgressColorKnob]; + + final circularProgressBackgroundColorKnob = context.knobs.options( + label: "backgroundColor", + description: "MoonColors variants for CircularProgress background.", + initial: 6, // trunks + options: colorOptions, + ); + + final backgroundColor = colorTable(context)[circularProgressBackgroundColorKnob]; + + final circularProgressValueKnob = context.knobs.slider( + label: "value", + description: "CircularProgress value.", + initial: 0.75, + ); + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + MoonCircularProgress( + value: circularProgressValueKnob, + color: color, + backgroundColor: backgroundColor, + circularProgressSize: circularProgressSizesKnob, + ), + const SizedBox(height: 64), + ], + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/stories/loader.dart b/example/lib/src/storybook/stories/loader.dart index e2d0d4d7..92a98322 100644 --- a/example/lib/src/storybook/stories/loader.dart +++ b/example/lib/src/storybook/stories/loader.dart @@ -9,7 +9,7 @@ class LoaderStory extends Story { name: "Loader", builder: (context) { final loaderSizesKnob = context.knobs.options( - label: "loaderSize", + label: "MoonLoaderSize", description: "Loader size variants.", initial: MoonLoaderSize.md, options: const [ diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index a9baee95..22d754bf 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -2,6 +2,7 @@ import 'package:example/src/storybook/common/widgets/version.dart'; import 'package:example/src/storybook/stories/avatar.dart'; import 'package:example/src/storybook/stories/button.dart'; import 'package:example/src/storybook/stories/chip.dart'; +import 'package:example/src/storybook/stories/circular_progress.dart'; import 'package:example/src/storybook/stories/icons.dart'; import 'package:example/src/storybook/stories/loader.dart'; import 'package:example/src/storybook/stories/tag.dart'; @@ -64,6 +65,7 @@ class StorybookPage extends StatelessWidget { AvatarStory(), ButtonStory(), ChipStory(), + CircularProgressStory(), IconsStory(), LoaderStory(), TagStory(), diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 89cc9661..d83ea41a 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -7,6 +7,8 @@ export 'package:moon_design/src/theme/button/button_sizes.dart'; export 'package:moon_design/src/theme/button/button_theme.dart'; export 'package:moon_design/src/theme/chip/chip_sizes.dart'; export 'package:moon_design/src/theme/chip/chip_theme.dart'; +export 'package:moon_design/src/theme/circular_progress/circular_progress_sizes.dart'; +export 'package:moon_design/src/theme/circular_progress/circular_progress_theme.dart'; export 'package:moon_design/src/theme/colors.dart'; export 'package:moon_design/src/theme/effects/controls_effects.dart'; export 'package:moon_design/src/theme/effects/effects.dart'; @@ -36,6 +38,7 @@ export 'package:moon_design/src/widgets/buttons/secondary_button.dart'; export 'package:moon_design/src/widgets/buttons/tertiary_button.dart'; export 'package:moon_design/src/widgets/chips/chip.dart'; export 'package:moon_design/src/widgets/chips/ghost_chip.dart'; +export 'package:moon_design/src/widgets/circular_progress/circular_progress.dart'; export 'package:moon_design/src/widgets/effects/focus_effect.dart'; export 'package:moon_design/src/widgets/effects/pulse_effect.dart'; export 'package:moon_design/src/widgets/icons.dart'; diff --git a/lib/src/theme/circular_progress/circular_progress_sizes.dart b/lib/src/theme/circular_progress/circular_progress_sizes.dart new file mode 100644 index 00000000..0875ea0d --- /dev/null +++ b/lib/src/theme/circular_progress/circular_progress_sizes.dart @@ -0,0 +1,75 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/sizes.dart'; + +@immutable +class MoonCircularProgressSizes extends ThemeExtension with DiagnosticableTreeMixin { + static final x2s = MoonCircularProgressSizes( + circularProgressSizeValue: MoonSizes.sizes.x2s, + circularProgressStrokeWidth: MoonSizes.sizes.x6s, + ); + + static final xs = MoonCircularProgressSizes( + circularProgressSizeValue: MoonSizes.sizes.xs, + circularProgressStrokeWidth: MoonSizes.sizes.x6s, + ); + + static final sm = MoonCircularProgressSizes( + circularProgressSizeValue: MoonSizes.sizes.sm, + circularProgressStrokeWidth: MoonSizes.sizes.x6s, + ); + + static final md = MoonCircularProgressSizes( + circularProgressSizeValue: MoonSizes.sizes.md, + circularProgressStrokeWidth: MoonSizes.sizes.x5s, + ); + + static final lg = MoonCircularProgressSizes( + circularProgressSizeValue: MoonSizes.sizes.lg, + circularProgressStrokeWidth: MoonSizes.sizes.x5s, + ); + + /// Circular progress size value. + final double circularProgressSizeValue; + + /// Circular progress stroke width. + final double circularProgressStrokeWidth; + + const MoonCircularProgressSizes({ + required this.circularProgressSizeValue, + required this.circularProgressStrokeWidth, + }); + + @override + MoonCircularProgressSizes copyWith({ + double? circularProgressSizeValue, + double? circularProgressStrokeWidth, + }) { + return MoonCircularProgressSizes( + circularProgressSizeValue: circularProgressSizeValue ?? this.circularProgressSizeValue, + circularProgressStrokeWidth: circularProgressStrokeWidth ?? this.circularProgressStrokeWidth, + ); + } + + @override + MoonCircularProgressSizes lerp(ThemeExtension? other, double t) { + if (other is! MoonCircularProgressSizes) return this; + + return MoonCircularProgressSizes( + circularProgressSizeValue: lerpDouble(circularProgressSizeValue, other.circularProgressSizeValue, t)!, + circularProgressStrokeWidth: lerpDouble(circularProgressStrokeWidth, other.circularProgressStrokeWidth, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonCircularProgressSizes")) + ..add(DoubleProperty("circularProgressSizeValue", circularProgressSizeValue)) + ..add(DoubleProperty("circularProgressStrokeWidth", circularProgressStrokeWidth)); + } +} diff --git a/lib/src/theme/circular_progress/circular_progress_theme.dart b/lib/src/theme/circular_progress/circular_progress_theme.dart new file mode 100644 index 00000000..3f7a05cb --- /dev/null +++ b/lib/src/theme/circular_progress/circular_progress_theme.dart @@ -0,0 +1,80 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/circular_progress/circular_progress_sizes.dart'; + +@immutable +class MoonCircularProgressTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final sizes = MoonCircularProgressTheme( + x2s: MoonCircularProgressSizes.x2s, + xs: MoonCircularProgressSizes.xs, + sm: MoonCircularProgressSizes.sm, + md: MoonCircularProgressSizes.md, + lg: MoonCircularProgressSizes.lg, + ); + + /// (2x) Extra small circular progress properties. + final MoonCircularProgressSizes x2s; + + /// Extra small circular progress properties. + final MoonCircularProgressSizes xs; + + /// Small circular progress properties. + final MoonCircularProgressSizes sm; + + /// Medium circular progress properties. + final MoonCircularProgressSizes md; + + /// Large circular progress properties. + final MoonCircularProgressSizes lg; + + const MoonCircularProgressTheme({ + required this.x2s, + required this.xs, + required this.sm, + required this.md, + required this.lg, + }); + + @override + MoonCircularProgressTheme copyWith({ + MoonCircularProgressSizes? x2s, + MoonCircularProgressSizes? xs, + MoonCircularProgressSizes? sm, + MoonCircularProgressSizes? md, + MoonCircularProgressSizes? lg, + }) { + return MoonCircularProgressTheme( + x2s: x2s ?? this.x2s, + xs: xs ?? this.xs, + sm: sm ?? this.sm, + md: md ?? this.md, + lg: lg ?? this.lg, + ); + } + + @override + MoonCircularProgressTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonCircularProgressTheme) return this; + + return MoonCircularProgressTheme( + x2s: x2s.lerp(other.x2s, t), + xs: xs.lerp(other.xs, t), + sm: sm.lerp(other.sm, t), + md: md.lerp(other.md, t), + lg: lg.lerp(other.lg, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonCircularProgressTheme")) + ..add(DiagnosticsProperty("x2s", x2s)) + ..add(DiagnosticsProperty("xs", xs)) + ..add(DiagnosticsProperty("sm", sm)) + ..add(DiagnosticsProperty("md", md)) + ..add(DiagnosticsProperty("lg", lg)); + } +} diff --git a/lib/src/theme/loader/loader_theme.dart b/lib/src/theme/loader/loader_theme.dart index d2eb8363..5540316e 100644 --- a/lib/src/theme/loader/loader_theme.dart +++ b/lib/src/theme/loader/loader_theme.dart @@ -13,19 +13,19 @@ class MoonLoaderTheme extends ThemeExtension with Diagnosticabl lg: MoonLoaderSizes.lg, ); - /// (2x) Extra small avatar properties. + /// (2x) Extra small loader properties. final MoonLoaderSizes x2s; - /// Extra small avatar properties. + /// Extra small loader properties. final MoonLoaderSizes xs; - /// Small avatar properties. + /// Small loader properties. final MoonLoaderSizes sm; - /// Medium avatar properties. + /// Medium loader properties. final MoonLoaderSizes md; - /// Large avatar properties. + /// Large loader properties. final MoonLoaderSizes lg; const MoonLoaderTheme({ diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index c2d44cf1..2632edf4 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -5,6 +5,7 @@ import 'package:moon_design/src/theme/avatar/avatar_theme.dart'; import 'package:moon_design/src/theme/borders.dart'; import 'package:moon_design/src/theme/button/button_theme.dart'; import 'package:moon_design/src/theme/chip/chip_theme.dart'; +import 'package:moon_design/src/theme/circular_progress/circular_progress_theme.dart'; import 'package:moon_design/src/theme/colors.dart'; import 'package:moon_design/src/theme/effects/effects.dart'; import 'package:moon_design/src/theme/loader/loader_theme.dart'; @@ -22,6 +23,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { borders: MoonBorders.borders, buttonTheme: MoonButtonTheme.sizes, chipTheme: MoonChipTheme.sizes, + circularProgressTheme: MoonCircularProgressTheme.sizes, colors: MoonColors.light, effects: MoonEffects.light, loaderTheme: MoonLoaderTheme.sizes, @@ -38,6 +40,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { borders: MoonBorders.borders, buttonTheme: MoonButtonTheme.sizes, chipTheme: MoonChipTheme.sizes, + circularProgressTheme: MoonCircularProgressTheme.sizes, colors: MoonColors.dark, effects: MoonEffects.dark, loaderTheme: MoonLoaderTheme.sizes, @@ -61,6 +64,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System chip theming. final MoonChipTheme chipTheme; + /// Moon Design System circular progress theming. + final MoonCircularProgressTheme circularProgressTheme; + /// Moon Design System colors. final MoonColors colors; @@ -93,6 +99,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.borders, required this.buttonTheme, required this.chipTheme, + required this.circularProgressTheme, required this.colors, required this.effects, required this.loaderTheme, @@ -110,6 +117,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonBorders? borders, MoonButtonTheme? buttonTheme, MoonChipTheme? chipTheme, + MoonCircularProgressTheme? circularProgressTheme, MoonColors? colors, MoonEffects? effects, MoonLoaderTheme? loaderTheme, @@ -125,6 +133,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { borders: borders ?? this.borders, buttonTheme: buttonTheme ?? this.buttonTheme, chipTheme: chipTheme ?? this.chipTheme, + circularProgressTheme: circularProgressTheme ?? this.circularProgressTheme, colors: colors ?? this.colors, effects: effects ?? this.effects, loaderTheme: loaderTheme ?? this.loaderTheme, @@ -146,6 +155,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { borders: borders.lerp(other.borders, t), buttonTheme: buttonTheme.lerp(other.buttonTheme, t), chipTheme: chipTheme.lerp(other.chipTheme, t), + circularProgressTheme: circularProgressTheme.lerp(other.circularProgressTheme, t), colors: colors.lerp(other.colors, t), effects: effects.lerp(other.effects, t), loaderTheme: loaderTheme.lerp(other.loaderTheme, t), @@ -167,6 +177,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonBorders", borders)) ..add(DiagnosticsProperty("MoonButtonTheme", buttonTheme)) ..add(DiagnosticsProperty("MoonChipTheme", chipTheme)) + ..add(DiagnosticsProperty("MoonCircularProgressTheme", circularProgressTheme)) ..add(DiagnosticsProperty("MoonColors", colors)) ..add(DiagnosticsProperty("MoonEffects", effects)) ..add(DiagnosticsProperty("MoonLoaderTheme", loaderTheme)) @@ -185,6 +196,7 @@ extension MoonThemeX on BuildContext { MoonBorders? get moonBorders => moonTheme?.borders; MoonButtonTheme? get moonButtonTheme => moonTheme?.buttonTheme; MoonChipTheme? get moonChipTheme => moonTheme?.chipTheme; + MoonCircularProgressTheme? get moonCircularProgressTheme => moonTheme?.circularProgressTheme; MoonColors? get moonColors => moonTheme?.colors; MoonEffects? get moonEffects => moonTheme?.effects; MoonLoaderTheme? get moonLoaderTheme => moonTheme?.loaderTheme; diff --git a/lib/src/widgets/circular_progress/circular_progress.dart b/lib/src/widgets/circular_progress/circular_progress.dart new file mode 100644 index 00000000..10ed02d0 --- /dev/null +++ b/lib/src/widgets/circular_progress/circular_progress.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/circular_progress/circular_progress_sizes.dart'; +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/theme.dart'; + +enum MoonCircularProgressSize { + x2s, + xs, + sm, + md, + lg, +} + +class MoonCircularProgress extends StatelessWidget { + final MoonCircularProgressSize? circularProgressSize; + final double value; + final double? sizeValue; + final double? strokeWidth; + final Color? color; + final Color? backgroundColor; + + const MoonCircularProgress({ + super.key, + this.circularProgressSize, + required this.value, + this.sizeValue, + this.strokeWidth, + this.color, + this.backgroundColor, + }); + + MoonCircularProgressSizes _getMoonCircularProgressSize( + BuildContext context, + MoonCircularProgressSize? moonCircularProgressSize, + ) { + switch (moonCircularProgressSize) { + case MoonCircularProgressSize.x2s: + return context.moonTheme?.circularProgressTheme.x2s ?? MoonCircularProgressSizes.x2s; + case MoonCircularProgressSize.xs: + return context.moonTheme?.circularProgressTheme.xs ?? MoonCircularProgressSizes.xs; + case MoonCircularProgressSize.sm: + return context.moonTheme?.circularProgressTheme.sm ?? MoonCircularProgressSizes.sm; + case MoonCircularProgressSize.md: + return context.moonTheme?.circularProgressTheme.md ?? MoonCircularProgressSizes.md; + case MoonCircularProgressSize.lg: + return context.moonTheme?.circularProgressTheme.lg ?? MoonCircularProgressSizes.lg; + + default: + return context.moonTheme?.circularProgressTheme.md ?? MoonCircularProgressSizes.md; + } + } + + @override + Widget build(BuildContext context) { + final MoonCircularProgressSizes effectiveMoonCircularProgressSize = + _getMoonCircularProgressSize(context, circularProgressSize); + final double effectiveSize = sizeValue ?? effectiveMoonCircularProgressSize.circularProgressSizeValue; + final double effectiveStrokeWidth = strokeWidth ?? effectiveMoonCircularProgressSize.circularProgressStrokeWidth; + final Color effectiveBackgroundColor = backgroundColor ?? context.moonColors?.trunks ?? MoonColors.light.trunks; + + return SizedBox( + height: effectiveSize, + width: effectiveSize, + child: CircularProgressIndicator( + value: value, + strokeWidth: effectiveStrokeWidth, + color: color, + backgroundColor: effectiveBackgroundColor, + ), + ); + } +}