v2.35 — Vollständiges Sync-System: Playlisten, Favoriten, Auto-Sync, Persistent Login, Benutzerdefinierte Namen
## Server (melo_cloud.py v3.0.0) - Neue DB-Tabellen: user_playlists, user_playlist_songs, user_favorites, user_history, sync_meta - Playlist-Endpunkte: GET/POST /api/cloud/playlists, GET /api/cloud/playlists/<id>, POST songs, DELETE songs, PUT positions - Favoriten-Endpunkte: GET/POST /api/cloud/favorites, POST /api/cloud/favorites/toggle - History-Endpunkte: GET/POST /api/cloud/history (mit Dedup) - Rename-Endpunkt: POST /api/cloud/rename (custom_title/custom_artist) - Sync-Endpunkte: POST /api/cloud/sync-all, GET /api/cloud/sync-status - delete-all erweitert um Playlists + Favorites + History ## App - db_helper.dart: Migration v4→v5 (cloud_id, cloud_title, cloud_artist, server_favorites, sync_metadata) - auth_service.dart: Token-Validierung beim Start (online check + Offline-Fallback), Persistent Login - cloud_service.dart: Neue Methoden für Playlists (CRUD), Favorites (get/sync/toggle), Rename, History, syncAll/syncStatus - cloud_screen.dart: Komplett-Rewrite mit Sync-Modus (Manuell/Auto), Intervall-Auswahl, Sync-Fortschritt-Anzeige, Playlist-Verwaltung, Favoriten-Anzeige + Umbenennen, Sync-Info-Karte, nächster Sync - main.dart: Auto-Sync beim App-Start (nur wenn konfiguriert + fällig) ## Flutter Analyze - 0 neue Issues — nur 7 pre-existing
This commit is contained in:
@@ -20,7 +20,7 @@ class DbHelper {
|
||||
final pfad = await getDatabasesPath();
|
||||
return openDatabase(
|
||||
p.join(pfad, 'melo.db'),
|
||||
version: 4,
|
||||
version: 5,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE songs (
|
||||
@@ -107,6 +107,33 @@ class DbHelper {
|
||||
// Spalte existiert bereits – ignorieren
|
||||
}
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
try {
|
||||
await db.execute('ALTER TABLE songs ADD COLUMN cloud_id TEXT');
|
||||
} catch (_) {}
|
||||
try {
|
||||
await db.execute('ALTER TABLE songs ADD COLUMN cloud_title TEXT');
|
||||
} catch (_) {}
|
||||
try {
|
||||
await db.execute('ALTER TABLE songs ADD COLUMN cloud_artist TEXT');
|
||||
} catch (_) {}
|
||||
try {
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS server_favorites (
|
||||
cloud_id TEXT PRIMARY KEY,
|
||||
favorited_at TEXT
|
||||
)
|
||||
''');
|
||||
} catch (_) {}
|
||||
try {
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS sync_metadata (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)
|
||||
''');
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -356,6 +383,73 @@ class DbHelper {
|
||||
return rows.map((r) => Song.fromMap(r)).toList();
|
||||
}
|
||||
|
||||
/// Cloud-ID für einen Song speichern (Verknüpfung lokal ↔ Cloud)
|
||||
Future<void> cloudIdSetzen(int songId, String cloudId,
|
||||
{String? cloudTitle, String? cloudArtist}) async {
|
||||
final d = await db;
|
||||
final update = <String, dynamic>{'cloud_id': cloudId};
|
||||
if (cloudTitle != null) update['cloud_title'] = cloudTitle;
|
||||
if (cloudArtist != null) update['cloud_artist'] = cloudArtist;
|
||||
await d.update('songs', update, where: 'id = ?', whereArgs: [songId]);
|
||||
}
|
||||
|
||||
/// Sync-Metadaten lesen
|
||||
Future<String?> syncMetaGet(String key) async {
|
||||
final d = await db;
|
||||
final rows = await d.query('sync_metadata',
|
||||
where: 'key = ?', whereArgs: [key]);
|
||||
if (rows.isEmpty) return null;
|
||||
return rows.first['value'] as String?;
|
||||
}
|
||||
|
||||
/// Sync-Metadaten setzen
|
||||
Future<void> syncMetaSet(String key, String value) async {
|
||||
final d = await db;
|
||||
await d.insert('sync_metadata', {'key': key, 'value': value},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
/// Server-Favoriten abrufen (alle cloud_ids)
|
||||
Future<Set<String>> serverFavoritesGet() async {
|
||||
final d = await db;
|
||||
final rows = await d.query('server_favorites');
|
||||
return rows.map((r) => r['cloud_id'] as String).toSet();
|
||||
}
|
||||
|
||||
/// Server-Favoriten ersetzen (komplette Liste)
|
||||
Future<void> serverFavoritesSet(List<String> cloudIds) async {
|
||||
final d = await db;
|
||||
await d.transaction((txn) async {
|
||||
await txn.delete('server_favorites');
|
||||
final now = DateTime.now().toIso8601String();
|
||||
for (final cid in cloudIds) {
|
||||
await txn.insert('server_favorites',
|
||||
{'cloud_id': cid, 'favorited_at': now});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Song per cloud_id finden
|
||||
Future<Song?> songNachCloudId(String cloudId) async {
|
||||
final d = await db;
|
||||
final rows = await d.query('songs',
|
||||
where: 'cloud_id = ?', whereArgs: [cloudId]);
|
||||
if (rows.isEmpty) return null;
|
||||
return Song.fromMap(rows.first);
|
||||
}
|
||||
|
||||
/// Benutzerdefinierte Metadaten für Cloud-Song aktualisieren
|
||||
Future<void> cloudMetadatenAktualisieren(int songId,
|
||||
{String? title, String? artist}) async {
|
||||
final d = await db;
|
||||
final update = <String, dynamic>{};
|
||||
if (title != null) update['cloud_title'] = title;
|
||||
if (artist != null) update['cloud_artist'] = artist;
|
||||
if (update.isNotEmpty) {
|
||||
await d.update('songs', update, where: 'id = ?', whereArgs: [songId]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loeschen() async {
|
||||
final d = await db;
|
||||
await d.transaction((txn) async {
|
||||
|
||||
Reference in New Issue
Block a user