mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 23:38:50 +02:00
141 lines
3.9 KiB
Dart
141 lines
3.9 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Smooths visual progress changes without increasing backend polling.
|
|
///
|
|
/// The first value is shown immediately so restored downloads never animate
|
|
/// from zero. Later monotonic increases are interpolated locally, while
|
|
/// resets, completion, and disabled animations snap to the authoritative
|
|
/// backend value.
|
|
class SmoothedProgressScope extends StatefulWidget {
|
|
final double value;
|
|
final bool animate;
|
|
final Duration duration;
|
|
final Curve curve;
|
|
final Widget child;
|
|
|
|
const SmoothedProgressScope({
|
|
super.key,
|
|
required this.value,
|
|
required this.child,
|
|
this.animate = true,
|
|
this.duration = const Duration(milliseconds: 900),
|
|
this.curve = Curves.linear,
|
|
});
|
|
|
|
static ValueListenable<double> of(BuildContext context) {
|
|
final scope = context
|
|
.dependOnInheritedWidgetOfExactType<_SmoothedProgressScopeData>();
|
|
assert(scope != null, 'No SmoothedProgressScope found in context');
|
|
return scope!.progress;
|
|
}
|
|
|
|
@override
|
|
State<SmoothedProgressScope> createState() => _SmoothedProgressScopeState();
|
|
}
|
|
|
|
class _SmoothedProgressScopeState extends State<SmoothedProgressScope>
|
|
with SingleTickerProviderStateMixin {
|
|
late final AnimationController _controller;
|
|
late final ValueNotifier<double> _displayedProgress;
|
|
Animation<double>? _animation;
|
|
|
|
double _normalized(double value) => value.clamp(0.0, 1.0).toDouble();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final initialValue = _normalized(widget.value);
|
|
_displayedProgress = ValueNotifier<double>(initialValue);
|
|
_controller = AnimationController(vsync: this, duration: widget.duration)
|
|
..addListener(_updateDisplayedProgress);
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant SmoothedProgressScope oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
_controller.duration = widget.duration;
|
|
|
|
final target = _normalized(widget.value);
|
|
final current = _displayedProgress.value;
|
|
if ((target - current).abs() < 0.0001) {
|
|
return;
|
|
}
|
|
|
|
final shouldSnap =
|
|
!widget.animate ||
|
|
target < _normalized(oldWidget.value) ||
|
|
target <= current ||
|
|
target <= 0 ||
|
|
target >= 1 ||
|
|
widget.duration == Duration.zero;
|
|
if (shouldSnap) {
|
|
_controller.stop();
|
|
_animation = null;
|
|
_displayedProgress.value = target;
|
|
return;
|
|
}
|
|
|
|
_animation = Tween<double>(
|
|
begin: current,
|
|
end: target,
|
|
).animate(CurvedAnimation(parent: _controller, curve: widget.curve));
|
|
_controller.forward(from: 0);
|
|
}
|
|
|
|
void _updateDisplayedProgress() {
|
|
final animation = _animation;
|
|
if (animation != null) {
|
|
_displayedProgress.value = animation.value;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller
|
|
..removeListener(_updateDisplayedProgress)
|
|
..dispose();
|
|
_displayedProgress.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return _SmoothedProgressScopeData(
|
|
progress: _displayedProgress,
|
|
child: widget.child,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SmoothedProgressScopeData extends InheritedWidget {
|
|
final ValueListenable<double> progress;
|
|
|
|
const _SmoothedProgressScopeData({
|
|
required this.progress,
|
|
required super.child,
|
|
});
|
|
|
|
@override
|
|
bool updateShouldNotify(_SmoothedProgressScopeData oldWidget) =>
|
|
progress != oldWidget.progress;
|
|
}
|
|
|
|
/// Rebuilds only the visual fragment that consumes smoothed progress.
|
|
class SmoothedProgressBuilder extends StatelessWidget {
|
|
final Widget? child;
|
|
final Widget Function(BuildContext context, double progress, Widget? child)
|
|
builder;
|
|
|
|
const SmoothedProgressBuilder({super.key, required this.builder, this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ValueListenableBuilder<double>(
|
|
valueListenable: SmoothedProgressScope.of(context),
|
|
builder: builder,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|