122 lines
4.4 KiB
Dart
122 lines
4.4 KiB
Dart
import 'dart:async';
|
|
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._();
|
|
factory FavoritenService() => _instanz;
|
|
FavoritenService._();
|
|
|
|
final DbHelper _db = DbHelper();
|
|
int? _favoritenPlaylistId;
|
|
|
|
/// Wird nach jedem erfolgreichen lokalen Toggle aufgerufen (Feuer-und-
|
|
/// Vergessen → Server-Push). Wird in `main()` verdrahtet; Fehler werden
|
|
/// beim nächsten vollständigen Sync korrigiert (favoritenMerge-Vereinigung).
|
|
Future<void> Function(int songId, bool neuerStatus)? onStatusGeaendert;
|
|
|
|
int? get favoritenId => _favoritenPlaylistId;
|
|
|
|
Future<void> init() async {
|
|
final playlists = await _db.allePlaylists();
|
|
final vorhanden = playlists.where((p) => p['name'] == '⭐ Favoriten').toList();
|
|
if (vorhanden.isNotEmpty) {
|
|
_favoritenPlaylistId = vorhanden.first['id'] as int;
|
|
} else {
|
|
_favoritenPlaylistId = await _db.playlistErstellen('⭐ Favoriten');
|
|
}
|
|
}
|
|
|
|
Future<bool> istFavorit(int songId) async {
|
|
if (_favoritenPlaylistId == null) return false;
|
|
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
|
|
return songs.any((s) => s.id == songId);
|
|
}
|
|
|
|
/// 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.
|
|
/// Nach dem lokalen Toggle wird [onStatusGeaendert] Feuer-und-Vergessen
|
|
/// aufgerufen (Server-Push; Retry = nächster vollständiger Sync).
|
|
Future<bool> umschalten(int songId) async {
|
|
if (_favoritenPlaylistId == null) return false;
|
|
try {
|
|
bool neuerStatus;
|
|
if (await istFavorit(songId)) {
|
|
await _db.songAusPlaylistEntfernen(_favoritenPlaylistId!, songId);
|
|
neuerStatus = 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);
|
|
neuerStatus = true;
|
|
}
|
|
final cb = onStatusGeaendert;
|
|
if (cb != null) {
|
|
unawaited(() async {
|
|
try {
|
|
await cb(songId, neuerStatus);
|
|
} catch (e) {
|
|
MeloLogger().fehler('favoriten_server_push', e);
|
|
}
|
|
}());
|
|
}
|
|
return neuerStatus;
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
Future<List<Song>> alleFavoriten() async {
|
|
if (_favoritenPlaylistId == null) return [];
|
|
return _db.songsDerPlaylist(_favoritenPlaylistId!);
|
|
}
|
|
|
|
/// Cloud-IDs aller lokalen ⭐-Favoriten (Push-Richtung lokal → Server).
|
|
/// Songs ohne cloud_id (nicht hochgeladen) werden übersprungen.
|
|
Future<Set<String>> favoritenCloudIds() async {
|
|
if (_favoritenPlaylistId == null) return {};
|
|
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
|
|
return songs
|
|
.map((s) => s.cloudId)
|
|
.whereType<String>()
|
|
.where((c) => c.isNotEmpty)
|
|
.toSet();
|
|
}
|
|
|
|
/// Markiert einen Server-Favoriten (cloud_id) lokal als ⭐-Favorit
|
|
/// (Richtung Server → lokal). Fehlt der Song lokal, passiert nichts —
|
|
/// der Favorit bleibt in `server_favorites` fürs Dashboard erhalten.
|
|
Future<void> merkeCloudFavorit(String cloudId) async {
|
|
if (_favoritenPlaylistId == null) return;
|
|
final song = await _db.songNachCloudId(cloudId);
|
|
final id = song?.id;
|
|
if (id == null) return;
|
|
if (await istFavorit(id)) return;
|
|
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
|
|
await _db.songZurPlaylist(_favoritenPlaylistId!, id, songs.length);
|
|
}
|
|
|
|
Future<int> anzahlFavoriten() async {
|
|
if (_favoritenPlaylistId == null) await init();
|
|
if (_favoritenPlaylistId == null) return 0;
|
|
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
|
|
return songs.length;
|
|
}
|
|
|
|
Future<Set<int>> favoritenIds() async {
|
|
if (_favoritenPlaylistId == null) return {};
|
|
final d = await _db.db;
|
|
final rows = await d.rawQuery(
|
|
'SELECT song_id FROM playlist_songs WHERE playlist_id = ?',
|
|
[_favoritenPlaylistId],
|
|
);
|
|
return rows.map((r) => r['song_id'] as int).toSet();
|
|
}
|
|
}
|