import 'dart:async'; import 'package:flutter/foundation.dart'; import 'categories.dart'; import 'database.dart'; /// Hält die Kategorien aller Songs und das daraus abgeleitete Cover je /// Kategorie an einer Stelle — sonst müsste jede Liederliste beide Streams /// selbst beobachten und die Cover-Zuordnung könnte je Liste abweichen. class CategoryService extends ChangeNotifier { CategoryService(this._db) { _songs = _db.watchSongs().listen((songs) { _allSongs = songs; _recompute(); }); _categories = _db.watchCategoriesBySong().listen((byId) { _byId = byId; _recompute(); }); } final MeloDb _db; late final StreamSubscription> _songs; late final StreamSubscription>> _categories; List _allSongs = const []; Map> _byId = const {}; Map _covers = const {}; /// Kategorien eines Songs in gespeicherter Reihenfolge. List of(String songId) => _byId[songId] ?? const []; /// Das anzuzeigende Cover eines Songs unter Berücksichtigung der /// Einstellung "Gleiche Kategorie = gleiches Coverbild". String? coverFor(Song song, {required bool groupByCategory}) { return effectiveCover(song, of(song.id), _covers, groupByCategory: groupByCategory); } Future setCategories(String songId, List names) => _db.setCategories(songId, names, byUser: true); Future> allNames() => _db.allCategoryNames(); /// Alle vergebenen Kategorien, alphabetisch — aus dem ohnehin beobachteten /// Bestand, ohne zusätzliche Abfrage. Für Oberflächen, die die Liste beim /// Bauen brauchen und nicht auf eine Antwort warten sollen. List get alleNamen { final namen = {}; for (final liste in _byId.values) { for (final name in liste) { namen.putIfAbsent(name.toLowerCase(), () => name); } } final sortiert = namen.keys.toList()..sort(); return [for (final k in sortiert) namen[k]!]; } void _recompute() { _covers = categoryCovers(_allSongs, _byId); notifyListeners(); } @override void dispose() { _songs.cancel(); _categories.cancel(); super.dispose(); } }