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:
@@ -401,11 +401,13 @@ class DbHelper {
|
||||
|
||||
Future<void> songZurPlaylist(int playlistId, int songId, int position) async {
|
||||
final d = await db;
|
||||
// INSERT OR IGNORE: Song bereits in der Playlist (Race/Doppel-Tap) ist
|
||||
// kein Fehler — der Eintrag bleibt einfach bestehen (PK-Konflikt abgefangen).
|
||||
await d.insert('playlist_songs', {
|
||||
'playlist_id': playlistId,
|
||||
'song_id': songId,
|
||||
'position': position,
|
||||
});
|
||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> allePlaylists() async {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../database/db_helper.dart';
|
||||
import '../models/song.dart';
|
||||
import 'melo_logger.dart';
|
||||
|
||||
class FavoritenService {
|
||||
static final FavoritenService _instanz = FavoritenService._();
|
||||
@@ -27,13 +29,26 @@ class FavoritenService {
|
||||
return songs.any((s) => s.id == songId);
|
||||
}
|
||||
|
||||
Future<void> umschalten(int songId) async {
|
||||
if (_favoritenPlaylistId == null) return;
|
||||
/// Schaltet den Favoriten-Status um und gibt den NEUEN Status zurück
|
||||
/// (true = jetzt Favorit). Bei Fehlern (z. B. DB-Race) wird der Zustand
|
||||
/// aus der DB neu geladen statt blind geflippt — kein unhandled Throw.
|
||||
Future<bool> umschalten(int songId) async {
|
||||
if (_favoritenPlaylistId == null) return false;
|
||||
try {
|
||||
if (await istFavorit(songId)) {
|
||||
await _db.songAusPlaylistEntfernen(_favoritenPlaylistId!, songId);
|
||||
return false;
|
||||
} else {
|
||||
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
|
||||
// INSERT OR IGNORE in songZurPlaylist fängt Doppel-Tap-Races ab
|
||||
await _db.songZurPlaylist(_favoritenPlaylistId!, songId, songs.length);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Favoriten-Toggle fehlgeschlagen: $e');
|
||||
MeloLogger().fehler('favoriten_toggle', e);
|
||||
// Zustand nach Fehler aus der DB synchronisieren (Quelle der Wahrheit)
|
||||
return istFavorit(songId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user