From 70a79520d0a0384bb98f6e9ed90c8bd750d55099 Mon Sep 17 00:00:00 2001 From: Dustin Date: Wed, 5 Aug 2026 21:04:18 +0200 Subject: [PATCH] =?UTF-8?q?v2.54.2=20=E2=80=94=20Review-Fixes=20(Graph):?= =?UTF-8?q?=20Shuffle-Duplikat=20bei=20Einzel-Song+Repeat,=20Speed-Chip=20?= =?UTF-8?q?1.75x,=20Drag-Race,=20Speed-Tap-Guard,=20Version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 2 +- lib/screens/now_playing_screen.dart | 46 +++++++++++++++++++++-------- lib/services/player_service.dart | 3 ++ test/player_service_test.dart | 14 +++++++++ 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 066f309..5da0e7a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); // Logger startet sofort – zeichnet ALLES auf - MeloLogger().init('2.54'); + MeloLogger().init('2.54.1'); // ── Notifications initialisieren (Issue #9) ── const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); diff --git a/lib/screens/now_playing_screen.dart b/lib/screens/now_playing_screen.dart index 6965e55..979ffe3 100644 --- a/lib/screens/now_playing_screen.dart +++ b/lib/screens/now_playing_screen.dart @@ -92,6 +92,9 @@ class _NowPlayingScreenState extends State _zeigeLyrics = false; _lyrics = null; _lyricsLaden = true; + // Drag-Position zurücksetzen: Sonst seekt onChangeEnd den NEUEN + // Song auf die alte Position des vorherigen Songs (Race). + _dragPosition = null; // Cover-Zustand SYNCHRON zurücksetzen: Sonst rendern Hintergrund // (und Front-Cover) beim Songwechsel kurz das ALTE Cover, bis die // async Existenz-Prüfung durch ist — der Switcher-Key (song.id) @@ -1015,24 +1018,43 @@ class _NowPlayingScreenState extends State } /// Gängige Geschwindigkeitsstufen — Tipp im Fullscreen wechselt durch. + /// Muss mit dem Settings-Slider (0.25er-Schritte) übereinstimmen, sonst + /// springt indexOf(-1) auf 1.0 (Reset) statt zur nächsten Stufe. static const List _geschwindigkeiten = [ - 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, + 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, ]; + bool _speedTapLaeuft = false; // In-Flight-Guard gegen Doppel-Tap-Race + Future _naechsteGeschwindigkeit() async { - final aktuell = _player.geschwindigkeit; - final idx = _geschwindigkeiten.indexOf(aktuell); - final neu = idx >= 0 - ? _geschwindigkeiten[(idx + 1) % _geschwindigkeiten.length] - : 1.0; - // Wie in den Einstellungen persistieren, damit die Wahl erhalten bleibt + if (_speedTapLaeuft) return; + _speedTapLaeuft = true; try { - final p = await SharedPreferences.getInstance(); - await p.setDouble('playback_speed', neu); - } catch (e) { - debugPrint('Geschwindigkeit persistieren fehlgeschlagen: $e'); + final aktuell = _player.geschwindigkeit; + var idx = _geschwindigkeiten.indexOf(aktuell); + if (idx < 0) { + // Wert außerhalb der Liste (z.B. 1.75 vor dem Fix): nächste + // höhere Stufe nehmen, sonst letzte + idx = _geschwindigkeiten.length - 1; + for (var i = 0; i < _geschwindigkeiten.length; i++) { + if (_geschwindigkeiten[i] > aktuell) { + idx = i; + break; + } + } + } + final neu = _geschwindigkeiten[(idx + 1) % _geschwindigkeiten.length]; + // Wie in den Einstellungen persistieren, damit die Wahl erhalten bleibt + try { + final p = await SharedPreferences.getInstance(); + await p.setDouble('playback_speed', neu); + } catch (e) { + debugPrint('Geschwindigkeit persistieren fehlgeschlagen: $e'); + } + await _player.setGeschwindigkeit(neu); + } finally { + _speedTapLaeuft = false; } - await _player.setGeschwindigkeit(neu); } String _formatZeit(Duration d) { diff --git a/lib/services/player_service.dart b/lib/services/player_service.dart index e4e923d..1863e33 100644 --- a/lib/services/player_service.dart +++ b/lib/services/player_service.dart @@ -358,6 +358,9 @@ class PlayerService extends ChangeNotifier { final start = _shufflePos >= 0 ? _shufflePos + 1 : 0; for (var i = start; i < _shuffleReihenfolge.length; i++) { final idx = _shuffleReihenfolge[i]; + // Duplikat vermeiden: den laufenden Song überspringen, wenn er in + // der Shuffle-Reihenfolge auftaucht (Einzel-Song + Repeat-playlist) + if (idx == _aktuellerIndex) continue; if (idx >= 0 && idx < _warteschlange.length) { ergebnis.add(_warteschlange[idx]); } diff --git a/test/player_service_test.dart b/test/player_service_test.dart index b73fb68..808615c 100644 --- a/test/player_service_test.dart +++ b/test/player_service_test.dart @@ -250,6 +250,20 @@ void main() { expect(p.wiedergabeReihenfolge.map((s) => s.id).toList(), [1]); }); + test('shuffle + Repeat-playlist + Einzel-Song-Queue: KEIN Duplikat', () { + final p = PlayerService(); + p.setWarteschlange([_song(1, 'A')]); + p.setZufallsmodus(true); + p.setWiederholmodus(Wiederholmodus.playlist); + // _shufflePos == -1 (Shuffle gerade eingeschaltet): der laufende Song + // wird separat gelistet — darf NICHT zusätzlich aus der + // Shuffle-Reihenfolge kommen (Regression: [A, A]) + final reihenfolge = p.wiedergabeReihenfolge.map((s) => s.id).toList(); + expect(reihenfolge, [1]); + expect(reihenfolge.toSet().length, reihenfolge.length, + reason: 'Keine Duplikate in der Wiedergabe-Reihenfolge'); + }); + test('verschieben (normal): ändert Wiedergabe-Reihenfolge und Queue', () { final p = PlayerService(); p.setWarteschlange(