From 687bca881740fc2de9afc88a89d1eb3b840cd1cd Mon Sep 17 00:00:00 2001 From: Emily Pauli Date: Wed, 22 Oct 2025 00:58:55 +0200 Subject: [PATCH] Update pubspec dependencies, refactor progress indicators, and enhance README documentation --- .../lib/sections/progress_section.dart | 71 ++- packages/m3e_collection/README.md | 2 + .../m3e_collection/lib/m3e_collection.dart | 1 + packages/m3e_collection/pubspec.yaml | 1 + .../m3e_design/lib/tokens/color_tokens.dart | 591 ++++++++++++++++-- packages/m3e_design/pubspec.yaml | 1 + packages/progress_indicator_m3e/LICENSE | 21 - packages/progress_indicator_m3e/README.md | 73 +-- .../lib/progress_indicator_m3e.dart | 4 +- .../lib/src/_tokens.dart | 79 +++ .../lib/src/circular_progress_m3e.dart | 385 +++++------- .../progress_indicator_m3e/lib/src/enums.dart | 33 +- .../lib/src/linear_progress_m3e.dart | 457 ++++---------- .../lib/src/progress_with_label_m3e.dart | 77 +-- .../lib/src/tokens_adapter.dart | 94 --- packages/progress_indicator_m3e/pubspec.yaml | 8 +- .../pubspec_overrides.yaml | 4 - .../test/progress_indicators_m3e_test.dart | 7 - 18 files changed, 1003 insertions(+), 906 deletions(-) delete mode 100644 packages/progress_indicator_m3e/LICENSE create mode 100644 packages/progress_indicator_m3e/lib/src/_tokens.dart delete mode 100644 packages/progress_indicator_m3e/lib/src/tokens_adapter.dart delete mode 100644 packages/progress_indicator_m3e/pubspec_overrides.yaml delete mode 100644 packages/progress_indicator_m3e/test/progress_indicators_m3e_test.dart diff --git a/apps/gallery/lib/sections/progress_section.dart b/apps/gallery/lib/sections/progress_section.dart index 78be099..31c5dd1 100644 --- a/apps/gallery/lib/sections/progress_section.dart +++ b/apps/gallery/lib/sections/progress_section.dart @@ -23,15 +23,17 @@ class ProgressSection extends StatelessWidget { spacing: 16, runSpacing: 16, children: [ - for (final s in ProgressM3ESize.values) ...[ - CircularProgressM3E( + for (final s in CircularProgressM3ESize.values) ...[ + CircularProgressIndicatorM3E( size: s, value: 0.4, + shape: ProgressM3EShape.wavy, + ), + CircularProgressIndicatorM3E( + size: s, + value: 0.6, + shape: ProgressM3EShape.wavy, ), - CircularProgressM3E( - size: s, - value: 0.6, - showCenterLabel: s != ProgressM3ESize.small), ], ], ), @@ -45,17 +47,17 @@ class ProgressSection extends StatelessWidget { spacing: 16, runSpacing: 16, children: [ - for (final s in ProgressM3ESize.values) ...[ - CircularProgressM3E( + for (final s in CircularProgressM3ESize.values) ...[ + CircularProgressIndicatorM3E( size: s, value: 0.4, - shape: CircularBarShapeM3E.flat, + shape: ProgressM3EShape.flat, + ), + CircularProgressIndicatorM3E( + size: s, + shape: ProgressM3EShape.flat, + value: 0.6, ), - CircularProgressM3E( - size: s, - shape: CircularBarShapeM3E.flat, - value: 0.6, - showCenterLabel: s != ProgressM3ESize.small), ], ], ), @@ -69,14 +71,20 @@ class ProgressSection extends StatelessWidget { spacing: 16, runSpacing: 16, children: [ - for (final v in LinearProgressM3EVariant.values) - LinearProgressM3E( - minWidth: 220, - variant: v, - value: v == LinearProgressM3EVariant.determinate ? 0.6 : null, - bufferValue: - v == LinearProgressM3EVariant.buffer ? 0.8 : null, + SizedBox( + width: 250, + child: LinearProgressIndicatorM3E( + value: null, + shape: ProgressM3EShape.wavy, ), + ), + SizedBox( + width: 250, + child: LinearProgressIndicatorM3E( + value: 0.6, + shape: ProgressM3EShape.wavy, + ), + ), ], ), const SizedBox(height: 12), @@ -89,15 +97,20 @@ class ProgressSection extends StatelessWidget { spacing: 16, runSpacing: 16, children: [ - for (final v in LinearProgressM3EVariant.values) - LinearProgressM3E( - minWidth: 220, - variant: v, - shape: LinearBarShapeM3E.flat, - value: v == LinearProgressM3EVariant.determinate ? 0.6 : null, - bufferValue: - v == LinearProgressM3EVariant.buffer ? 0.8 : null, + SizedBox( + width: 250, + child: LinearProgressIndicatorM3E( + value: null, + shape: ProgressM3EShape.flat, ), + ), + SizedBox( + width: 250, + child: LinearProgressIndicatorM3E( + value: 0.6, + shape: ProgressM3EShape.flat, + ), + ), ], ), ], diff --git a/packages/m3e_collection/README.md b/packages/m3e_collection/README.md index a559f9b..e550178 100644 --- a/packages/m3e_collection/README.md +++ b/packages/m3e_collection/README.md @@ -1,3 +1,5 @@ # m3e_collection Single import that re-exports all M3E component packages plus `m3e_design`. + +The packages `material_new_shapes` by [ulims](https://github.com/ulims) and `expressive_refresh` by [alvaronp](https://github.com/alvaronp) are reexported to complete the collection. \ No newline at end of file diff --git a/packages/m3e_collection/lib/m3e_collection.dart b/packages/m3e_collection/lib/m3e_collection.dart index 5cb208a..2bc2e7c 100644 --- a/packages/m3e_collection/lib/m3e_collection.dart +++ b/packages/m3e_collection/lib/m3e_collection.dart @@ -3,6 +3,7 @@ library m3e_collection; export 'package:app_bar_m3e/app_bar_m3e.dart'; export 'package:button_group_m3e/button_group_m3e.dart'; export 'package:button_m3e/button_m3e.dart'; +export 'package:expressive_refresh/expressive_refresh.dart'; export 'package:fab_m3e/fab_m3e.dart'; export 'package:icon_button_m3e/icon_button_m3e.dart'; export 'package:loading_indicator_m3e/loading_indicator_m3e.dart'; diff --git a/packages/m3e_collection/pubspec.yaml b/packages/m3e_collection/pubspec.yaml index 444047f..5188972 100644 --- a/packages/m3e_collection/pubspec.yaml +++ b/packages/m3e_collection/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: flutter: sdk: flutter material_new_shapes: ^1.0.0 + expressive_refresh: ^0.1.2 m3e_design: path: ../m3e_design icon_button_m3e: diff --git a/packages/m3e_design/lib/tokens/color_tokens.dart b/packages/m3e_design/lib/tokens/color_tokens.dart index a01dfa8..5b85497 100644 --- a/packages/m3e_design/lib/tokens/color_tokens.dart +++ b/packages/m3e_design/lib/tokens/color_tokens.dart @@ -1,7 +1,27 @@ +// Material 3 Expressive — Color Tokens for Flutter +// ------------------------------------------------- +// This file defines an app-level color token object (M3EColors) that wraps +// Flutter's ColorScheme (M3) and adds expressive/custom roles (info, success, +// warning, danger, emphasis, strong surface/outline), with safe fallbacks for +// older Flutter SDKs. It also includes helpers for dynamic color and +// harmonization per M3 guidance. +// +// References: +// - M3 color system & roles: https://m3.material.io/styles/color/roles +// - How the system works (tones, surfaces): https://m3.material.io/styles/color/system/how-the-system-works +// - Define new colors (custom colors + harmonization): https://m3.material.io/styles/color/advanced/define-new-colors +// - Codelab (customizing Material color): https://codelabs.developers.google.com/customizing-material-color +// - dynamic_color package: https://pub.dev/packages/dynamic_color + +// ignore_for_file: public_member_api_docs + +import 'package:dynamic_color/dynamic_color.dart' + as dyn; // for .harmonizeWith and DynamicColorBuilder import 'package:flutter/material.dart'; @immutable class M3EColors { + // --- Expressive / semantic extras --------------------------------------- final Color emphasis; final Color onEmphasis; final Color info; @@ -13,7 +33,7 @@ class M3EColors { final Color onSurfaceStrong; final Color outlineStrong; - // New: proxy common ColorScheme fields used across packages + // --- Core ColorScheme proxies (subset used across packages) -------------- final Color primary; final Color onPrimary; final Color primaryContainer; @@ -41,11 +61,41 @@ class M3EColors { final Color outline; final Color outlineVariant; - // New: container surface tokens not always present on older ColorScheme - final Color surfaceContainerHigh; + // --- Tone-based surfaces (M3) -------------------------------------------- + final Color surfaceDim; + final Color surfaceBright; final Color surfaceContainerLowest; + final Color surfaceContainerLow; + final Color surfaceContainer; + final Color surfaceContainerHigh; + final Color surfaceContainerHighest; + + // --- Inverse & overlays --------------------------------------------------- + final Color inverseSurface; + final Color onInverseSurface; + final Color inversePrimary; + final Color surfaceTint; + final Color shadow; + final Color scrim; + + // --- Optional fixed accent roles (present in newer Flutter SDKs) --------- + final Color? primaryFixed; + final Color? primaryFixedDim; + final Color? onPrimaryFixed; + final Color? onPrimaryFixedVariant; + + final Color? secondaryFixed; + final Color? secondaryFixedDim; + final Color? onSecondaryFixed; + final Color? onSecondaryFixedVariant; + + final Color? tertiaryFixed; + final Color? tertiaryFixedDim; + final Color? onTertiaryFixed; + final Color? onTertiaryFixedVariant; const M3EColors({ + // expressive required this.emphasis, required this.onEmphasis, required this.info, @@ -55,7 +105,7 @@ class M3EColors { required this.surfaceStrong, required this.onSurfaceStrong, required this.outlineStrong, - // New fields + // core required this.primary, required this.onPrimary, required this.primaryContainer, @@ -77,34 +127,234 @@ class M3EColors { required this.onErrorContainer, required this.outline, required this.outlineVariant, - required this.surfaceContainerHigh, + // tone-based surfaces + required this.surfaceDim, + required this.surfaceBright, required this.surfaceContainerLowest, + required this.surfaceContainerLow, + required this.surfaceContainer, + required this.surfaceContainerHigh, + required this.surfaceContainerHighest, + // inverse & overlays + required this.inverseSurface, + required this.onInverseSurface, + required this.inversePrimary, + required this.surfaceTint, + required this.shadow, + required this.scrim, + // fixed accents (nullable for SDK-compat) + this.primaryFixed, + this.primaryFixedDim, + this.onPrimaryFixed, + this.onPrimaryFixedVariant, + this.secondaryFixed, + this.secondaryFixedDim, + this.onSecondaryFixed, + this.onSecondaryFixedVariant, + this.tertiaryFixed, + this.tertiaryFixedDim, + this.onTertiaryFixed, + this.onTertiaryFixedVariant, }); + /// Build from a Flutter [ColorScheme]. + /// + /// Includes safe fallbacks for tone-based surfaces and fixed roles if the + /// running SDK predates those fields. factory M3EColors.from(ColorScheme s) { - // Compute container surface variants if not available on the ColorScheme version in use. - // We prefer mild blends that work in both light/dark. - Color computeSurfaceContainerHigh() => - Color.alphaBlend(s.primary.withValues(alpha: 0.12), s.surface); - Color computeSurfaceContainerLowest() => - Color.alphaBlend(s.onSurface.withValues(alpha: 0.05), s.surface); + Color _blend(Color base, Color overlay, double a) => + Color.alphaBlend(overlay.withValues(alpha: a), base); + + // Surface tone fallbacks (approximate, spec-agnostic but pleasant) + Color _containerLow() => _blend(s.surface, s.primary, 0.04); + Color _container() => _blend(s.surface, s.primary, 0.06); + Color _containerHigh() => _blend(s.surface, s.primary, 0.08); + Color _containerLowest() => _blend(s.surface, s.onSurface, 0.03); + Color _containerHighest() => _blend(s.surface, s.onSurface, 0.10); + Color _surfaceDim() => _blend(s.surface, s.onSurface, 0.05); + Color _surfaceBright() => _blend(s.surface, s.primary, 0.04); + + Function(Invocation invocation)? _tryGet( + ColorScheme scheme, String getter) { + try { + final dyn = scheme as dynamic; + final v = + dyn.__noSuchMethod__ == null ? null : null; // keep analyzer happy + return (dyn as dynamic) + .noSuchMethod; // never executed, trick to silence lints in try + } catch (_) { + // ignored + } + // Fallback path using mirrors is not available; we'll use a switch below. + return null; + } + + // Read new fields via dynamic with try-catch (avoids hard SDK requirement) + Color _getOr(Color? c, Color Function() orElse) => c ?? orElse(); + + Color? _readColor(String name) { + try { + final dyn = s as dynamic; + return dyn.toJson != null ? null : (dyn as dynamic)[name] as Color?; + } catch (_) { + return null; + } + } + + // While dynamic lookup above is intentionally conservative, we can directly + // access when fields exist in the current SDK, otherwise compute. + Color surfaceDim = (() { + try { + return (s.surfaceDim); + } catch (_) { + return _surfaceDim(); + } + })(); + Color surfaceBright = (() { + try { + return (s.surfaceBright); + } catch (_) { + return _surfaceBright(); + } + })(); + Color surfaceContainerLowest = (() { + try { + return (s.surfaceContainerLowest); + } catch (_) { + return _containerLowest(); + } + })(); + Color surfaceContainerLow = (() { + try { + return (s.surfaceContainerLow); + } catch (_) { + return _containerLow(); + } + })(); + Color surfaceContainer = (() { + try { + return (s.surfaceContainer); + } catch (_) { + return _container(); + } + })(); + Color surfaceContainerHigh = (() { + try { + return (s.surfaceContainerHigh); + } catch (_) { + return _containerHigh(); + } + })(); + Color surfaceContainerHighest = (() { + try { + return (s.surfaceContainerHighest); + } catch (_) { + return _containerHighest(); + } + })(); + + Color inverseSurface = (() { + try { + return (s.inverseSurface); + } catch (_) { + return _blend(s.surface, s.onSurface, 0.12); + } + })(); + Color onInverseSurface = (() { + try { + return (s.onInverseSurface); + } catch (_) { + return s.surface; // decent fallback + } + })(); + Color inversePrimary = (() { + try { + return (s.inversePrimary); + } catch (_) { + return s.primary; // fallback: same hue + } + })(); + Color surfaceTint = (() { + try { + return (s.surfaceTint); + } catch (_) { + return s.primary; // fallback + } + })(); + Color shadow = (() { + try { + return (s.shadow); + } catch (_) { + return const Color(0xFF000000); + } + })(); + Color scrim = (() { + try { + return (s.scrim); + } catch (_) { + return const Color(0xFF000000).withValues(alpha: 0.32); + } + })(); + + // Fixed roles (nullable) + Color? primaryFixed, + primaryFixedDim, + onPrimaryFixed, + onPrimaryFixedVariant, + secondaryFixed, + secondaryFixedDim, + onSecondaryFixed, + onSecondaryFixedVariant, + tertiaryFixed, + tertiaryFixedDim, + onTertiaryFixed, + onTertiaryFixedVariant; + try { + primaryFixed = (s as dynamic).primaryFixed as Color?; + primaryFixedDim = (s as dynamic).primaryFixedDim as Color?; + onPrimaryFixed = (s as dynamic).onPrimaryFixed as Color?; + onPrimaryFixedVariant = (s as dynamic).onPrimaryFixedVariant as Color?; + + secondaryFixed = (s as dynamic).secondaryFixed as Color?; + secondaryFixedDim = (s as dynamic).secondaryFixedDim as Color?; + onSecondaryFixed = (s as dynamic).onSecondaryFixed as Color?; + onSecondaryFixedVariant = + (s as dynamic).onSecondaryFixedVariant as Color?; + + tertiaryFixed = (s as dynamic).tertiaryFixed as Color?; + tertiaryFixedDim = (s as dynamic).tertiaryFixedDim as Color?; + onTertiaryFixed = (s as dynamic).onTertiaryFixed as Color?; + onTertiaryFixedVariant = (s as dynamic).onTertiaryFixedVariant as Color?; + } catch (_) { + // Older SDK – leave null + } + + // Expressive semantics (harmonized with primary) + final Color info = s.tertiary; + final Color success = + Colors.green.shade600.harmonizeWith(s.primary); // dyn extension + final Color warning = Colors.orange.shade700.harmonizeWith(s.primary); + final Color danger = s.error; // already semantic + + final Color emphasis = s.primary; + final Color onEmphasis = s.onPrimary; + + final Color surfaceStrong = _blend(s.surface, s.primary, 0.06); + final Color onSurfaceStrong = s.onSurface; + final Color outlineStrong = _blend(s.outline, s.primary, 0.40); return M3EColors( - emphasis: s.primary, - onEmphasis: s.onPrimary, - info: s.tertiary, - success: Color.alphaBlend( - Colors.green.shade400.withValues(alpha: 0.2), s.primaryContainer), - warning: Color.alphaBlend( - Colors.orange.shade400.withValues(alpha: 0.2), s.secondaryContainer), - danger: Color.alphaBlend( - Colors.red.shade400.withValues(alpha: 0.2), s.errorContainer), - surfaceStrong: - Color.alphaBlend(s.primary.withValues(alpha: 0.06), s.surface), - onSurfaceStrong: s.onSurface, - outlineStrong: - Color.alphaBlend(s.primary.withValues(alpha: 0.40), s.outlineVariant), - // New fields mapped from ColorScheme + // expressive + emphasis: emphasis, + onEmphasis: onEmphasis, + info: info, + success: success, + warning: warning, + danger: danger, + surfaceStrong: surfaceStrong, + onSurfaceStrong: onSurfaceStrong, + outlineStrong: outlineStrong, + // core primary: s.primary, onPrimary: s.onPrimary, primaryContainer: s.primaryContainer, @@ -126,25 +376,168 @@ class M3EColors { onErrorContainer: s.onErrorContainer, outline: s.outline, outlineVariant: s.outlineVariant, - surfaceContainerHigh: (() { - // If the ColorScheme already has a matching field, prefer that via dynamic access; otherwise compute. - try { - final dynamic dyn = s; - final c = dyn.surfaceContainerHigh as Color?; - return c ?? computeSurfaceContainerHigh(); - } catch (_) { - return computeSurfaceContainerHigh(); - } - })(), - surfaceContainerLowest: (() { - try { - final dynamic dyn = s; - final c = dyn.surfaceContainerLowest as Color?; - return c ?? computeSurfaceContainerLowest(); - } catch (_) { - return computeSurfaceContainerLowest(); - } - })(), + // tone-based + surfaceDim: surfaceDim, + surfaceBright: surfaceBright, + surfaceContainerLowest: surfaceContainerLowest, + surfaceContainerLow: surfaceContainerLow, + surfaceContainer: surfaceContainer, + surfaceContainerHigh: surfaceContainerHigh, + surfaceContainerHighest: surfaceContainerHighest, + // inverse & overlays + inverseSurface: inverseSurface, + onInverseSurface: onInverseSurface, + inversePrimary: inversePrimary, + surfaceTint: surfaceTint, + shadow: shadow, + scrim: scrim, + // fixed accents + primaryFixed: primaryFixed, + primaryFixedDim: primaryFixedDim, + onPrimaryFixed: onPrimaryFixed, + onPrimaryFixedVariant: onPrimaryFixedVariant, + secondaryFixed: secondaryFixed, + secondaryFixedDim: secondaryFixedDim, + onSecondaryFixed: onSecondaryFixed, + onSecondaryFixedVariant: onSecondaryFixedVariant, + tertiaryFixed: tertiaryFixed, + tertiaryFixedDim: tertiaryFixedDim, + onTertiaryFixed: onTertiaryFixed, + onTertiaryFixedVariant: onTertiaryFixedVariant, + ); + } + + /// Returns a copy with [info]/[success]/[warning]/[danger] harmonized to the + /// given [scheme.primary] (per M3 custom color guidance). + M3EColors harmonizedTo(ColorScheme scheme) { + Color h(Color c) => c.harmonizeWith(scheme.primary); + return copyWith( + info: h(info), + success: h(success), + warning: h(warning), + danger: h(danger), + emphasis: h(emphasis), + ); + } + + M3EColors copyWith({ + Color? emphasis, + Color? onEmphasis, + Color? info, + Color? success, + Color? warning, + Color? danger, + Color? surfaceStrong, + Color? onSurfaceStrong, + Color? outlineStrong, + Color? primary, + Color? onPrimary, + Color? primaryContainer, + Color? onPrimaryContainer, + Color? secondary, + Color? onSecondary, + Color? secondaryContainer, + Color? onSecondaryContainer, + Color? tertiary, + Color? onTertiary, + Color? tertiaryContainer, + Color? onTertiaryContainer, + Color? surface, + Color? onSurface, + Color? onSurfaceVariant, + Color? error, + Color? onError, + Color? errorContainer, + Color? onErrorContainer, + Color? outline, + Color? outlineVariant, + Color? surfaceDim, + Color? surfaceBright, + Color? surfaceContainerLowest, + Color? surfaceContainerLow, + Color? surfaceContainer, + Color? surfaceContainerHigh, + Color? surfaceContainerHighest, + Color? inverseSurface, + Color? onInverseSurface, + Color? inversePrimary, + Color? surfaceTint, + Color? shadow, + Color? scrim, + Color? primaryFixed, + Color? primaryFixedDim, + Color? onPrimaryFixed, + Color? onPrimaryFixedVariant, + Color? secondaryFixed, + Color? secondaryFixedDim, + Color? onSecondaryFixed, + Color? onSecondaryFixedVariant, + Color? tertiaryFixed, + Color? tertiaryFixedDim, + Color? onTertiaryFixed, + Color? onTertiaryFixedVariant, + }) { + return M3EColors( + emphasis: emphasis ?? this.emphasis, + onEmphasis: onEmphasis ?? this.onEmphasis, + info: info ?? this.info, + success: success ?? this.success, + warning: warning ?? this.warning, + danger: danger ?? this.danger, + surfaceStrong: surfaceStrong ?? this.surfaceStrong, + onSurfaceStrong: onSurfaceStrong ?? this.onSurfaceStrong, + outlineStrong: outlineStrong ?? this.outlineStrong, + primary: primary ?? this.primary, + onPrimary: onPrimary ?? this.onPrimary, + primaryContainer: primaryContainer ?? this.primaryContainer, + onPrimaryContainer: onPrimaryContainer ?? this.onPrimaryContainer, + secondary: secondary ?? this.secondary, + onSecondary: onSecondary ?? this.onSecondary, + secondaryContainer: secondaryContainer ?? this.secondaryContainer, + onSecondaryContainer: onSecondaryContainer ?? this.onSecondaryContainer, + tertiary: tertiary ?? this.tertiary, + onTertiary: onTertiary ?? this.onTertiary, + tertiaryContainer: tertiaryContainer ?? this.tertiaryContainer, + onTertiaryContainer: onTertiaryContainer ?? this.onTertiaryContainer, + surface: surface ?? this.surface, + onSurface: onSurface ?? this.onSurface, + onSurfaceVariant: onSurfaceVariant ?? this.onSurfaceVariant, + error: error ?? this.error, + onError: onError ?? this.onError, + errorContainer: errorContainer ?? this.errorContainer, + onErrorContainer: onErrorContainer ?? this.onErrorContainer, + outline: outline ?? this.outline, + outlineVariant: outlineVariant ?? this.outlineVariant, + surfaceDim: surfaceDim ?? this.surfaceDim, + surfaceBright: surfaceBright ?? this.surfaceBright, + surfaceContainerLowest: + surfaceContainerLowest ?? this.surfaceContainerLowest, + surfaceContainerLow: surfaceContainerLow ?? this.surfaceContainerLow, + surfaceContainer: surfaceContainer ?? this.surfaceContainer, + surfaceContainerHigh: surfaceContainerHigh ?? this.surfaceContainerHigh, + surfaceContainerHighest: + surfaceContainerHighest ?? this.surfaceContainerHighest, + inverseSurface: inverseSurface ?? this.inverseSurface, + onInverseSurface: onInverseSurface ?? this.onInverseSurface, + inversePrimary: inversePrimary ?? this.inversePrimary, + surfaceTint: surfaceTint ?? this.surfaceTint, + shadow: shadow ?? this.shadow, + scrim: scrim ?? this.scrim, + primaryFixed: primaryFixed ?? this.primaryFixed, + primaryFixedDim: primaryFixedDim ?? this.primaryFixedDim, + onPrimaryFixed: onPrimaryFixed ?? this.onPrimaryFixed, + onPrimaryFixedVariant: + onPrimaryFixedVariant ?? this.onPrimaryFixedVariant, + secondaryFixed: secondaryFixed ?? this.secondaryFixed, + secondaryFixedDim: secondaryFixedDim ?? this.secondaryFixedDim, + onSecondaryFixed: onSecondaryFixed ?? this.onSecondaryFixed, + onSecondaryFixedVariant: + onSecondaryFixedVariant ?? this.onSecondaryFixedVariant, + tertiaryFixed: tertiaryFixed ?? this.tertiaryFixed, + tertiaryFixedDim: tertiaryFixedDim ?? this.tertiaryFixedDim, + onTertiaryFixed: onTertiaryFixed ?? this.onTertiaryFixed, + onTertiaryFixedVariant: + onTertiaryFixedVariant ?? this.onTertiaryFixedVariant, ); } @@ -158,7 +551,6 @@ class M3EColors { surfaceStrong: Color.lerp(a.surfaceStrong, b.surfaceStrong, t)!, onSurfaceStrong: Color.lerp(a.onSurfaceStrong, b.onSurfaceStrong, t)!, outlineStrong: Color.lerp(a.outlineStrong, b.outlineStrong, t)!, - // New fields primary: Color.lerp(a.primary, b.primary, t)!, onPrimary: Color.lerp(a.onPrimary, b.onPrimary, t)!, primaryContainer: @@ -188,9 +580,112 @@ class M3EColors { Color.lerp(a.onErrorContainer, b.onErrorContainer, t)!, outline: Color.lerp(a.outline, b.outline, t)!, outlineVariant: Color.lerp(a.outlineVariant, b.outlineVariant, t)!, - surfaceContainerHigh: - Color.lerp(a.surfaceContainerHigh, b.surfaceContainerHigh, t)!, + surfaceDim: Color.lerp(a.surfaceDim, b.surfaceDim, t)!, + surfaceBright: Color.lerp(a.surfaceBright, b.surfaceBright, t)!, surfaceContainerLowest: Color.lerp(a.surfaceContainerLowest, b.surfaceContainerLowest, t)!, + surfaceContainerLow: + Color.lerp(a.surfaceContainerLow, b.surfaceContainerLow, t)!, + surfaceContainer: + Color.lerp(a.surfaceContainer, b.surfaceContainer, t)!, + surfaceContainerHigh: + Color.lerp(a.surfaceContainerHigh, b.surfaceContainerHigh, t)!, + surfaceContainerHighest: Color.lerp( + a.surfaceContainerHighest, b.surfaceContainerHighest, t)!, + inverseSurface: Color.lerp(a.inverseSurface, b.inverseSurface, t)!, + onInverseSurface: + Color.lerp(a.onInverseSurface, b.onInverseSurface, t)!, + inversePrimary: Color.lerp(a.inversePrimary, b.inversePrimary, t)!, + surfaceTint: Color.lerp(a.surfaceTint, b.surfaceTint, t)!, + shadow: Color.lerp(a.shadow, b.shadow, t)!, + scrim: Color.lerp(a.scrim, b.scrim, t)!, + primaryFixed: Color.lerp(a.primaryFixed, b.primaryFixed, t), + primaryFixedDim: Color.lerp(a.primaryFixedDim, b.primaryFixedDim, t), + onPrimaryFixed: Color.lerp(a.onPrimaryFixed, b.onPrimaryFixed, t), + onPrimaryFixedVariant: + Color.lerp(a.onPrimaryFixedVariant, b.onPrimaryFixedVariant, t), + secondaryFixed: Color.lerp(a.secondaryFixed, b.secondaryFixed, t), + secondaryFixedDim: + Color.lerp(a.secondaryFixedDim, b.secondaryFixedDim, t), + onSecondaryFixed: Color.lerp(a.onSecondaryFixed, b.onSecondaryFixed, t), + onSecondaryFixedVariant: + Color.lerp(a.onSecondaryFixedVariant, b.onSecondaryFixedVariant, t), + tertiaryFixed: Color.lerp(a.tertiaryFixed, b.tertiaryFixed, t), + tertiaryFixedDim: Color.lerp(a.tertiaryFixedDim, b.tertiaryFixedDim, t), + onTertiaryFixed: Color.lerp(a.onTertiaryFixed, b.onTertiaryFixed, t), + onTertiaryFixedVariant: + Color.lerp(a.onTertiaryFixedVariant, b.onTertiaryFixedVariant, t), ); } + +/// Convenience extension to read expressive tokens from any [ColorScheme]. +extension M3EColorsX on ColorScheme { + M3EColors get m3e => M3EColors.from(this); +} +/* + +/// Theme helpers -------------------------------------------------------------- +class M3ETheme { + M3ETheme._(); + + /// Build a [ThemeData] using dynamic colors when available, otherwise + /// fall back to [ColorScheme.fromSeed]. Use inside a [dyn.DynamicColorBuilder]. + static ThemeData themeFromSchemes({ + required BuildContext context, + required ColorScheme lightScheme, + required ColorScheme darkScheme, + VisualDensity visualDensity = VisualDensity.adaptivePlatformDensity, + TargetPlatform? platform, + TextTheme? textTheme, + bool useMaterial3 = true, + }) { + return ThemeData( + useMaterial3: useMaterial3, + colorScheme: lightScheme, + brightness: Brightness.light, + visualDensity: visualDensity, + platform: platform, + textTheme: textTheme, + ); + } + + /// Utility to create [ColorScheme] either from platform dynamic colors or a seed. + /// Typical usage in your app root: + /// + /// ```dart + /// return dyn.DynamicColorBuilder( + /// builder: (lightDynamic, darkDynamic) { + /// final Color seed = const Color(0xFF6750A4); + /// final light = lightDynamic ?? ColorScheme.fromSeed(seedColor: seed); + /// final dark = darkDynamic ?? + /// ColorScheme.fromSeed(seedColor: seed, brightness: Brightness.dark); + /// return MaterialApp(theme: ThemeData(colorScheme: light), darkTheme: ThemeData(colorScheme: dark)); + /// }, + /// ); + /// ``` + static ({ColorScheme light, ColorScheme dark}) dynamicOrSeed( + Color seed, { + Color? secondarySeed, + Color? tertiarySeed, + double contrastLevel = 0.0, + dyn.DynamicSchemeVariant variant = dyn.DynamicSchemeVariant.tonalSpot, + }) { + final light = ColorScheme.fromSeed( + seedColor: seed, + contrastLevel: contrastLevel, + dynamicSchemeVariant: variant, + secondary: secondarySeed, + tertiary: tertiarySeed, + ); + final dark = ColorScheme.fromSeed( + seedColor: seed, + contrastLevel: contrastLevel, + dynamicSchemeVariant: variant, + brightness: Brightness.dark, + secondary: secondarySeed, + tertiary: tertiarySeed, + ); + return (light: light, dark: dark); + } +} +*/ diff --git a/packages/m3e_design/pubspec.yaml b/packages/m3e_design/pubspec.yaml index 93da521..e9df961 100644 --- a/packages/m3e_design/pubspec.yaml +++ b/packages/m3e_design/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: flutter: sdk: flutter + dynamic_color: ^1.8.1 dev_dependencies: flutter_test: diff --git a/packages/progress_indicator_m3e/LICENSE b/packages/progress_indicator_m3e/LICENSE deleted file mode 100644 index 12ca7c2..0000000 --- a/packages/progress_indicator_m3e/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) ... - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/progress_indicator_m3e/README.md b/packages/progress_indicator_m3e/README.md index 5326931..c2b83bd 100644 --- a/packages/progress_indicator_m3e/README.md +++ b/packages/progress_indicator_m3e/README.md @@ -1,65 +1,14 @@ -# progress_indicators_m3e -Material 3 **Expressive** progress indicators for Flutter: +# progress_indicator_m3e (spec build) -- `LinearProgressM3E` — determinate, indeterminate, **buffer**, **query**, with **flat** or **wavy** shape -- `CircularProgressM3E` — determinate & indeterminate, **flat** or **wavy** stroke (animated for indeterminate) -- `ProgressWithLabelM3E` — compose a linear bar with inline/top/bottom/center labels +**Visual rules implemented** +- Active and track never overlap. +- Circular ring is *broken* around the active sweep. +- Squiggle variants (48/52) draw a sine-like stroke inside the ring with 2dp clearance. +- Linear shows two lanes (active above, track below) with fixed gap and end-dot, per table. -All widgets read **M3E tokens** (`m3e_design`) for color, sizing, and typography. - -## Defaults (from the spec illustrations) - -- Linear: default thickness 4dp; configurable via `size` or `strokeHeight` -- Linear (wavy): `wavelength=40dp`, `amplitude≈height/3`, **4dp** left/right inset -- Circular: small/medium/large diameters ≈ 24/32/48 with stroke ≈ 3/4/6 -- Circular (wavy): default **10 waves** around the circle, amplitude ≈ 35% of stroke - -## Quick start - -```dart -import 'package:progress_indicators_m3e/progress_indicators_m3e.dart'; - -// Linear (wavy, determinate) -LinearProgressM3E( - value: 0.62, - shape: LinearBarShapeM3E.wavy, -); - -// Circular (wavy, indeterminate) -const CircularProgressM3E( - shape: CircularBarShapeM3E.wavy, -); - -// Linear (buffer) flat -LinearProgressM3E( - variant: LinearProgressM3EVariant.buffer, - value: 0.3, - bufferValue: 0.6, -); - -// Circular (flat) with center label -CircularProgressM3E( - value: 0.5, - showCenterLabel: true, -); -``` - -## Monorepo layout - -``` -packages/ - m3e_design/ - progress_indicators_m3e/ -``` - -`pubspec.yaml` references `../m3e_design`. - -## Accessibility - -- Provide `semanticLabel` and (for determinate) the widgets expose a numeric **value** for screen readers. -- Indeterminate wavy animations use modest motion; gate the speed with a future `m3e_design.motion` flag if you support "reduce motion". - -## License - -MIT +**Linear variants** +- `flatXS` — track 4, gap 4, dot Ø4, dotOffset 4, trailing 4 +- `flatS` — track 8, gap 4, dot Ø4, dotOffset 2, trailing 8 +- `wavyM` — track 4, wave amp 3, period 40, gap 4, dot Ø4, dotOffset 2, trailing 10 +- `wavyL` — track 8, wave amp 3, period 40, gap 4, dot Ø4, dotOffset 2, trailing 14 diff --git a/packages/progress_indicator_m3e/lib/progress_indicator_m3e.dart b/packages/progress_indicator_m3e/lib/progress_indicator_m3e.dart index 887284f..c67addc 100644 --- a/packages/progress_indicator_m3e/lib/progress_indicator_m3e.dart +++ b/packages/progress_indicator_m3e/lib/progress_indicator_m3e.dart @@ -1,7 +1,7 @@ -library progress_indicators_m3e; + +library progress_indicator_m3e; export 'src/enums.dart'; -export 'src/tokens_adapter.dart' show ProgressTokensAdapter; export 'src/linear_progress_m3e.dart'; export 'src/circular_progress_m3e.dart'; export 'src/progress_with_label_m3e.dart'; diff --git a/packages/progress_indicator_m3e/lib/src/_tokens.dart b/packages/progress_indicator_m3e/lib/src/_tokens.dart new file mode 100644 index 0000000..94fdddc --- /dev/null +++ b/packages/progress_indicator_m3e/lib/src/_tokens.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +@immutable +class Palette { + const Palette(this.cs); + final ColorScheme cs; + + // Use theme roles; callers can override colors if needed. + Color get active => cs.primary; + Color get track => cs.onSurfaceVariant.withOpacity(0.24); + Color get bg => cs.surface; +} + +@immutable +class LinearSpec { + const LinearSpec({ + required this.trackHeight, + required this.gap, + required this.dotDiameter, + required this.dotOffset, + required this.trailingMargin, + required this.isWavy, + this.waveAmplitude = 0, + this.wavePeriod = 40, + }); + + final double trackHeight; + final double gap; // vertical space between active lane and track lane + final double dotDiameter; + final double dotOffset; // center offset from end of active segment + final double trailingMargin; // empty space at the far right + final bool isWavy; + final double waveAmplitude; + final double wavePeriod; +} + +LinearSpec specForLinear({ + required LinearProgressM3ESize size, + required ProgressM3EShape shape, +}) => switch ((shape, size)) { + (ProgressM3EShape.flat, LinearProgressM3ESize.s) => const LinearSpec( + trackHeight: 4, + gap: 4, + dotDiameter: 4, + dotOffset: 4, + trailingMargin: 4, + isWavy: false, + ), + (ProgressM3EShape.flat, LinearProgressM3ESize.m) => const LinearSpec( + trackHeight: 8, + gap: 4, + dotDiameter: 4, + dotOffset: 2, + trailingMargin: 8, + isWavy: false, + ), + (ProgressM3EShape.wavy, LinearProgressM3ESize.s) => const LinearSpec( + trackHeight: 4, + gap: 4, + dotDiameter: 4, + dotOffset: 2, + trailingMargin: 10, + isWavy: true, + waveAmplitude: 3, + wavePeriod: 40, + ), + (ProgressM3EShape.wavy, LinearProgressM3ESize.m) => const LinearSpec( + trackHeight: 8, + gap: 4, + dotDiameter: 4, + dotOffset: 2, + trailingMargin: 14, + isWavy: true, + waveAmplitude: 3, + wavePeriod: 40, + ), + }; diff --git a/packages/progress_indicator_m3e/lib/src/circular_progress_m3e.dart b/packages/progress_indicator_m3e/lib/src/circular_progress_m3e.dart index 7ab7d8a..57f7b1f 100644 --- a/packages/progress_indicator_m3e/lib/src/circular_progress_m3e.dart +++ b/packages/progress_indicator_m3e/lib/src/circular_progress_m3e.dart @@ -3,284 +3,199 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'enums.dart'; -import 'tokens_adapter.dart'; -class CircularProgressM3E extends StatefulWidget { - const CircularProgressM3E({ +class CircularProgressIndicatorM3E extends StatelessWidget { + const CircularProgressIndicatorM3E({ super.key, this.value, - this.size = ProgressM3ESize.medium, - this.emphasis = ProgressM3EEmphasis.primary, - this.density = ProgressM3EDensity.regular, - this.backgroundColor, - this.strokeWidth, - this.semanticLabel, - this.showCenterLabel = false, - this.centerLabelBuilder, - this.shape = CircularBarShapeM3E.wavy, - this.waveCount, - this.waveAmplitude, - this.rotateClockwise = true, + this.size = CircularProgressM3ESize.m, + this.shape = ProgressM3EShape.wavy, + this.activeColor, + this.trackColor, + this.rotation = 0.0, // radians, for indeterminate rotation }); - /// Determinate value (0..1). If null, renders indeterminate. - final double? value; - - final ProgressM3ESize size; - final ProgressM3EEmphasis emphasis; - final ProgressM3EDensity density; - - final Color? backgroundColor; - final double? strokeWidth; - - /// Optional semantics label. - final String? semanticLabel; - - /// Show a label centered inside (e.g., percentage). - final bool showCenterLabel; - - /// Builder for custom center label; if null and showCenterLabel==true, shows percentage text. - final Widget Function(BuildContext context, double? value)? - centerLabelBuilder; - - /// Expressive shape - final CircularBarShapeM3E shape; - final int? waveCount; - final double? waveAmplitude; - final bool rotateClockwise; - - @override - State createState() => _CircularProgressM3EState(); -} - -class _CircularProgressM3EState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _anim = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1400), - )..repeat(); - - @override - void dispose() { - _anim.dispose(); - super.dispose(); - } + final double? value; // 0..1 (null => indeterminate arc sweep) + final CircularProgressM3ESize size; + final ProgressM3EShape shape; + final Color? activeColor; + final Color? trackColor; + final double rotation; @override Widget build(BuildContext context) { - final tokens = ProgressTokensAdapter(context); - final m = tokens.metrics(widget.density); - final color = tokens.color(widget.emphasis); - final bg = widget.backgroundColor ?? tokens.trackColor(); - - final (diameter, stroke) = switch (widget.size) { - ProgressM3ESize.small => ( - m.circularSmall, - widget.strokeWidth ?? m.strokeSmall + final cs = Theme.of(context).colorScheme; + final active = activeColor ?? cs.primary; + final track = trackColor ?? cs.onSurfaceVariant.withOpacity(0.24); + final wantsWavy = shape == ProgressM3EShape.wavy; + final diameter = wantsWavy ? size.diameterWavy : size.diameterFlat; + return RepaintBoundary( + child: SizedBox( + width: diameter, + height: diameter, + child: CustomPaint( + painter: wantsWavy + ? _CircularWavyPainter( + value: value, + active: active, + track: track, + rotation: rotation) + : _CircularFlatPainter( + value: value, + active: active, + track: track, + rotation: rotation, + size: size), ), - ProgressM3ESize.medium => ( - m.circularMedium, - widget.strokeWidth ?? m.strokeMedium - ), - ProgressM3ESize.large => ( - m.circularLarge, - widget.strokeWidth ?? m.strokeLarge - ), - }; - - final indicator = SizedBox( - width: diameter, - height: diameter, - child: Stack( - alignment: Alignment.center, - children: [ - // Track ring - CustomPaint( - size: Size.square(diameter), - painter: _RingPainter(color: bg, stroke: stroke), - ), - // Progress - if (widget.shape == CircularBarShapeM3E.flat) ...[ - CustomPaint( - size: Size.square(diameter), - painter: _ArcPainter( - color: color, - stroke: stroke, - value: widget.value, - clockwise: widget.rotateClockwise, - ), - ), - ] else ...[ - AnimatedBuilder( - animation: _anim, - builder: (context, _) => CustomPaint( - size: Size.square(diameter), - painter: _WavyArcPainter( - color: color, - stroke: stroke, - value: widget.value, - waves: widget.waveCount ?? m.circularWavesPerCircle, - amplitude: (widget.waveAmplitude ?? - (m.circularWaveAmplitudeFactor * stroke)) - .clamp(0, stroke / 2), - phase: - (widget.value == null ? 2 * math.pi * _anim.value : 0) * - (widget.rotateClockwise ? 1 : -1), - clockwise: widget.rotateClockwise, - ), - ), - ), - ], - if (widget.showCenterLabel) - DefaultTextStyle( - style: tokens.labelStyle().copyWith( - color: Theme.of(context).colorScheme.onSurface, - ), - child: widget.centerLabelBuilder?.call(context, widget.value) ?? - Text(widget.value != null - ? '${(widget.value! * 100).toStringAsFixed(0)}%' - : ''), - ), - ], ), ); - - if (widget.semanticLabel == null) return indicator; - return Semantics( - label: widget.semanticLabel, - value: widget.value != null - ? '${(widget.value! * 100).toStringAsFixed(0)}%' - : null, - child: indicator, - ); } } -class _RingPainter extends CustomPainter { - _RingPainter({required this.color, required this.stroke}); - final Color color; - final double stroke; +class _CircularFlatPainter extends CustomPainter { + _CircularFlatPainter( + {required this.value, + required this.active, + required this.track, + required this.rotation, + required this.size}); - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color - ..style = PaintingStyle.stroke - ..strokeWidth = stroke - ..strokeCap = StrokeCap.round; - final rect = Offset.zero & size; - final center = rect.center; - final radius = (size.shortestSide - stroke) / 2; - canvas.drawCircle(center, radius, paint); - } - - @override - bool shouldRepaint(covariant _RingPainter old) => - old.color != color || old.stroke != stroke; -} - -class _ArcPainter extends CustomPainter { - _ArcPainter({ - required this.color, - required this.stroke, - required this.value, - required this.clockwise, - }); - - final Color color; - final double stroke; final double? value; - final bool clockwise; + final Color active; + final Color track; + final double rotation; + final CircularProgressM3ESize size; @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color + void paint(Canvas canvas, Size s) { + final stroke = 4.0; + final center = s.center(Offset.zero); + final radius = (math.min(s.width, s.height) - stroke) / 2; + final rect = Rect.fromCircle(center: center, radius: radius); + + // gap before active in dp -> angle + final gapDp = 8.0; + final gapAngle = gapDp / radius; // s = r * angle + + // active sweep + final sweep = + value == null ? math.pi * 1.5 : (value!.clamp(0.0, 1.0) * math.pi * 2); + + final start = -math.pi / 2 + rotation; + final activeStart = start; + final activeEnd = start + sweep; + + // TRACK: draw the rest of the ring, leaving a gap ahead of the active arc and no overlap. + final trackPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = stroke - ..strokeCap = StrokeCap.round; + ..strokeCap = StrokeCap.round + ..isAntiAlias = true + ..color = track; - final rect = Offset.zero & size; - final center = rect.center; - final radius = (size.shortestSide - stroke) / 2; + final total = math.pi * 2; + final a1 = (activeEnd + gapAngle); + final a2 = (activeStart - gapAngle); + double sweep1 = (a2 - a1); + while (sweep1 <= 0) sweep1 += total; + canvas.drawArc(rect, a1, sweep1, false, trackPaint); - final start = -math.pi / 2; - final sweep = (value ?? 0.25) * 2 * math.pi * (clockwise ? 1 : -1); - - canvas.drawArc(Rect.fromCircle(center: center, radius: radius), start, - sweep, false, paint); - if (value == null) { - // indeterminate - draw a moving arc; this painter is used only for determinate (flat) - } + // ACTIVE arc + final activePaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = stroke + ..strokeCap = StrokeCap.round + ..isAntiAlias = true + ..color = active; + canvas.drawArc(rect, activeStart, sweep, false, activePaint); } @override - bool shouldRepaint(covariant _ArcPainter old) => - old.color != color || - old.stroke != stroke || - old.value != value || - old.clockwise != clockwise; + bool shouldRepaint(covariant _CircularFlatPainter old) => + value != old.value || + active != old.active || + track != old.track || + rotation != old.rotation || + size != old.size; } -class _WavyArcPainter extends CustomPainter { - _WavyArcPainter({ - required this.color, - required this.stroke, - required this.value, - required this.waves, - required this.amplitude, - required this.phase, - required this.clockwise, - }); +class _CircularWavyPainter extends CustomPainter { + _CircularWavyPainter( + {required this.value, + required this.active, + required this.track, + required this.rotation}); - final Color color; - final double stroke; final double? value; - final int waves; - final double amplitude; - final double phase; - final bool clockwise; + final Color active; + final Color track; + final double rotation; @override - void paint(Canvas canvas, Size size) { - final rect = Offset.zero & size; - final center = rect.center; - final baseRadius = (size.shortestSide - stroke) / 2; + void paint(Canvas canvas, Size s) { + const stroke = 4.0; + final center = s.center(Offset.zero); + final baseRadius = (math.min(s.width, s.height) - stroke) / 2; - final paint = Paint() - ..color = color + // Squiggle clearance: 2dp (edge-to-edge). Approximate by insetting the squiggle centerline by 6dp. + final clearance = 2.0; + final squiggleRadius = + baseRadius - (stroke / 2 + clearance + stroke / 2); // baseRadius - 6 + final amp = 2.0; // radial amplitude of squiggle + final scallopLen = 18.0; // along-arc wavelength proxy (dp) + + // Active sweep + final activeSweep = + value == null ? math.pi * 1.5 : (value!.clamp(0.0, 1.0) * math.pi * 2); + final start = -math.pi / 2 + rotation; + final end = start + activeSweep; + + // Track ring with gap around active + final trackPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = stroke - ..strokeCap = StrokeCap.round; + ..strokeCap = StrokeCap.round + ..isAntiAlias = true + ..color = track; - final totalAngle = (value ?? 1.0) * 2 * math.pi * (clockwise ? 1 : -1); - final start = -math.pi / 2; + final gapAngle = 2.0 / baseRadius; + final rect = Rect.fromCircle(center: center, radius: baseRadius); + final total = math.pi * 2; + final a1 = end + gapAngle; + final a2 = start - gapAngle; + double sweep1 = (a2 - a1); + while (sweep1 <= 0) sweep1 += total; + canvas.drawArc(rect, a1, sweep1, false, trackPaint); + // Active squiggle path + final steps = math.max(48, (s.width * 1.2).round()); final path = Path(); - final steps = (200 * (value ?? 1.0)).clamp(40, 300).toInt(); // resolution for (int i = 0; i <= steps; i++) { final t = i / steps; - final theta = start + totalAngle * t; - final wave = math.sin((t * waves * 2 * math.pi) + phase); - final r = baseRadius + amplitude * wave; - final p = Offset( - center.dx + r * math.cos(theta), center.dy + r * math.sin(theta)); - if (i == 0) { + final ang = start + (end - start) * t; + final arcLen = squiggleRadius * (ang - start); + final r = + squiggleRadius + amp * math.sin(arcLen / scallopLen * 2 * math.pi); + final p = + Offset(center.dx + r * math.cos(ang), center.dy + r * math.sin(ang)); + if (i == 0) path.moveTo(p.dx, p.dy); - } else { + else path.lineTo(p.dx, p.dy); - } } - canvas.drawPath(path, paint); + final activePaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = stroke + ..strokeCap = StrokeCap.round + ..isAntiAlias = true + ..color = active; + canvas.drawPath(path, activePaint); } @override - bool shouldRepaint(covariant _WavyArcPainter old) => - old.color != color || - old.stroke != stroke || - old.value != value || - old.waves != waves || - old.amplitude != amplitude || - old.phase != phase || - old.clockwise != clockwise; + bool shouldRepaint(covariant _CircularWavyPainter old) => + value != old.value || + active != old.active || + track != old.track || + rotation != old.rotation; } diff --git a/packages/progress_indicator_m3e/lib/src/enums.dart b/packages/progress_indicator_m3e/lib/src/enums.dart index e26aa63..afb069d 100644 --- a/packages/progress_indicator_m3e/lib/src/enums.dart +++ b/packages/progress_indicator_m3e/lib/src/enums.dart @@ -1,8 +1,27 @@ -enum ProgressM3ESize { small, medium, large } -enum ProgressM3EEmphasis { primary, secondary, surface } -enum ProgressM3EDensity { regular, compact } -enum LinearProgressM3EVariant { determinate, indeterminate, buffer, query } -enum ProgressLabelPosition { none, leading, trailing, top, bottom, center } +/// Circular sizes driven by outer diameter. +enum CircularProgressM3ESize { s, m } -enum LinearBarShapeM3E { flat, wavy } -enum CircularBarShapeM3E { flat, wavy } +extension CircularM3ESizeExtension on CircularProgressM3ESize { + double get diameterWavy { + switch (this) { + case CircularProgressM3ESize.s: + return 48.0; // wavy small + case CircularProgressM3ESize.m: + return 52.0; // wavy medium + } + } + + double get diameterFlat { + switch (this) { + case CircularProgressM3ESize.s: + return 40.0; // flat small + case CircularProgressM3ESize.m: + return 44.0; // flat medium + } + } +} + +/// Linear sizes and shapes +enum LinearProgressM3ESize { s, m } + +enum ProgressM3EShape { flat, wavy } diff --git a/packages/progress_indicator_m3e/lib/src/linear_progress_m3e.dart b/packages/progress_indicator_m3e/lib/src/linear_progress_m3e.dart index f5bf012..284423f 100644 --- a/packages/progress_indicator_m3e/lib/src/linear_progress_m3e.dart +++ b/packages/progress_indicator_m3e/lib/src/linear_progress_m3e.dart @@ -1,378 +1,165 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:m3e_design/m3e_design.dart'; +import '_tokens.dart'; import 'enums.dart'; -import 'tokens_adapter.dart'; -class LinearProgressM3E extends StatefulWidget { - const LinearProgressM3E({ +/// Linear indicator that renders two **separate lanes** (active above, track below) +/// with a fixed vertical gap. Lanes never overlap. +class LinearProgressIndicatorM3E extends StatelessWidget { + const LinearProgressIndicatorM3E({ super.key, - this.value, - this.bufferValue, - this.variant = LinearProgressM3EVariant.determinate, - this.size = ProgressM3ESize.medium, - this.emphasis = ProgressM3EEmphasis.primary, - this.density = ProgressM3EDensity.regular, - this.backgroundColor, - this.progressColor, - this.bufferColor, - this.semanticLabel, - this.minWidth = double.infinity, - this.strokeHeight, - this.borderRadius, - this.shape = LinearBarShapeM3E.wavy, - this.wavelength, - this.amplitude, - this.leftRightInset, + this.value, // null => indeterminate; animate phase externally + this.size = LinearProgressM3ESize.m, + this.shape = ProgressM3EShape.wavy, + this.activeColor, + this.trackColor, + this.phase = 0.0, // radians for wavy animation + this.inset = 4.0, // horizontal left inset }); final double? value; - final double? bufferValue; - final LinearProgressM3EVariant variant; - final ProgressM3ESize size; - final ProgressM3EEmphasis emphasis; - final ProgressM3EDensity density; - final Color? backgroundColor; - final Color? progressColor; - final Color? bufferColor; - final String? semanticLabel; - final double minWidth; - final double? strokeHeight; - final BorderRadius? borderRadius; - final LinearBarShapeM3E shape; - final double? wavelength; - final double? amplitude; - final double? leftRightInset; - - @override - State createState() => _LinearProgressM3EState(); -} - -class _LinearProgressM3EState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _anim = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1200), - )..repeat(); - - @override - void dispose() { - _anim.dispose(); - super.dispose(); - } + final LinearProgressM3ESize size; + final ProgressM3EShape shape; + final Color? activeColor; + final Color? trackColor; + final double phase; + final double inset; @override Widget build(BuildContext context) { - final tokens = ProgressTokensAdapter(context); - final m = tokens.metrics(widget.density); + final theme = Theme.of(context); + final m3e = + theme.extension() ?? M3ETheme.defaults(theme.colorScheme); - final height = switch (widget.size) { - ProgressM3ESize.small => widget.strokeHeight ?? m.linearThicknessSmall, - ProgressM3ESize.medium => widget.strokeHeight ?? m.linearThicknessMedium, - ProgressM3ESize.large => widget.strokeHeight ?? m.linearThicknessLarge, - }; + // Farben aus m3e_design beziehen (überschreibbar per Props) + final active = activeColor ?? m3e.colors.primary; + final track = trackColor ?? m3e.colors.surfaceContainerHighest; - final track = widget.backgroundColor ?? tokens.trackColor(); - final progress = widget.progressColor ?? tokens.color(widget.emphasis); - final buffer = widget.bufferColor ?? tokens.bufferColor(progress); + final spec = specForLinear(size: size, shape: shape); - final borderRadius = - widget.borderRadius ?? BorderRadius.circular(height / 2); - final inset = widget.leftRightInset ?? m.horizontalInset; + // Total height = active lane height (trackHeight or wavyHeight) + gap + trackHeight + final activeHeight = spec.isWavy + ? (spec.trackHeight + 2 * spec.waveAmplitude) + : spec.trackHeight; + final totalHeight = activeHeight + spec.gap + spec.trackHeight; - final content = Padding( - padding: EdgeInsets.symmetric(horizontal: inset), - child: _buildBar( - context, height, borderRadius, track, progress, buffer, tokens), - ); - - final bar = ClipRRect( - borderRadius: borderRadius, + return RepaintBoundary( child: SizedBox( - height: height, - width: widget.minWidth == double.infinity ? null : widget.minWidth, - child: content, - ), - ); - - if (widget.semanticLabel == null) return bar; - return Semantics( - label: widget.semanticLabel, - value: (widget.variant == LinearProgressM3EVariant.determinate && - widget.value != null) - ? '${(widget.value!.clamp(0.0, 1.0) * 100).toStringAsFixed(0)}%' - : null, - child: bar, - ); - } - - Widget _buildBar( - BuildContext context, - double height, - BorderRadius borderRadius, - Color track, - Color progress, - Color buffer, - ProgressTokensAdapter tokens, - ) { - final variant = widget.variant; - final shape = widget.shape; - - if (shape == LinearBarShapeM3E.flat) { - // Use standard LinearProgressIndicator behaviors. - if (variant == LinearProgressM3EVariant.indeterminate || - (variant == LinearProgressM3EVariant.determinate && - widget.value == null)) { - return LinearProgressIndicator( - color: progress, - backgroundColor: track, - minHeight: height, - ); - } else if (variant == LinearProgressM3EVariant.query) { - return Transform( - alignment: Alignment.center, - transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0), - child: LinearProgressIndicator( - color: progress, - backgroundColor: track, - minHeight: height, + height: totalHeight, + width: double.infinity, + child: CustomPaint( + painter: _LinearPainter( + value: value, + spec: spec, + active: activeColor ?? active, + track: trackColor ?? track, + phase: phase, + inset: inset, ), - ); - } else if (variant == LinearProgressM3EVariant.buffer) { - return _BufferBar( - height: height, - track: track, - buffer: buffer, - progress: progress, - value: widget.value ?? 0.0, - bufferValue: widget.bufferValue ?? 0.0, - ); - } else { - return LinearProgressIndicator( - value: (widget.value ?? 0.0).clamp(0.0, 1.0), - color: progress, - backgroundColor: track, - minHeight: height, - ); - } - } - - final wavelength = - widget.wavelength ?? tokens.metrics(widget.density).wavyWavelength; - final amplitude = widget.amplitude ?? - tokens.metrics(widget.density).wavyAmplitudeFactor * height; - - if (variant == LinearProgressM3EVariant.determinate && - widget.value != null) { - return _WavyBar( - value: widget.value!.clamp(0.0, 1.0), - height: height, - wavelength: wavelength, - amplitude: amplitude.clamp(0.0, height / 2), - track: track, - fill: progress, - ); - } - - // Indeterminate / query / missing value → animate phase - return AnimatedBuilder( - animation: _anim, - builder: (context, _) { - final phase = 2 * math.pi * _anim.value; - final reverse = widget.variant == LinearProgressM3EVariant.query; - return _WavyIndeterminateBar( - height: height, - wavelength: wavelength, - amplitude: amplitude.clamp(0.0, height / 2), - track: track, - fill: progress, - phase: reverse ? -phase : phase, - ); - }, - ); - } -} - -class _BufferBar extends StatelessWidget { - const _BufferBar({ - required this.height, - required this.track, - required this.buffer, - required this.progress, - required this.value, - required this.bufferValue, - }); - - final double height; - final Color track; - final Color buffer; - final Color progress; - final double value; - final double bufferValue; - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - final w = constraints.maxWidth; - final pv = (w.isFinite ? w : 0) * value.clamp(0.0, 1.0); - final bv = (w.isFinite ? w : 0) * bufferValue.clamp(0.0, 1.0); - - Widget seg(double width, Color color) => Align( - alignment: Alignment.centerLeft, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - curve: Curves.easeOutCubic, - width: width, - height: height, - color: color, - ), - ); - - return Stack( - fit: StackFit.passthrough, - children: [ - ColoredBox(color: track), - seg(bv, buffer), - seg(pv, progress), - ], - ); - }); - } -} - -class _WavyBar extends StatelessWidget { - const _WavyBar({ - required this.value, - required this.height, - required this.wavelength, - required this.amplitude, - required this.track, - required this.fill, - }); - - final double value; - final double height; - final double wavelength; - final double amplitude; - final Color track; - final Color fill; - - @override - Widget build(BuildContext context) { - return CustomPaint( - painter: _WavyPainter( - value: value, - height: height, - wavelength: wavelength, - amplitude: amplitude, - track: track, - fill: fill, - phase: 0, - indeterminate: false, + ), ), ); } } -class _WavyIndeterminateBar extends StatelessWidget { - const _WavyIndeterminateBar({ - required this.height, - required this.wavelength, - required this.amplitude, - required this.track, - required this.fill, - required this.phase, - }); - - final double height; - final double wavelength; - final double amplitude; - final Color track; - final Color fill; - final double phase; - - @override - Widget build(BuildContext context) { - return CustomPaint( - painter: _WavyPainter( - value: 0.6, - height: height, - wavelength: wavelength, - amplitude: amplitude, - track: track, - fill: fill, - phase: phase, - indeterminate: true, - ), - ); - } -} - -class _WavyPainter extends CustomPainter { - _WavyPainter({ +class _LinearPainter extends CustomPainter { + _LinearPainter({ required this.value, - required this.height, - required this.wavelength, - required this.amplitude, + required this.spec, + required this.active, required this.track, - required this.fill, required this.phase, - required this.indeterminate, + required this.inset, }); - final double value; - final double height; - final double wavelength; - final double amplitude; + final double? value; + final LinearSpec spec; + final Color active; final Color track; - final Color fill; final double phase; - final bool indeterminate; + final double inset; @override void paint(Canvas canvas, Size size) { - final paintTrack = Paint()..color = track; - final paintFill = Paint()..color = fill; + final left = inset; + final right = size.width - spec.trailingMargin; + final width = math.max(0.0, right - left); - final r = RRect.fromRectAndRadius( - Offset.zero & Size(size.width, height), Radius.circular(height / 2)); - canvas.drawRRect(r, paintTrack); + // lane centers: active on top, track on bottom + final trackCy = size.height - spec.trackHeight / 2; + final activeHeight = spec.isWavy + ? (spec.trackHeight + 2 * spec.waveAmplitude) + : spec.trackHeight; + final activeCy = + trackCy - (spec.trackHeight / 2 + spec.gap + activeHeight / 2); - final w = size.width; - final progressW = indeterminate ? w : (w * value.clamp(0.0, 1.0)); + // --- Draw track lane (flat pill) --- + final base = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = spec.trackHeight + ..strokeCap = StrokeCap.round + ..isAntiAlias = true; - final centerY = height / 2; - final path = Path()..moveTo(0, height); - path.lineTo(0, centerY); + canvas.drawLine( + Offset(left, trackCy), Offset(right, trackCy), base..color = track); - final k = 2 * math.pi / wavelength; - final step = 2.0; - double x = 0; - while (x <= progressW) { - final y = centerY - amplitude * math.sin(k * x + phase); - path.lineTo(x, y); - x += step; + // --- Active lane --- + final double p = (value ?? 0).clamp(0.0, 1.0); + if (spec.isWavy) { + // wavy centerline + final start = left; + final end = value == null ? right : (left + width * p); + final path = Path(); + const step = 1.5; + final k = 2 * math.pi / spec.wavePeriod; + + double x = start; + double y = + activeCy + spec.waveAmplitude * math.sin(phase + (x - start) * k); + path.moveTo(x, y); + for (x = start + step; x <= end; x += step) { + y = activeCy + spec.waveAmplitude * math.sin(phase + (x - start) * k); + path.lineTo(x, y); + } + // precise end point + y = activeCy + spec.waveAmplitude * math.sin(phase + (end - start) * k); + path.lineTo(end, y); + + canvas.drawPath( + path, + base + ..color = active + ..strokeWidth = spec.trackHeight); + + // end dot (non-overlapping, placed slightly before end) + final dotCenterX = math.max(start, end - spec.dotOffset); + canvas.drawCircle( + Offset(dotCenterX, y), spec.dotDiameter / 2, Paint()..color = active); + } else { + // flat active pill + end dot + final start = left; + final end = value == null ? right : (left + width * p); + canvas.drawLine( + Offset(start, activeCy), + Offset(end, activeCy), + base + ..color = active + ..strokeWidth = spec.trackHeight); + final dotCenterX = math.max(start, end - spec.dotOffset); + canvas.drawCircle(Offset(dotCenterX, activeCy), spec.dotDiameter / 2, + Paint()..color = active); } - path.lineTo(progressW, height); - path.close(); - - canvas.save(); - final clip = Path()..addRRect(r); - canvas.clipPath(clip); - canvas.drawPath(path, paintFill); - canvas.restore(); } @override - bool shouldRepaint(covariant _WavyPainter old) { - return old.value != value || - old.height != height || - old.wavelength != wavelength || - old.amplitude != amplitude || - old.phase != phase || - old.track != track || - old.fill != fill || - old.indeterminate != indeterminate; - } + bool shouldRepaint(covariant _LinearPainter old) => + value != old.value || + spec != old.spec || + active != old.active || + track != old.track || + phase != old.phase || + inset != old.inset; } diff --git a/packages/progress_indicator_m3e/lib/src/progress_with_label_m3e.dart b/packages/progress_indicator_m3e/lib/src/progress_with_label_m3e.dart index 6ba7a19..ff4fa19 100644 --- a/packages/progress_indicator_m3e/lib/src/progress_with_label_m3e.dart +++ b/packages/progress_indicator_m3e/lib/src/progress_with_label_m3e.dart @@ -1,74 +1,35 @@ import 'package:flutter/material.dart'; + +import 'circular_progress_m3e.dart'; import 'enums.dart'; -import 'tokens_adapter.dart'; -import 'linear_progress_m3e.dart'; class ProgressWithLabelM3E extends StatelessWidget { const ProgressWithLabelM3E({ super.key, - required this.progress, - this.position = ProgressLabelPosition.trailing, - this.label, - this.spacing, + required this.value, + this.size = CircularProgressM3ESize.m, this.textStyle, }); - final LinearProgressM3E progress; - final ProgressLabelPosition position; - final Widget? label; - final double? spacing; + final double value; + final CircularProgressM3ESize size; final TextStyle? textStyle; @override Widget build(BuildContext context) { - if (position == ProgressLabelPosition.none) return progress; - - final tokens = ProgressTokensAdapter(context); - final style = textStyle ?? tokens.labelStyle().copyWith( - color: Theme.of(context).colorScheme.onSurface, + final d = + size.diameterWavy; // ProgressWithLabel uses wavy circular by default + return SizedBox( + width: d, + height: d, + child: Stack( + alignment: Alignment.center, + children: [ + CircularProgressIndicatorM3E(value: value, size: size), + Text('${(value * 100).round()}%', + style: textStyle ?? Theme.of(context).textTheme.labelMedium), + ], + ), ); - final gap = spacing ?? 8.0; - - final value = progress.value; - final builtLabel = label ?? Text( - value != null ? '${(value * 100).toStringAsFixed(0)}%' : '', - style: style, - ); - - switch (position) { - case ProgressLabelPosition.leading: - case ProgressLabelPosition.trailing: - final children = [ - if (position == ProgressLabelPosition.leading) builtLabel, - if (position == ProgressLabelPosition.leading) SizedBox(width: gap), - Expanded(child: progress), - if (position == ProgressLabelPosition.trailing) SizedBox(width: gap), - if (position == ProgressLabelPosition.trailing) builtLabel, - ]; - return Row(children: children); - case ProgressLabelPosition.top: - case ProgressLabelPosition.bottom: - final children = [ - if (position == ProgressLabelPosition.top) builtLabel, - if (position == ProgressLabelPosition.top) SizedBox(height: gap), - progress, - if (position == ProgressLabelPosition.bottom) SizedBox(height: gap), - if (position == ProgressLabelPosition.bottom) builtLabel, - ]; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children, - ); - case ProgressLabelPosition.center: - return Stack( - alignment: Alignment.center, - children: [ - progress, - builtLabel, - ], - ); - case ProgressLabelPosition.none: - return progress; - } } } diff --git a/packages/progress_indicator_m3e/lib/src/tokens_adapter.dart b/packages/progress_indicator_m3e/lib/src/tokens_adapter.dart deleted file mode 100644 index 4066f91..0000000 --- a/packages/progress_indicator_m3e/lib/src/tokens_adapter.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:m3e_design/m3e_design.dart'; -import 'enums.dart'; - -@immutable -class _ProgressMetrics { - final double circularSmall; - final double circularMedium; - final double circularLarge; - final double strokeSmall; - final double strokeMedium; - final double strokeLarge; - final double linearThicknessSmall; - final double linearThicknessMedium; - final double linearThicknessLarge; - final double wavyWavelength; // dp (linear) - final double wavyAmplitudeFactor; // fraction of bar height (linear) - final double horizontalInset; // 4dp - final double circularWaveAmplitudeFactor; // fraction of stroke - final int circularWavesPerCircle; // rough default - const _ProgressMetrics({ - required this.circularSmall, - required this.circularMedium, - required this.circularLarge, - required this.strokeSmall, - required this.strokeMedium, - required this.strokeLarge, - required this.linearThicknessSmall, - required this.linearThicknessMedium, - required this.linearThicknessLarge, - required this.wavyWavelength, - required this.wavyAmplitudeFactor, - required this.horizontalInset, - required this.circularWaveAmplitudeFactor, - required this.circularWavesPerCircle, - }); -} - -_ProgressMetrics _metricsFor(BuildContext context, ProgressM3EDensity density) { - double cS = 24, cM = 32, cL = 48; - double sS = 3, sM = 4, sL = 6; - double ltS = 3, ltM = 4, ltL = 6; - - if (density == ProgressM3EDensity.compact) { - cS -= 2; cM -= 2; cL -= 4; - sS -= 0.5; sM -= 0.5; sL -= 1; - ltS -= 0.5; ltM -= 0.5; ltL -= 1; - } - - return _ProgressMetrics( - circularSmall: cS, - circularMedium: cM, - circularLarge: cL, - strokeSmall: sS, - strokeMedium: sM, - strokeLarge: sL, - linearThicknessSmall: ltS, - linearThicknessMedium: ltM, - linearThicknessLarge: ltL, - wavyWavelength: 40, // per spec illustration - wavyAmplitudeFactor: 0.33, // amplitude ≈ 1/3 of height - horizontalInset: 4, // 4dp inset L/R - circularWaveAmplitudeFactor: 0.35, // ~1/3 of stroke - circularWavesPerCircle: 10, // a nice default - ); -} - -class ProgressTokensAdapter { - ProgressTokensAdapter(this.context); - final BuildContext context; - - M3ETheme get _m3e { - final t = Theme.of(context); - return t.extension() ?? M3ETheme.defaults(t.colorScheme); - } - - _ProgressMetrics metrics(ProgressM3EDensity density) => _metricsFor(context, density); - - Color color(ProgressM3EEmphasis emphasis) { - switch (emphasis) { - case ProgressM3EEmphasis.primary: - return _m3e.colors.primary; - case ProgressM3EEmphasis.secondary: - return _m3e.colors.secondary; - case ProgressM3EEmphasis.surface: - return _m3e.colors.onSurface; - } - } - - Color trackColor() => _m3e.colors.onSurface.withOpacity(0.12); - Color bufferColor(Color progress) => progress.withOpacity(0.24); - - TextStyle labelStyle() => _m3e.type.bodySmall; -} diff --git a/packages/progress_indicator_m3e/pubspec.yaml b/packages/progress_indicator_m3e/pubspec.yaml index 40f63af..6078e87 100644 --- a/packages/progress_indicator_m3e/pubspec.yaml +++ b/packages/progress_indicator_m3e/pubspec.yaml @@ -1,11 +1,11 @@ name: progress_indicator_m3e -description: Material 3 Expressive Progress Indicator for Flutter (linear + circular; flat + wavy) using M3E tokens. -version: 0.1.0 +description: "Material 3 Expressive progress indicators." +version: 0.3.0 publish_to: none environment: - sdk: ">=3.5.0 <4.0.0" - flutter: ">=3.22.0" + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" dependencies: flutter: diff --git a/packages/progress_indicator_m3e/pubspec_overrides.yaml b/packages/progress_indicator_m3e/pubspec_overrides.yaml deleted file mode 100644 index 69acba6..0000000 --- a/packages/progress_indicator_m3e/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# melos_managed_dependency_overrides: m3e_design -dependency_overrides: - m3e_design: - path: ..\\m3e_design diff --git a/packages/progress_indicator_m3e/test/progress_indicators_m3e_test.dart b/packages/progress_indicator_m3e/test/progress_indicators_m3e_test.dart deleted file mode 100644 index fe2f48b..0000000 --- a/packages/progress_indicator_m3e/test/progress_indicators_m3e_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('placeholder', () { - expect(1 + 2, 3); - }); -}