v2.49.4 — Favoriten-Toggle race-sicher (kein PK-Crash bei Doppel-Tap)

## Fix (Code-Review Finding 2, HIGH)
- DbHelper.songZurPlaylist: INSERT OR IGNORE (ConflictAlgorithm.ignore) —
  Doppel-Tap-Race verletzt PRIMARY KEY (playlist_id, song_id) nicht mehr
- FavoritenService.umschalten: gibt neuen Status zurück, try/catch mit
  MeloLogger, Zustand nach Fehler aus DB neu geladen statt blind geflippt
- NowPlayingScreen._toggleFavorit: In-Flight-Guard _toggleLaeuft gegen
  parallele Toggles + Statusübernahme aus DB (Quelle der Wahrheit)
This commit is contained in:
Dustin
2026-08-04 16:46:49 +02:00
parent c6826db651
commit bd692d4682
3 changed files with 41 additions and 10 deletions
+16 -2
View File
@@ -6,6 +6,7 @@ import 'package:just_audio/just_audio.dart';
import '../models/song.dart';
import '../services/favoriten_service.dart';
import '../services/lyrics_service.dart';
import '../services/melo_logger.dart';
import '../services/player_service.dart';
import '../utils/farb_theme.dart';
import '../widgets/warteschlange_sheet.dart';
@@ -48,6 +49,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen> {
// ─── Favoriten (Like) ───
bool _istFavorit = false;
bool _toggleLaeuft = false; // In-Flight-Guard gegen Doppel-Tap-Race
@override
void initState() {
@@ -136,12 +138,24 @@ class _NowPlayingScreenState extends State<NowPlayingScreen> {
}
Future<void> _toggleFavorit() async {
if (_toggleLaeuft) return; // zweiter Tap während laufendem Toggle ignorieren
final song = _player.aktuellerSong;
final id = song?.id;
if (song == null || id == null) return;
await _favoriten.umschalten(id);
_toggleLaeuft = true;
bool neuerStatus;
try {
neuerStatus = await _favoriten.umschalten(id);
} catch (e) {
debugPrint('Favoriten-Toggle fehlgeschlagen: $e');
MeloLogger().fehler('favoriten_toggle_ui', e);
neuerStatus = _istFavorit; // unverändert lassen
} finally {
_toggleLaeuft = false;
}
if (!mounted) return;
setState(() => _istFavorit = !_istFavorit);
// Zustand aus der DB (Quelle der Wahrheit) übernehmen statt blind zu flippen
setState(() => _istFavorit = neuerStatus);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_istFavorit