mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 13:19:44 +02:00
feat(lyrics): animate live previews while scrubbing playback
This commit is contained in:
1 parent
55a390d679
commit
8e7e71827c
6 files changed
+639
-113
No files matched your search
@@ -11,16 +11,18 @@ class LyricGapIndicator extends ConsumerWidget {
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.color,
|
||||
this.position,
|
||||
});
|
||||
|
||||
final Duration start;
|
||||
final Duration end;
|
||||
final Color color;
|
||||
final Duration? position;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final progress = syncedLyricSegmentProgress(
|
||||
position: ref.watch(playbackPositionProvider),
|
||||
position: position ?? ref.watch(playbackPositionProvider),
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/utils/playback_seek_preview.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_player_slider.dart';
|
||||
|
||||
/// Previews scrubbing locally and seeks once when the gesture ends.
|
||||
@@ -7,12 +8,14 @@ class PlaybackSeekSlider extends StatefulWidget {
|
||||
final Duration position;
|
||||
final Duration duration;
|
||||
final Future<void> Function(Duration) onSeek;
|
||||
final PlaybackSeekPreview? preview;
|
||||
|
||||
const PlaybackSeekSlider({
|
||||
super.key,
|
||||
required this.position,
|
||||
required this.duration,
|
||||
required this.onSeek,
|
||||
this.preview,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -22,18 +25,53 @@ class PlaybackSeekSlider extends StatefulWidget {
|
||||
class _PlaybackSeekSliderState extends State<PlaybackSeekSlider> {
|
||||
double? _previewMs;
|
||||
int _gestureGeneration = 0;
|
||||
Object? _previewSession;
|
||||
|
||||
void _start(double value) {
|
||||
_gestureGeneration++;
|
||||
_previewSession = widget.preview?.begin(
|
||||
Duration(milliseconds: value.round()),
|
||||
);
|
||||
}
|
||||
|
||||
void _preview(double value) {
|
||||
setState(() => _previewMs = value);
|
||||
final session = _previewSession;
|
||||
if (session != null) {
|
||||
widget.preview?.update(session, Duration(milliseconds: value.round()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _adjust(double value) {
|
||||
_start(value);
|
||||
_preview(value);
|
||||
return _commit(value);
|
||||
}
|
||||
|
||||
Future<void> _commit(double value) async {
|
||||
final generation = _gestureGeneration;
|
||||
final preview = widget.preview;
|
||||
final session = _previewSession;
|
||||
try {
|
||||
await widget.onSeek(Duration(milliseconds: value.round()));
|
||||
} finally {
|
||||
preview?.end(session);
|
||||
if (mounted && generation == _gestureGeneration) {
|
||||
setState(() => _previewMs = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
final preview = widget.preview;
|
||||
final session = _previewSession;
|
||||
// Rotation or track replacement can remove this slider during layout.
|
||||
// Notify surviving lyrics/time labels after that frame has finished.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => preview?.end(session));
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final durationMs = widget.duration.inMilliseconds;
|
||||
@@ -58,10 +96,10 @@ class _PlaybackSeekSliderState extends State<PlaybackSeekSlider> {
|
||||
? percentage((currentMs - 5000).clamp(0, maxMs).toDouble())
|
||||
: null,
|
||||
onIncrease: enabled
|
||||
? () => _commit((currentMs + 5000).clamp(0, maxMs).toDouble())
|
||||
? () => _adjust((currentMs + 5000).clamp(0, maxMs).toDouble())
|
||||
: null,
|
||||
onDecrease: enabled
|
||||
? () => _commit((currentMs - 5000).clamp(0, maxMs).toDouble())
|
||||
? () => _adjust((currentMs - 5000).clamp(0, maxMs).toDouble())
|
||||
: null,
|
||||
child: IgnorePointer(
|
||||
ignoring: !enabled,
|
||||
@@ -72,8 +110,8 @@ class _PlaybackSeekSliderState extends State<PlaybackSeekSlider> {
|
||||
SliderTheme.of(context).inactiveTrackColor ?? Colors.white12,
|
||||
value: enabled ? currentMs : 0,
|
||||
max: maxMs,
|
||||
onChangeStart: (_) => _gestureGeneration++,
|
||||
onChanged: (value) => setState(() => _previewMs = value),
|
||||
onChangeStart: _start,
|
||||
onChanged: _preview,
|
||||
onChangeEnd: _commit,
|
||||
),
|
||||
),
|
||||
@@ -88,8 +126,8 @@ class _PlaybackSeekSliderState extends State<PlaybackSeekSlider> {
|
||||
)
|
||||
: 0,
|
||||
max: maxMs,
|
||||
onChangeStart: enabled ? (_) => _gestureGeneration++ : null,
|
||||
onChanged: enabled ? (value) => setState(() => _previewMs = value) : null,
|
||||
onChangeStart: enabled ? _start : null,
|
||||
onChanged: enabled ? _preview : null,
|
||||
onChangeEnd: enabled ? _commit : null,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user