feat: Modernisation UI/UX et configuration Flutter multi-plateforme

Phase 1 - Corrections Critiques:
- Fixed memory leaks dans music_provider.dart (stream subscriptions)
- Fixed race conditions dans search_provider.dart (stale results)
- Fixed token refresh errors dans api_service.dart
- Improved error handling avec messages utilisateur
- Changed API URL to HTTPS by default

Phase 2 - Améliorations UX Desktop:
- Ajouté cursor pointers sur tous les éléments cliquables
- Implémenté hover states avec effets néon glow (200ms transitions)
- Créé skeleton loading states avec shimmer animation
- Ajouté widgets: ClickableWrapper, ErrorDisplay, SkeletonLoading
- Enhanced visual feedback pour desktop users

Phase 3 - Configuration Flutter:
- Configuré Android (Gradle 8.1.0, Kotlin 1.9.0, minSdk 21, targetSdk 34)
- Créé launcher icons cyberpunk néon (5 densités)
- Configuré Windows desktop (structure complète)
- Activé Linux desktop support
- Ajouté package équatable pour entités de domaine
- Corrigé imports (colors.dart, auth_provider.dart)
- Fixed Dio API compatibility (RequestOptions)

Documentation:
- STYLE_GUIDE.md: Guide complet (100+ pages)
- DESIGN_IMPLEMENTATION_GUIDE.md: Implémentation Flutter
- BUILD_STATUS.md: Status builds + troubleshooting
- QUICKSTART_BUILDS.md: Guide rapide
- BUILD_INDEX.md: Index documentation
- PHASE_1_CORRECTIONS.md: Corrections Phase 1
- PHASE_2_UX_IMPROVEMENTS.md: Améliorations Phase 2
- PR_REVIEW_SUMMARY.md: Revue code complète
- CODE_ANALYSIS_AND_PRIORITIES.md: Analyse code

Scripts & Builds:
- BUILD_ALL.sh: Script automatisé builds multi-plateforme
- builds/: Structure avec README par plateforme
- design-system/: Système de design complet

Backend:
- Ajouté streaming HTTP Range pour audio progressif
- Enhanced YouTube service avec métadonnées complètes
- Improved error handling et validation

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
root
2026-01-19 07:44:40 +00:00
parent a89c7894cf
commit 85dad89d5b
100 changed files with 13570 additions and 323 deletions
@@ -1,6 +1,9 @@
/// Music Provider - Player state management
library;
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart';
@@ -56,44 +59,65 @@ class PlayerState {
class PlayerNotifier extends StateNotifier<PlayerState> {
PlayerNotifier() : super(const PlayerState()) {
_player = AudioPlayer();
_subscriptions = [];
_init();
}
late final AudioPlayer _player;
final List<StreamSubscription> _subscriptions = [];
void _init() {
_player.positionStream.listen((position) {
// Subscribe to position stream and store subscription
_subscriptions.add(_player.positionStream.listen((position) {
state = state.copyWith(position: position);
});
}));
_player.durationStream.listen((duration) {
// Subscribe to duration stream and store subscription
_subscriptions.add(_player.durationStream.listen((duration) {
state = state.copyWith(duration: duration ?? Duration.zero);
});
}));
_player.playerStateStream.listen((playerState) {
// Subscribe to player state stream and store subscription
_subscriptions.add(_player.playerStateStream.listen((playerState) {
state = state.copyWith(
isPlaying: playerState.playing,
isLoading: playerState.processingState == ProcessingState.loading,
);
});
}));
}
Future<void> loadTrack(Track track) async {
state = state.copyWith(isLoading: true);
state = state.copyWith(isLoading: true, errorMessage: null);
try {
// Get stream URL from API
final streamUrl = track.audioUrl ?? '';
// Validate audio URL exists
final streamUrl = track.audioUrl;
if (streamUrl == null || streamUrl.isEmpty) {
throw Exception('No audio URL available for track: ${track.title}');
}
await _player.setUrl(streamUrl);
if (state.queue.isEmpty) {
state = state.copyWith(queue: [track], currentIndex: 0);
}
} catch (e) {
// Clear error and loading state on success
state = state.copyWith(isLoading: false, errorMessage: null);
} on PlayerException catch (e) {
// Specific audio player errors
debugPrint('Player error loading track: ${e.message}');
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
errorMessage: 'Unable to play this track. Please try another.',
);
} catch (e) {
// Network or other errors
debugPrint('Error loading track: $e');
state = state.copyWith(
isLoading: false,
errorMessage: 'An error occurred while loading the track.',
);
}
}
@@ -110,6 +134,15 @@ class PlayerNotifier extends StateNotifier<PlayerState> {
state = state.copyWith(isPlaying: false);
}
/// Convenience method to toggle play/pause
Future<void> togglePlay() async {
if (state.isPlaying) {
await pause();
} else {
await play();
}
}
Future<void> seek(Duration position) async {
await _player.seek(position);
}
@@ -153,6 +186,10 @@ class PlayerNotifier extends StateNotifier<PlayerState> {
@override
void dispose() {
// Cancel all stream subscriptions to prevent memory leaks
for (final subscription in _subscriptions) {
subscription.cancel();
}
_player.dispose();
super.dispose();
}