🎉 Initial commit: AudiOhm - Alternative à Spotify avec streaming YouTube
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>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
/// Artist Provider - Artist details state management
|
||||
library;
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../infrastructure/datasources/remote/music_api_service.dart';
|
||||
import '../../../domain/entities/artist.dart';
|
||||
import '../../../domain/entities/track.dart';
|
||||
import '../../../domain/entities/album.dart';
|
||||
|
||||
/// Artist state
|
||||
class ArtistState {
|
||||
final Artist? artist;
|
||||
final List<Track> topTracks;
|
||||
final List<Album> albums;
|
||||
final List<Track> relatedTracks;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const ArtistState({
|
||||
this.artist,
|
||||
this.topTracks = const [],
|
||||
this.albums = const [],
|
||||
this.relatedTracks = const [],
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
ArtistState copyWith({
|
||||
Artist? artist,
|
||||
List<Track>? topTracks,
|
||||
List<Album>? albums,
|
||||
List<Track>? relatedTracks,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
}) {
|
||||
return ArtistState(
|
||||
artist: artist ?? this.artist,
|
||||
topTracks: topTracks ?? this.topTracks,
|
||||
albums: albums ?? this.albums,
|
||||
relatedTracks: relatedTracks ?? this.relatedTracks,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Artist notifier
|
||||
class ArtistNotifier extends StateNotifier<ArtistState> {
|
||||
ArtistNotifier(this._musicApiService) : super(const ArtistState());
|
||||
|
||||
final MusicApiService _musicApiService;
|
||||
|
||||
/// Load complete artist information
|
||||
Future<void> loadArtist(String artistId) async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
|
||||
try {
|
||||
// Load artist details
|
||||
final artistData = await _musicApiService.getArtist(artistId);
|
||||
final artist = Artist.fromJson(artistData);
|
||||
|
||||
state = state.copyWith(
|
||||
artist: artist,
|
||||
isLoading: false,
|
||||
);
|
||||
} catch (e) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load artist's top tracks
|
||||
Future<void> loadTopTracks(String artistId) async {
|
||||
try {
|
||||
final tracksData = await _musicApiService.getArtistTopTracks(artistId);
|
||||
final tracks = tracksData
|
||||
.map((json) => Track.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(topTracks: tracks);
|
||||
} catch (e) {
|
||||
state = state.copyWith(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Load artist's albums
|
||||
Future<void> loadAlbums(String artistId) async {
|
||||
try {
|
||||
final albumsData = await _musicApiService.getArtistAlbums(artistId);
|
||||
final albums = albumsData
|
||||
.map((json) => Album.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(albums: albums);
|
||||
} catch (e) {
|
||||
state = state.copyWith(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Load related tracks (based on artist's top track)
|
||||
Future<void> loadRelatedTracks(String artistId) async {
|
||||
try {
|
||||
// Get first track from top tracks for recommendations
|
||||
if (state.topTracks.isEmpty) {
|
||||
await loadTopTracks(artistId);
|
||||
}
|
||||
|
||||
if (state.topTracks.isNotEmpty) {
|
||||
final firstTrack = state.topTracks.first;
|
||||
final relatedData =
|
||||
await _musicApiService.getRecommendations(firstTrack.id);
|
||||
final related = relatedData
|
||||
.map((json) => Track.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(relatedTracks: related);
|
||||
}
|
||||
} catch (e) {
|
||||
state = state.copyWith(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all artist data at once
|
||||
Future<void> loadAllArtistData(String artistId) async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
|
||||
try {
|
||||
// Load all data in parallel
|
||||
final results = await Future.wait([
|
||||
_musicApiService.getArtist(artistId),
|
||||
_musicApiService.getArtistTopTracks(artistId),
|
||||
_musicApiService.getArtistAlbums(artistId),
|
||||
]);
|
||||
|
||||
final artist = Artist.fromJson(results[0] as Map<String, dynamic>);
|
||||
final topTracks = (results[1] as List)
|
||||
.map((json) => Track.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
final albums = (results[2] as List)
|
||||
.map((json) => Album.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
// Load related tracks based on first top track
|
||||
List<Track> relatedTracks = [];
|
||||
if (topTracks.isNotEmpty) {
|
||||
try {
|
||||
final relatedData =
|
||||
await _musicApiService.getRecommendations(topTracks.first.id);
|
||||
relatedTracks = relatedData
|
||||
.map((json) => Track.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (_) {
|
||||
// Don't fail if recommendations fail
|
||||
}
|
||||
}
|
||||
|
||||
state = ArtistState(
|
||||
artist: artist,
|
||||
topTracks: topTracks,
|
||||
albums: albums,
|
||||
relatedTracks: relatedTracks,
|
||||
isLoading: false,
|
||||
);
|
||||
} catch (e) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear state
|
||||
void clear() {
|
||||
state = const ArtistState();
|
||||
}
|
||||
}
|
||||
|
||||
/// Artist provider
|
||||
final artistProvider =
|
||||
StateNotifierProvider<ArtistNotifier, ArtistState>((ref) {
|
||||
final musicApiService = ref.watch(musicApiServiceProvider);
|
||||
return ArtistNotifier(musicApiService);
|
||||
});
|
||||
|
||||
/// Artist data provider for a specific artist ID
|
||||
final artistDataProvider = Provider.family<ArtistState, String>((ref, artistId) {
|
||||
final notifier = ref.watch(artistProvider.notifier);
|
||||
// Load data when first accessed
|
||||
if (notifier.state.artist?.id != artistId) {
|
||||
Future.microtask(() => notifier.loadAllArtistData(artistId));
|
||||
}
|
||||
return notifier.state;
|
||||
});
|
||||
Reference in New Issue
Block a user