/// Playlist Provider - State management for playlist details library; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../domain/entities/playlist.dart'; import '../../../domain/entities/track.dart'; import '../../../infrastructure/datasources/remote/playlist_api_service.dart'; import '../../providers/music_provider.dart'; /// Playlist state class PlaylistState { final Playlist? playlist; final List tracks; final bool isLoading; final bool isReordering; final String? error; const PlaylistState({ this.playlist, this.tracks = const [], this.isLoading = false, this.isReordering = false, this.error, }); PlaylistState copyWith({ Playlist? playlist, List? tracks, bool? isLoading, bool? isReordering, String? error, }) { return PlaylistState( playlist: playlist ?? this.playlist, tracks: tracks ?? this.tracks, isLoading: isLoading ?? this.isLoading, isReordering: isReordering ?? this.isReordering, error: error, ); } /// Get total duration of all tracks Duration get totalDuration { final totalSeconds = tracks.fold( 0, (sum, track) => sum + (track.duration ?? 0), ); return Duration(seconds: totalSeconds); } /// Format total duration String get formattedTotalDuration { final duration = totalDuration; final hours = duration.inHours; final minutes = duration.inMinutes.remainder(60); if (hours > 0) { return '${hours}h ${minutes}m'; } else { return '${minutes}m'; } } } /// Playlist notifier class PlaylistNotifier extends StateNotifier { PlaylistNotifier(this._playlistApiService) : super(const PlaylistState()); final PlaylistApiService _playlistApiService; /// Load playlist with tracks Future loadPlaylist(String playlistId) async { state = state.copyWith(isLoading: true, error: null); try { final response = await _playlistApiService.getPlaylist(playlistId); // Parse playlist final playlist = Playlist.fromJson(response); // Parse tracks from response final tracks = []; if (response['tracks'] != null) { for (final trackData in response['tracks'] as List) { if (trackData is Map && trackData['track'] != null) { final track = Track.fromJson(trackData['track'] as Map); tracks.add(track); } } } state = PlaylistState( playlist: playlist, tracks: tracks, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); } } /// Add track to playlist Future addTrack(Track track, {int? position}) async { if (state.playlist == null) return; try { await _playlistApiService.addTracks( state.playlist!.id, [track.id], position: position, ); // Reload playlist to get updated tracks await loadPlaylist(state.playlist!.id); } catch (e) { state = state.copyWith(error: e.toString()); } } /// Remove track from playlist Future removeTrack(String trackId) async { if (state.playlist == null) return; try { await _playlistApiService.removeTrack(state.playlist!.id, trackId); // Update local state final updatedTracks = state.tracks.where((t) => t.id != trackId).toList(); state = state.copyWith(tracks: updatedTracks); } catch (e) { state = state.copyWith(error: e.toString()); } } /// Reorder tracks Future reorderTracks(int oldIndex, int newIndex) async { if (state.playlist == null || state.tracks.isEmpty) return; // Update local state immediately for responsiveness final updatedTracks = List.from(state.tracks); final track = updatedTracks.removeAt(oldIndex); updatedTracks.insert(newIndex, track); state = state.copyWith(tracks: updatedTracks, isReordering: true); try { // Call API to update position await _playlistApiService.reorderTrack( state.playlist!.id, track.id, newIndex, ); state = state.copyWith(isReordering: false); } catch (e) { // Revert on error state = state.copyWith( tracks: state.tracks, isReordering: false, error: e.toString(), ); } } /// Update playlist details Future updatePlaylist({ String? name, String? description, String? imageUrl, bool? isPublic, }) async { if (state.playlist == null) return; try { final response = await _playlistApiService.updatePlaylist( state.playlist!.id, name: name, description: description, imageUrl: imageUrl, isPublic: isPublic, ); final updatedPlaylist = Playlist.fromJson(response); state = state.copyWith(playlist: updatedPlaylist); } catch (e) { state = state.copyWith(error: e.toString()); } } /// Delete playlist Future deletePlaylist() async { if (state.playlist == null) return; try { await _playlistApiService.deletePlaylist(state.playlist!.id); state = const PlaylistState(); } catch (e) { state = state.copyWith(error: e.toString()); } } /// Shuffle and play playlist void shufflePlaylist(PlayerNotifier playerNotifier) { if (state.tracks.isEmpty) return; final shuffledTracks = List.from(state.tracks)..shuffle(); playerNotifier.setQueue(shuffledTracks, startIndex: 0); } /// Play playlist from start void playPlaylist(PlayerNotifier playerNotifier) { if (state.tracks.isEmpty) return; playerNotifier.setQueue(state.tracks, startIndex: 0); } } /// Playlist provider final playlistProvider = StateNotifierProvider.family( (ref, playlistId) { final playlistApiService = ref.watch(playlistApiServiceProvider); final notifier = PlaylistNotifier(playlistApiService); // Auto-load playlist Future.microtask(() => notifier.loadPlaylist(playlistId)); return notifier; }, ); /// Current playlist tracks provider (for easy access) final playlistTracksProvider = Provider.family, String>( (ref, playlistId) { final playlistState = ref.watch(playlistProvider(playlistId)); return playlistState.tracks; }, );