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>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
/// 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<Track> tracks;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const AlbumState({
|
||||
this.album,
|
||||
this.tracks = const [],
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
AlbumState copyWith({
|
||||
Album? album,
|
||||
List<Track>? 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<AlbumState> {
|
||||
AlbumNotifier(this._musicApiService) : super(const AlbumState());
|
||||
|
||||
final MusicApiService _musicApiService;
|
||||
|
||||
/// Load complete album information with tracks
|
||||
Future<void> 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<String, dynamic>);
|
||||
final tracks = (results[1] as List)
|
||||
.map((json) => Track.fromJson(json as Map<String, dynamic>))
|
||||
.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<void> 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<void> shuffle(PlayerNotifier playerNotifier) async {
|
||||
if (state.tracks.isEmpty) return;
|
||||
|
||||
// Create shuffled list
|
||||
final shuffledTracks = List<Track>.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<void> 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<AlbumNotifier, AlbumState>((ref) {
|
||||
final musicApiService = ref.watch(musicApiServiceProvider);
|
||||
return AlbumNotifier(musicApiService);
|
||||
});
|
||||
|
||||
/// Album data provider for a specific album ID
|
||||
final albumDataProvider = Provider.family<AlbumState, String>((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;
|
||||
});
|
||||
Reference in New Issue
Block a user