Files
AudiOhm/frontend/lib/presentation/providers/playlist_provider.dart
T
root a89c7894cf Initial commit: AudiOhm - Alternative Spotify avec streaming YouTube
Backend:
- FastAPI avec PostgreSQL et Redis
- Authentification JWT complète
- API REST pour musique, playlists, recherche
- Streaming audio via yt-dlp
- SQLAlchemy 2.0 async

Frontend:
- Flutter avec thème néon cyberpunk
- State management Riverpod
- Layout adaptatif desktop/mobile
- Lecteur audio avec mini-player

Infrastructure:
- Docker Compose (PostgreSQL + Redis)
- Scripts d'installation automatisés
- Scripts de build pour exécutables

Fichiers ajoutés:
- BUILD_CLIENT_*.bat/sh: Scripts de compilation
- BUILD_CLIENT_README.md: Documentation compilation
- CHECK_FLUTTER.sh: Vérificateur d'environnement
- requirements.txt mis à jour pour Python 3.13
- Modèles SQLAlchemy corrigés (metadata -> extra_metadata)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 20:08:36 +00:00

244 lines
6.3 KiB
Dart

/// 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<Track> 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<Track>? 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<int>(
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<PlaylistState> {
PlaylistNotifier(this._playlistApiService) : super(const PlaylistState());
final PlaylistApiService _playlistApiService;
/// Load playlist with tracks
Future<void> 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 = <Track>[];
if (response['tracks'] != null) {
for (final trackData in response['tracks'] as List) {
if (trackData is Map<String, dynamic> && trackData['track'] != null) {
final track = Track.fromJson(trackData['track'] as Map<String, dynamic>);
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<void> 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<void> 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<void> reorderTracks(int oldIndex, int newIndex) async {
if (state.playlist == null || state.tracks.isEmpty) return;
// Update local state immediately for responsiveness
final updatedTracks = List<Track>.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<void> 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<void> 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<Track>.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<PlaylistNotifier, PlaylistState, String>(
(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<List<Track>, String>(
(ref, playlistId) {
final playlistState = ref.watch(playlistProvider(playlistId));
return playlistState.tracks;
},
);