9c504d2c3d
Features: - Frontend Flutter avec thème néon cyberpunk - Backend FastAPI avec streaming YouTube - Base de données PostgreSQL + Redis - Authentification JWT complète - Recherche multi-source (DB + YouTube) - Playlists CRUD avec drag & drop - Queue management - Settings avec audio quality - Interface adaptative (Desktop + Mobile) Tech Stack: - Frontend: Flutter 3.2+, Riverpod - Backend: Python 3.11+, FastAPI - Database: PostgreSQL 15+ - Cache: Redis 7+ - Streaming: yt-dlp + FFmpeg 🚀 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
170 lines
5.3 KiB
Dart
170 lines
5.3 KiB
Dart
/// Library Provider - Library state management
|
|
library;
|
|
|
|
import 'dart:async';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../infrastructure/datasources/remote/playlist_api_service.dart';
|
|
import '../../../infrastructure/datasources/remote/music_api_service.dart';
|
|
import '../../../domain/entities/playlist.dart';
|
|
import '../../../domain/entities/track.dart';
|
|
import '../../../domain/entities/album.dart';
|
|
import '../../../domain/entities/artist.dart';
|
|
|
|
/// Library state
|
|
class LibraryState {
|
|
final List<Playlist> playlists;
|
|
final List<Track> likedSongs;
|
|
final List<Album> savedAlbums;
|
|
final List<Artist> followedArtists;
|
|
final bool isLoading;
|
|
final String? error;
|
|
|
|
const LibraryState({
|
|
this.playlists = const [],
|
|
this.likedSongs = const [],
|
|
this.savedAlbums = const [],
|
|
this.followedArtists = const [],
|
|
this.isLoading = false,
|
|
this.error,
|
|
});
|
|
|
|
LibraryState copyWith({
|
|
List<Playlist>? playlists,
|
|
List<Track>? likedSongs,
|
|
List<Album>? savedAlbums,
|
|
List<Artist>? followedArtists,
|
|
bool? isLoading,
|
|
String? error,
|
|
}) {
|
|
return LibraryState(
|
|
playlists: playlists ?? this.playlists,
|
|
likedSongs: likedSongs ?? this.likedSongs,
|
|
savedAlbums: savedAlbums ?? this.savedAlbums,
|
|
followedArtists: followedArtists ?? this.followedArtists,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
error: error,
|
|
);
|
|
}
|
|
|
|
int get totalItems => playlists.length + likedSongs.length + savedAlbums.length + followedArtists.length;
|
|
}
|
|
|
|
/// Library notifier
|
|
class LibraryNotifier extends StateNotifier<LibraryState> {
|
|
LibraryNotifier(this._playlistApiService, this._musicApiService)
|
|
: super(const LibraryState());
|
|
|
|
final PlaylistApiService _playlistApiService;
|
|
final MusicApiService _musicApiService;
|
|
|
|
Future<void> loadLibrary() async {
|
|
state = state.copyWith(isLoading: true, error: null);
|
|
|
|
try {
|
|
// Load playlists in parallel
|
|
final results = await Future.wait([
|
|
_loadPlaylists(),
|
|
// TODO: Implement these endpoints when backend is ready
|
|
// _loadLikedSongs(),
|
|
// _loadSavedAlbums(),
|
|
// _loadFollowedArtists(),
|
|
]);
|
|
|
|
state = LibraryState(
|
|
playlists: results[0] as List<Playlist>,
|
|
likedSongs: [],
|
|
savedAlbums: [],
|
|
followedArtists: [],
|
|
);
|
|
} catch (e) {
|
|
state = LibraryState(
|
|
playlists: state.playlists,
|
|
likedSongs: state.likedSongs,
|
|
savedAlbums: state.savedAlbums,
|
|
followedArtists: state.followedArtists,
|
|
error: e.toString(),
|
|
);
|
|
} finally {
|
|
state = state.copyWith(isLoading: false);
|
|
}
|
|
}
|
|
|
|
Future<List<Playlist>> _loadPlaylists() async {
|
|
final playlistsJson = await _playlistApiService.getPlaylists();
|
|
return playlistsJson
|
|
.map((json) => Playlist.fromJson(json as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
Future<void> addLikedSong(Track track) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = [...state.likedSongs, track];
|
|
state = state.copyWith(likedSongs: updated);
|
|
}
|
|
|
|
Future<void> removeLikedSong(String trackId) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = state.likedSongs.where((t) => t.id != trackId).toList();
|
|
state = state.copyWith(likedSongs: updated);
|
|
}
|
|
|
|
Future<void> addSavedAlbum(Album album) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = [...state.savedAlbums, album];
|
|
state = state.copyWith(savedAlbums: updated);
|
|
}
|
|
|
|
Future<void> removeSavedAlbum(String albumId) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = state.savedAlbums.where((a) => a.id != albumId).toList();
|
|
state = state.copyWith(savedAlbums: updated);
|
|
}
|
|
|
|
Future<void> followArtist(Artist artist) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = [...state.followedArtists, artist];
|
|
state = state.copyWith(followedArtists: updated);
|
|
}
|
|
|
|
Future<void> unfollowArtist(String artistId) async {
|
|
// TODO: Implement when backend endpoint is ready
|
|
final updated = state.followedArtists.where((a) => a.id != artistId).toList();
|
|
state = state.copyWith(followedArtists: updated);
|
|
}
|
|
|
|
Future<void> deletePlaylist(String playlistId) async {
|
|
await _playlistApiService.deletePlaylist(playlistId);
|
|
final updated = state.playlists.where((p) => p.id != playlistId).toList();
|
|
state = state.copyWith(playlists: updated);
|
|
}
|
|
|
|
Future<void> createPlaylist({
|
|
required String name,
|
|
String? description,
|
|
String? imageUrl,
|
|
bool isPublic = false,
|
|
}) async {
|
|
final playlistJson = await _playlistApiService.createPlaylist(
|
|
name: name,
|
|
description: description,
|
|
imageUrl: imageUrl,
|
|
isPublic: isPublic,
|
|
);
|
|
final playlist = Playlist.fromJson(playlistJson as Map<String, dynamic>);
|
|
final updated = [...state.playlists, playlist];
|
|
state = state.copyWith(playlists: updated);
|
|
}
|
|
|
|
Future<void> refresh() async {
|
|
await loadLibrary();
|
|
}
|
|
}
|
|
|
|
/// Library provider
|
|
final libraryProvider = StateNotifierProvider<LibraryNotifier, LibraryState>((ref) {
|
|
final playlistApiService = ref.watch(playlistApiServiceProvider);
|
|
final musicApiService = ref.watch(musicApiServiceProvider);
|
|
return LibraryNotifier(playlistApiService, musicApiService);
|
|
});
|