| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import 'package:flutter/foundation.dart';
- import '../../data/models/jellyfin_library.dart';
- import '../../data/models/jellyfin_models.dart';
- import '../../data/services/jellyfin_library_service.dart';
- import '../settings/jellyfin_controller.dart';
- /// Loads the three dashboard rows and owns the Random row's re-roll. No library
- /// data is cached across dashboard openings; the server is the source of truth.
- class JellyfinDashboardController extends ChangeNotifier {
- JellyfinDashboardController({required this.library, required this.jellyfin});
- /// The wide layout can show six albums per row; the server is asked for the
- /// maximum and the screen hides the extra card when only five fit.
- static const maxAlbumsPerRow = 6;
- final JellyfinLibraryService library;
- final JellyfinController jellyfin;
- List<JellyfinAlbum> _latestPlayed = const [];
- List<JellyfinAlbum> _latestAdded = const [];
- List<JellyfinAlbum> _random = const [];
- List<JellyfinAlbum> _favorites = const [];
- bool _loading = false;
- bool _randomLoading = false;
- bool _favoritesLoading = false;
- bool _hasLoaded = false;
- bool _favoritesLoaded = false;
- bool _disposed = false;
- String? _error;
- String? _randomError;
- String? _favoritesError;
- List<JellyfinAlbum> get latestPlayed => _latestPlayed;
- List<JellyfinAlbum> get latestAdded => _latestAdded;
- List<JellyfinAlbum> get random => _random;
- List<JellyfinAlbum> get favorites => _favorites;
- bool get loading => _loading;
- bool get randomLoading => _randomLoading;
- bool get favoritesLoading => _favoritesLoading;
- bool get hasLoaded => _hasLoaded;
- bool get favoritesLoaded => _favoritesLoaded;
- String? get error => _error;
- String? get randomError => _randomError;
- String? get favoritesError => _favoritesError;
- bool get hasGenres => jellyfin.genreSelections.isNotEmpty;
- List<JellyfinGenreSelection> get selections => jellyfin.genreSelections;
- Future<void> load() async {
- if (_disposed || _loading) return;
- if (!library.isConfigured) {
- _hasLoaded = true;
- _error = 'Pair Jellyfin first.';
- _notify();
- return;
- }
- _loading = true;
- _error = null;
- _randomError = null;
- _notify();
- final latestFuture = Future.wait([
- library.latestPlayed(limit: maxAlbumsPerRow),
- library.latestAdded(limit: maxAlbumsPerRow),
- ]);
- final randomFuture = _randomForSelections();
- try {
- final latest = await latestFuture;
- if (_disposed) return;
- _latestPlayed = latest[0];
- _latestAdded = latest[1];
- } catch (error) {
- if (_disposed) return;
- _error = _messageFor(error);
- }
- try {
- final random = await randomFuture;
- if (_disposed) return;
- _random = random;
- } catch (error) {
- if (_disposed) return;
- _randomError = _messageFor(error);
- _random = const [];
- }
- _loading = false;
- _hasLoaded = true;
- _notify();
- }
- Future<void> retry() => load();
- Future<void> loadFavorites() async {
- if (_disposed || _favoritesLoading) return;
- if (!library.isConfigured) {
- _favorites = const [];
- _favoritesError = 'Pair Jellyfin first.';
- _favoritesLoaded = true;
- _notify();
- return;
- }
- _favoritesLoading = true;
- _favoritesError = null;
- _notify();
- try {
- final favorites = await library.favoriteAlbums();
- if (_disposed) return;
- _favorites = favorites;
- _favoritesLoaded = true;
- } catch (error) {
- if (_disposed) return;
- _favoritesError = _messageFor(error);
- _favorites = const [];
- _favoritesLoaded = true;
- } finally {
- if (!_disposed) {
- _favoritesLoading = false;
- _notify();
- }
- }
- }
- Future<void> retryFavorites() => loadFavorites();
- Future<void> shuffle() async {
- if (_disposed || _randomLoading) return;
- if (jellyfin.genreSelections.isEmpty) {
- _random = const [];
- _randomError = null;
- _notify();
- return;
- }
- _randomLoading = true;
- _randomError = null;
- _notify();
- try {
- final random = await _randomForSelections();
- if (_disposed) return;
- _random = random;
- } catch (error) {
- if (_disposed) return;
- _randomError = _messageFor(error);
- _random = const [];
- } finally {
- if (!_disposed) {
- _randomLoading = false;
- _notify();
- }
- }
- }
- /// Moves a started album to the front of Recently played until the next
- /// server refresh. Keeps the dashboard useful immediately after a tap.
- void markPlayed(JellyfinAlbum album) {
- if (_disposed) return;
- final next = [
- album,
- ..._latestPlayed.where((entry) => entry.id != album.id),
- ];
- _latestPlayed = next.take(maxAlbumsPerRow).toList(growable: false);
- _notify();
- }
- Future<List<JellyfinAlbum>> _randomForSelections({
- int limit = maxAlbumsPerRow,
- }) async {
- final selections = jellyfin.genreSelections;
- if (selections.isEmpty) return const [];
- final families = await library.genreFamilies();
- final raw = library.catalog.rawGenresFor(
- families: families,
- selectedIds: selections.map((selection) => selection.id),
- );
- return library.randomAlbumsForGenres(raw, limit: limit);
- }
- String _messageFor(Object error) {
- if (error is JellyfinUnreachable) {
- return 'Jellyfin is unreachable. Check the server and try again.';
- }
- if (error is JellyfinAuthException) {
- return 'Pair Jellyfin again.';
- }
- if (error is JellyfinException) return error.message;
- return 'Could not load the music library.';
- }
- void _notify() {
- if (!_disposed) notifyListeners();
- }
- @override
- void dispose() {
- _disposed = true;
- super.dispose();
- }
- }
|