/// Album Provider - Album details state management library; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:math'; import '../../../infrastructure/datasources/remote/music_api_service.dart'; import '../../../domain/entities/album.dart'; import '../../../domain/entities/track.dart'; import 'music_provider.dart'; /// Album state class AlbumState { final Album? album; final List tracks; final bool isLoading; final String? error; const AlbumState({ this.album, this.tracks = const [], this.isLoading = false, this.error, }); AlbumState copyWith({ Album? album, List? tracks, bool? isLoading, String? error, }) { return AlbumState( album: album ?? this.album, tracks: tracks ?? this.tracks, isLoading: isLoading ?? this.isLoading, error: error, ); } /// Get total duration of all tracks in seconds int get totalDuration { return tracks.fold(0, (sum, track) { return sum + (track.duration ?? 0); }); } /// Get formatted total duration (hours:minutes:seconds) String get formattedTotalDuration { final totalSeconds = totalDuration; final hours = totalSeconds ~/ 3600; final minutes = (totalSeconds % 3600) ~/ 60; final seconds = totalSeconds % 60; if (hours > 0) { return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } else { return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } } } /// Album notifier class AlbumNotifier extends StateNotifier { AlbumNotifier(this._musicApiService) : super(const AlbumState()); final MusicApiService _musicApiService; /// Load complete album information with tracks Future loadAlbum(String albumId) async { state = state.copyWith(isLoading: true, error: null); try { // Load album details and tracks in parallel final results = await Future.wait([ _musicApiService.getAlbum(albumId), _musicApiService.getAlbumTracks(albumId), ]); final album = Album.fromJson(results[0] as Map); final tracks = (results[1] as List) .map((json) => Track.fromJson(json as Map)) .toList(); // Sort tracks by track number tracks.sort((a, b) { final aNum = a.trackNumber ?? 0; final bNum = b.trackNumber ?? 0; return aNum.compareTo(bNum); }); state = AlbumState( album: album, tracks: tracks, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); } } /// Play all tracks from album Future playAll(PlayerNotifier playerNotifier) async { if (state.tracks.isEmpty) return; playerNotifier.setQueue(state.tracks, startIndex: 0); await playerNotifier.loadTrack(state.tracks.first); await playerNotifier.play(); } /// Shuffle and play all tracks from album Future shuffle(PlayerNotifier playerNotifier) async { if (state.tracks.isEmpty) return; // Create shuffled list final shuffledTracks = List.from(state.tracks); final random = Random(); for (int i = shuffledTracks.length - 1; i > 0; i--) { final j = random.nextInt(i + 1); final temp = shuffledTracks[i]; shuffledTracks[i] = shuffledTracks[j]; shuffledTracks[j] = temp; } playerNotifier.setQueue(shuffledTracks, startIndex: 0); await playerNotifier.loadTrack(shuffledTracks.first); await playerNotifier.play(); } /// Play specific track from album Future playTrack( PlayerNotifier playerNotifier, Track track, ) async { final index = state.tracks.indexWhere((t) => t.id == track.id); if (index == -1) return; playerNotifier.setQueue(state.tracks, startIndex: index); await playerNotifier.loadTrack(track); await playerNotifier.play(); } /// Clear state void clear() { state = const AlbumState(); } } /// Album provider final albumProvider = StateNotifierProvider((ref) { final musicApiService = ref.watch(musicApiServiceProvider); return AlbumNotifier(musicApiService); }); /// Album data provider for a specific album ID final albumDataProvider = Provider.family((ref, albumId) { final notifier = ref.watch(albumProvider.notifier); // Load data when first accessed if (notifier.state.album?.id != albumId) { Future.microtask(() => notifier.loadAlbum(albumId)); } return notifier.state; });