a89c7894cf
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>
131 lines
3.5 KiB
Dart
131 lines
3.5 KiB
Dart
import 'package:equatable/equatable.dart';
|
|
|
|
import 'track.dart';
|
|
|
|
/// Playlist entity
|
|
class Playlist extends Equatable {
|
|
final String id;
|
|
final String userId;
|
|
final String name;
|
|
final String? description;
|
|
final String? imageUrl;
|
|
final bool isPublic;
|
|
final bool isCollaborative;
|
|
final bool isSmart;
|
|
final int trackCount;
|
|
final int totalDuration;
|
|
final DateTime createdAt;
|
|
final DateTime updatedAt;
|
|
final List<PlaylistTrack>? tracks;
|
|
|
|
const Playlist({
|
|
required this.id,
|
|
required this.userId,
|
|
required this.name,
|
|
this.description,
|
|
this.imageUrl,
|
|
this.isPublic = false,
|
|
this.isCollaborative = false,
|
|
this.isSmart = false,
|
|
this.trackCount = 0,
|
|
this.totalDuration = 0,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
this.tracks,
|
|
});
|
|
|
|
/// Format total duration as Xh Ym or Ym Zs
|
|
String get formattedDuration {
|
|
final hours = totalDuration ~/ 3600;
|
|
final minutes = (totalDuration % 3600) ~/ 60;
|
|
final seconds = totalDuration % 60;
|
|
|
|
if (hours > 0) {
|
|
return '${hours}h ${minutes}m';
|
|
} else if (minutes > 0) {
|
|
return '${minutes}m ${seconds}s';
|
|
} else {
|
|
return '${seconds}s';
|
|
}
|
|
}
|
|
|
|
/// Create Playlist from JSON
|
|
factory Playlist.fromJson(Map<String, dynamic> json) {
|
|
return Playlist(
|
|
id: json['id'] as String,
|
|
userId: json['user_id'] as String,
|
|
name: json['name'] as String,
|
|
description: json['description'] as String?,
|
|
imageUrl: json['image_url'] as String?,
|
|
isPublic: json['is_public'] as bool? ?? false,
|
|
isCollaborative: json['is_collaborative'] as bool? ?? false,
|
|
isSmart: json['is_smart'] as bool? ?? false,
|
|
trackCount: json['track_count'] as int? ?? 0,
|
|
totalDuration: json['total_duration'] as int? ?? 0,
|
|
createdAt: json['created_at'] != null
|
|
? DateTime.parse(json['created_at'] as String)
|
|
: DateTime.now(),
|
|
updatedAt: json['updated_at'] != null
|
|
? DateTime.parse(json['updated_at'] as String)
|
|
: DateTime.now(),
|
|
tracks: json['tracks'] != null
|
|
? (json['tracks'] as List)
|
|
.map((item) => PlaylistTrack.fromJson(item as Map<String, dynamic>))
|
|
.toList()
|
|
: null,
|
|
);
|
|
}
|
|
|
|
@override
|
|
List<Object?> get props => [
|
|
id,
|
|
userId,
|
|
name,
|
|
isPublic,
|
|
isCollaborative,
|
|
trackCount,
|
|
totalDuration,
|
|
];
|
|
}
|
|
|
|
/// Playlist track association
|
|
class PlaylistTrack extends Equatable {
|
|
final String id;
|
|
final String playlistId;
|
|
final String trackId;
|
|
final int position;
|
|
final DateTime addedAt;
|
|
final String? addedBy;
|
|
final Track? track;
|
|
|
|
const PlaylistTrack({
|
|
required this.id,
|
|
required this.playlistId,
|
|
required this.trackId,
|
|
required this.position,
|
|
required this.addedAt,
|
|
this.adddedBy,
|
|
this.track,
|
|
});
|
|
|
|
/// Create PlaylistTrack from JSON
|
|
factory PlaylistTrack.fromJson(Map<String, dynamic> json) {
|
|
return PlaylistTrack(
|
|
id: json['id'] as String,
|
|
playlistId: json['playlist_id'] as String,
|
|
trackId: json['track_id'] as String,
|
|
position: json['position'] as int,
|
|
addedAt: json['added_at'] != null
|
|
? DateTime.parse(json['added_at'] as String)
|
|
: DateTime.now(),
|
|
addedBy: json['added_by'] as String?,
|
|
track: json['track'] != null
|
|
? Track.fromJson(json['track'] as Map<String, dynamic>)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
@override
|
|
List<Object?> get props => [id, playlistId, trackId, position, addedAt];
|
|
}
|