|
...
|
...
|
@@ -51,11 +51,27 @@ class _ActivityBurnRingState extends State<ActivityBurnRing> |
|
|
|
_animation = _RingProgressTween(begin: from, end: target).animate(
|
|
|
|
CurvedAnimation(parent: _controller, curve: Curves.easeInOutQuad),
|
|
|
|
);
|
|
|
|
_controller.duration =
|
|
|
|
widget.animate ? const Duration(seconds: 1) : Duration.zero;
|
|
|
|
_controller.duration = _animationDuration(from: from, target: target);
|
|
|
|
_controller.forward(from: 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
Duration _animationDuration({
|
|
|
|
required _RingProgress from,
|
|
|
|
required _RingProgress target,
|
|
|
|
}) {
|
|
|
|
if (!widget.animate) return Duration.zero;
|
|
|
|
|
|
|
|
final visualDelta = _RingProgressTween.maximumVisualDelta(from, target);
|
|
|
|
if (visualDelta == 0) return Duration.zero;
|
|
|
|
|
|
|
|
// A tiny drawable change should not spend a full second creeping forward.
|
|
|
|
final milliseconds = (1000 * visualDelta.clamp(0, 1))
|
|
|
|
.round()
|
|
|
|
.clamp(150, 1000)
|
|
|
|
.toInt();
|
|
|
|
return Duration(milliseconds: milliseconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_controller.dispose();
|
|
...
|
...
|
@@ -116,12 +132,101 @@ class _RingProgress { |
|
|
|
class _RingProgressTween extends Tween<_RingProgress> {
|
|
|
|
_RingProgressTween({required super.begin, required super.end});
|
|
|
|
|
|
|
|
static final _activeMinimumProgress = _minimumProgress(
|
|
|
|
radius: _ActivityBurnRingPainter._outerRadius,
|
|
|
|
width: _ActivityBurnRingPainter._ringWidth,
|
|
|
|
);
|
|
|
|
static final _exerciseMinimumProgress = _minimumProgress(
|
|
|
|
radius: _ActivityBurnRingPainter._middleRadius,
|
|
|
|
width: _ActivityBurnRingPainter._ringWidth,
|
|
|
|
);
|
|
|
|
static final _standMinimumProgress = _minimumProgress(
|
|
|
|
radius: _ActivityBurnRingPainter._innerRadius,
|
|
|
|
width: _ActivityBurnRingPainter._ringWidth,
|
|
|
|
);
|
|
|
|
|
|
|
|
@override
|
|
|
|
_RingProgress lerp(double t) => _RingProgress(
|
|
|
|
active: begin!.active + (end!.active - begin!.active) * t,
|
|
|
|
exercise: begin!.exercise + (end!.exercise - begin!.exercise) * t,
|
|
|
|
stand: begin!.stand + (end!.stand - begin!.stand) * t,
|
|
|
|
active: _lerpProgress(
|
|
|
|
begin!.active,
|
|
|
|
end!.active,
|
|
|
|
t,
|
|
|
|
minimumProgress: _activeMinimumProgress,
|
|
|
|
),
|
|
|
|
exercise: _lerpProgress(
|
|
|
|
begin!.exercise,
|
|
|
|
end!.exercise,
|
|
|
|
t,
|
|
|
|
minimumProgress: _exerciseMinimumProgress,
|
|
|
|
),
|
|
|
|
stand: _lerpProgress(
|
|
|
|
begin!.stand,
|
|
|
|
end!.stand,
|
|
|
|
t,
|
|
|
|
minimumProgress: _standMinimumProgress,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
static double _minimumProgress({
|
|
|
|
required double radius,
|
|
|
|
required double width,
|
|
|
|
}) =>
|
|
|
|
math.asin((width / 2) / radius) / math.pi;
|
|
|
|
|
|
|
|
static double maximumVisualDelta(
|
|
|
|
_RingProgress from,
|
|
|
|
_RingProgress target,
|
|
|
|
) =>
|
|
|
|
math.max(
|
|
|
|
_visualDelta(
|
|
|
|
from.active,
|
|
|
|
target.active,
|
|
|
|
_activeMinimumProgress,
|
|
|
|
),
|
|
|
|
math.max(
|
|
|
|
_visualDelta(
|
|
|
|
from.exercise,
|
|
|
|
target.exercise,
|
|
|
|
_exerciseMinimumProgress,
|
|
|
|
),
|
|
|
|
_visualDelta(
|
|
|
|
from.stand,
|
|
|
|
target.stand,
|
|
|
|
_standMinimumProgress,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
static double _visualDelta(
|
|
|
|
double begin,
|
|
|
|
double end,
|
|
|
|
double minimumProgress,
|
|
|
|
) {
|
|
|
|
if (begin == 0 && end > 0) {
|
|
|
|
return end - math.min(end, minimumProgress);
|
|
|
|
}
|
|
|
|
return (_visibleProgress(begin, minimumProgress) -
|
|
|
|
_visibleProgress(end, minimumProgress))
|
|
|
|
.abs();
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _visibleProgress(double value, double minimumProgress) {
|
|
|
|
if (value <= 0 || value >= 1) return value;
|
|
|
|
return math.max(value, minimumProgress).toDouble();
|
|
|
|
}
|
|
|
|
|
|
|
|
double _lerpProgress(
|
|
|
|
double begin,
|
|
|
|
double end,
|
|
|
|
double t, {
|
|
|
|
required double minimumProgress,
|
|
|
|
}) {
|
|
|
|
if (begin == 0 && end > 0) {
|
|
|
|
final visualStart = math.min(end, minimumProgress);
|
|
|
|
return visualStart + (end - visualStart) * t;
|
|
|
|
}
|
|
|
|
return begin + (end - begin) * t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ActivityBurnRingPainter extends CustomPainter {
|
|
...
|
...
|
@@ -163,7 +268,6 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
center: center,
|
|
|
|
radius: _outerRadius * scale,
|
|
|
|
width: _ringWidth * scale,
|
|
|
|
trackColor: _track,
|
|
|
|
progressColor: _active,
|
|
|
|
progress: progress.active,
|
|
|
|
startAngle: startAngle,
|
|
...
|
...
|
@@ -173,7 +277,6 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
center: center,
|
|
|
|
radius: _middleRadius * scale,
|
|
|
|
width: _ringWidth * scale,
|
|
|
|
trackColor: _innerTrack,
|
|
|
|
progressColor: _exercise,
|
|
|
|
progress: progress.exercise,
|
|
|
|
startAngle: startAngle,
|
|
...
|
...
|
@@ -183,7 +286,6 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
center: center,
|
|
|
|
radius: _innerRadius * scale,
|
|
|
|
width: _ringWidth * scale,
|
|
|
|
trackColor: _center,
|
|
|
|
progressColor: _stand,
|
|
|
|
progress: progress.stand,
|
|
|
|
startAngle: startAngle,
|
|
...
|
...
|
@@ -248,7 +350,6 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
required Offset center,
|
|
|
|
required double radius,
|
|
|
|
required double width,
|
|
|
|
required Color trackColor,
|
|
|
|
required Color progressColor,
|
|
|
|
required double progress,
|
|
|
|
required double startAngle,
|
|
...
|
...
|
@@ -329,6 +430,10 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
|
|
|
|
/// Matches the home-page ring: shrink the butt-capped arc, then add two
|
|
|
|
/// circular caps so that rounded ends never consume the remaining gap.
|
|
|
|
///
|
|
|
|
/// A non-zero arc shorter than the diameter of its caps is rendered at the
|
|
|
|
/// minimum visible length. This avoids the merged cap's center drifting
|
|
|
|
/// clockwise while the value is animated from zero.
|
|
|
|
void _drawPartialRingWithRoundedCaps(
|
|
|
|
Canvas canvas, {
|
|
|
|
required Rect rect,
|
|
...
|
...
|
@@ -337,7 +442,12 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
required double progress,
|
|
|
|
required Paint paint,
|
|
|
|
}) {
|
|
|
|
final sweep = (math.pi * 2 * progress.clamp(0, 1)).toDouble();
|
|
|
|
final visibleProgress = _minimumVisibleProgress(
|
|
|
|
progress: progress,
|
|
|
|
radius: radius,
|
|
|
|
width: paint.strokeWidth,
|
|
|
|
);
|
|
|
|
final sweep = math.pi * 2 * visibleProgress;
|
|
|
|
if (sweep <= 0) return;
|
|
|
|
|
|
|
|
final capAngle = math.asin((paint.strokeWidth / 2) / radius);
|
|
...
|
...
|
@@ -371,11 +481,29 @@ class _ActivityBurnRingPainter extends CustomPainter { |
|
|
|
required double width,
|
|
|
|
required double progress,
|
|
|
|
}) {
|
|
|
|
final sweep = (math.pi * 2 * progress.clamp(0, 1)).toDouble();
|
|
|
|
final visibleProgress = _minimumVisibleProgress(
|
|
|
|
progress: progress,
|
|
|
|
radius: radius,
|
|
|
|
width: width,
|
|
|
|
);
|
|
|
|
final sweep = math.pi * 2 * visibleProgress;
|
|
|
|
final capAngle = math.asin((width / 2) / radius);
|
|
|
|
return startAngle + sweep - math.min(capAngle, sweep / 2).toDouble();
|
|
|
|
}
|
|
|
|
|
|
|
|
double _minimumVisibleProgress({
|
|
|
|
required double progress,
|
|
|
|
required double radius,
|
|
|
|
required double width,
|
|
|
|
}) {
|
|
|
|
final clampedProgress = progress.clamp(0, 1).toDouble();
|
|
|
|
if (clampedProgress <= 0) return 0;
|
|
|
|
|
|
|
|
final capAngle = math.asin((width / 2) / radius);
|
|
|
|
final minimumProgress = capAngle / math.pi;
|
|
|
|
return math.max(clampedProgress, minimumProgress).toDouble();
|
|
|
|
}
|
|
|
|
|
|
|
|
Offset _pointOnRing(Offset center, double radius, double angle) => Offset(
|
|
|
|
center.dx + math.cos(angle) * radius,
|
|
|
|
center.dy + math.sin(angle) * radius,
|
...
|
...
|
|