Update pubspec dependencies, refactor progress indicators, and enhance README documentation
This commit is contained in:
parent
020db0ac38
commit
687bca8817
18 changed files with 1013 additions and 916 deletions
|
|
@ -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';
|
||||
|
|
|
|||
79
packages/progress_indicator_m3e/lib/src/_tokens.dart
Normal file
79
packages/progress_indicator_m3e/lib/src/_tokens.dart
Normal file
|
|
@ -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,
|
||||
),
|
||||
};
|
||||
|
|
@ -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<CircularProgressM3E> createState() => _CircularProgressM3EState();
|
||||
}
|
||||
|
||||
class _CircularProgressM3EState extends State<CircularProgressM3E>
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<LinearProgressM3E> createState() => _LinearProgressM3EState();
|
||||
}
|
||||
|
||||
class _LinearProgressM3EState extends State<LinearProgressM3E>
|
||||
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>() ?? 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = <Widget>[
|
||||
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 = <Widget>[
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>() ?? 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue