Navidrome-Integration, Widgets (Recent + Tag-Statistik)

This commit is contained in:
Hermes (Server)
2026-07-23 22:23:28 +02:00
parent 94a4063070
commit c7828b851c
16 changed files with 1434 additions and 158 deletions
+209
View File
@@ -0,0 +1,209 @@
import 'package:flutter/foundation.dart';
import '../database/db_helper.dart';
import '../services/player_service.dart';
import '../services/musik_scanner.dart';
import '../services/favoriten_service.dart';
import '../services/download_service.dart';
import '../services/navidrome_service.dart';
import '../services/playlist_service.dart';
import '../models/song.dart';
import '../models/tag.dart';
class MeloHomeViewModel extends ChangeNotifier {
final PlayerService player = PlayerService();
final DbHelper db = DbHelper();
final FavoritenService favoriten = FavoritenService();
final MusikScanner scanner = MusikScanner();
final DownloadService downloader = DownloadService();
final NavidromeService navidrome = NavidromeService();
final PlaylistService playlists = PlaylistService();
List<Song> songs = [];
Set<String> aktiveTags = {};
bool ladt = true;
Set<int> favoritenIds = {};
List<Map<String, String>> tags = [];
List<Song> letzteSongs = [];
Map<String, int> tagCounts = {};
bool zeigeBotschaft = false;
bool serverLadt = false;
List<SubsonicAlbum> navidromeAlben = [];
int _playCount = 0;
static const int _botschaftSchwellwert = 10;
static const _defaultTags = [
{'name': 'Alle', 'icon': ''},
{'name': '❤️ Für uns', 'icon': '❤️'},
{'name': 'Nightcore', 'icon': ''},
{'name': 'Traurig', 'icon': '😢'},
{'name': 'Party', 'icon': '🎉'},
{'name': 'Mitsingen', 'icon': '🎤'},
{'name': '2000er', 'icon': '📀'},
];
List<Song> get gefilterteSongs {
if (aktiveTags.isEmpty) return songs;
return songs.where((s) =>
aktiveTags.any((tag) =>
s.titel.contains(tag) || s.kuenstler.contains(tag))
).toList();
}
Future<void> ladeSongs() async {
ladt = true;
notifyListeners();
var alle = await db.alleSongs();
if (alle.isEmpty) {
final beispiele = [
Song(titel: 'Leichtes Gepäck', kuenstler: 'Silbermond', album: 'Schritte',
dauerSekunden: 232, dateiPfad: '', groesseBytes: 5242880, istHeruntergeladen: true),
Song(titel: 'Auf uns', kuenstler: 'Andreas Bourani', album: 'Hey',
dauerSekunden: 241, dateiPfad: '', groesseBytes: 4819200, istHeruntergeladen: true),
Song(titel: '80 Millionen', kuenstler: 'Max Giesinger', album: 'Der Junge',
dauerSekunden: 225, dateiPfad: '', groesseBytes: 5107200, istHeruntergeladen: true),
Song(titel: 'Tage wie diese', kuenstler: 'Die Toten Hosen', album: 'Ballast',
dauerSekunden: 252, dateiPfad: '', groesseBytes: 4300800, istHeruntergeladen: true),
Song(titel: 'Atlantis', kuenstler: 'Frida Gold', album: 'Liebe',
dauerSekunden: 228, dateiPfad: '', groesseBytes: 3987200, istHeruntergeladen: true),
];
await db.songsEinfuegen(beispiele);
alle = await db.alleSongs();
}
songs = alle;
favoritenIds = await favoriten.favoritenIds();
tagCounts = _berechneTagCounts();
letzteSongs = await db.letzteWiedergaben();
await ladeTags();
ladt = false;
notifyListeners();
}
Map<String, int> _berechneTagCounts() {
final counts = <String, int>{};
for (final s in songs) {
for (final t in tags) {
final name = t['name']!;
if (name == 'Alle') continue;
if (s.titel.contains(name) || s.kuenstler.contains(name)) {
counts[name] = (counts[name] ?? 0) + 1;
}
}
}
return counts;
}
Future<void> ladeTags() async {
var dbTags = await db.alleTags();
if (dbTags.isEmpty) {
for (final t in _defaultTags) {
if (t['name'] != 'Alle') {
await db.tagErstellen(t['name']!, icon: t['icon']);
}
}
dbTags = await db.alleTags();
}
tags = [{'name': 'Alle', 'icon': ''}, ...dbTags.map((t) => t.toDisplay())];
// Tag-Counts nachladen wenn Tags geladen
tagCounts = _berechneTagCounts();
}
void spieleSong(Song song) {
player.setWarteschlange(songs,
startIndex: songs.indexWhere((s) => s.id == song.id));
player.spiele(song);
// In Verlauf speichern
if (song.id != null) {
db.positionAktualisieren(song.id!, 0);
}
// Play-Counter für Hidden Message
_playCount++;
if (_playCount >= _botschaftSchwellwert && !zeigeBotschaft) {
zeigeBotschaft = true;
notifyListeners();
}
// Letzte Songs aktualisieren
db.letzteWiedergaben().then((liste) {
letzteSongs = liste;
notifyListeners();
});
}
/// Manuell die Botschaft "Seit 2008" anzeigen
void botschaftAnzeigen() {
zeigeBotschaft = true;
notifyListeners();
}
/// Botschaft ausblenden
void botschaftAusblenden() {
zeigeBotschaft = false;
notifyListeners();
}
void toggleTag(String tag) {
if (tag == 'Alle') {
aktiveTags.clear();
} else if (aktiveTags.contains(tag)) {
aktiveTags.remove(tag);
} else {
aktiveTags.add(tag);
}
notifyListeners();
}
Future<void> favoritenUmschalten(Song song) async {
if (song.id == null) return;
await favoriten.umschalten(song.id!);
favoritenIds = await favoriten.favoritenIds();
notifyListeners();
}
// ─── Navidrome Server-Sync ─────────────────────────
/// Navidrome-Zugangsdaten setzen
void verbindeNavidrome(String url, String user, String password) {
navidrome.setCredentials(url, user, password);
}
/// Verbindung testen und Alben laden
Future<bool> ladeNavidromeAlben() async {
if (!navidrome.istVerbunden) return false;
serverLadt = true;
notifyListeners();
final ok = await navidrome.ping();
if (ok) {
navidromeAlben = await navidrome.getAlben();
}
serverLadt = false;
notifyListeners();
return ok;
}
/// Songs eines Albums laden
Future<List<SubsonicSong>> ladeAlbumSongs(String albumId) async {
return navidrome.getSongs(albumId);
}
/// Song von Navidrome runterladen
Future<bool> downloadNavidromeSong(SubsonicSong s) async {
final song = await navidrome.downloadSong(s);
if (song != null) {
await ladeSongs();
return true;
}
return false;
}
@override
void dispose() {
player.dispose();
super.dispose();
}
}