Restore original swingmusic_mobile folder from 9f1623b

This commit is contained in:
Tomas Dvorak
2026-03-18 19:43:40 +01:00
parent e5c7c0ca16
commit 6ca75aedf3
159 changed files with 18429 additions and 0 deletions
@@ -0,0 +1,147 @@
import 'package:flutter/foundation.dart';
import '../../data/services/audio_service.dart';
import '../../data/models/track_model.dart';
import '../../core/enums/playback_mode.dart';
class AudioProvider extends ChangeNotifier {
final AudioService _audioService = AudioService();
// Getters for audio state
TrackModel? get currentTrack => _audioService.currentTrack;
bool get isPlaying => _audioService.isPlaying;
bool get isPaused => _audioService.isPaused;
bool get isLoading => _audioService.isLoading;
bool get isBuffering => _audioService.isBuffering;
bool get hasError => _audioService.hasError;
String? get errorMessage => _audioService.errorMessage;
Duration get position => _audioService.position;
Duration get duration => _audioService.duration;
double get volume => _audioService.volume;
List<TrackModel> get queue => _audioService.queue;
int get currentIndex => _audioService.currentIndex;
bool get isShuffleMode => _audioService.isShuffleMode;
bool get isRepeatMode => _audioService.isRepeatMode;
RepeatMode get repeatMode => _audioService.repeatMode;
ShuffleMode get shuffleMode => _audioService.shuffleMode;
double get playbackSpeed => _audioService.playbackSpeed;
// Streams
Stream<Duration> get positionStream => _audioService.positionStream;
Stream<Duration> get durationStream => _audioService.durationStream;
Stream<bool> get playingStateStream => _audioService.playingStateStream;
Stream<TrackModel?> get currentTrackStream => _audioService.currentTrackStream;
Stream<List<TrackModel>> get queueStream => _audioService.queueStream;
Stream<bool> get bufferingStream => _audioService.bufferingStream;
Stream<String?> get errorStream => _audioService.errorStream;
Stream<RepeatMode> get repeatModeStream => _audioService.repeatModeStream;
Stream<ShuffleMode> get shuffleModeStream => _audioService.shuffleModeStream;
// Audio controls
Future<void> initialize() async {
await _audioService.initialize();
notifyListeners();
}
Future<void> loadTrack(TrackModel track) async {
await _audioService.loadTrack(track);
notifyListeners();
}
Future<void> play() async {
await _audioService.play();
notifyListeners();
}
Future<void> pause() async {
await _audioService.pause();
notifyListeners();
}
Future<void> stop() async {
await _audioService.stop();
notifyListeners();
}
Future<void> seekTo(Duration position) async {
await _audioService.seekTo(position);
notifyListeners();
}
Future<void> setVolume(double volume) async {
await _audioService.setVolume(volume);
notifyListeners();
}
Future<void> setSpeed(double speed) async {
await _audioService.setSpeed(speed);
notifyListeners();
}
// Queue management
void setQueue(List<TrackModel> tracks) {
_audioService.setQueue(tracks);
notifyListeners();
}
void addToQueue(TrackModel track) {
_audioService.addToQueue(track);
notifyListeners();
}
void removeFromQueue(int index) {
_audioService.removeFromQueue(index);
notifyListeners();
}
void clearQueue() {
_audioService.clearQueue();
notifyListeners();
}
Future<void> playNext() async {
await _audioService.playNext();
notifyListeners();
}
Future<void> playPrevious() async {
await _audioService.playPrevious();
notifyListeners();
}
void jumpToIndex(int index) {
_audioService.jumpToIndex(index);
notifyListeners();
}
// Playback modes
void toggleShuffle() {
_audioService.toggleShuffle();
notifyListeners();
}
void toggleRepeat() {
_audioService.toggleRepeat();
notifyListeners();
}
void setShuffleMode(ShuffleMode mode) {
_audioService.setShuffleMode(mode == ShuffleMode.on);
notifyListeners();
}
void setRepeatMode(RepeatMode mode) {
_audioService.setRepeatMode(mode);
notifyListeners();
}
// Utility methods
String get positionFormatted => _audioService.positionFormatted;
String get durationFormatted => _audioService.durationFormatted;
double get progress => _audioService.progress;
@override
void dispose() {
_audioService.dispose();
super.dispose();
}
}
@@ -0,0 +1,208 @@
import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class AuthProvider extends ChangeNotifier {
AuthState _authState = AuthState.loggedOut;
String? _errorMessage;
String? _baseUrl;
String? _accessToken;
String? _refreshToken;
// Getters
AuthState get authState => _authState;
String? get errorMessage => _errorMessage;
String? get baseUrl => _baseUrl;
String? get accessToken => _accessToken;
String? get refreshToken => _refreshToken;
bool get isLoggedIn => _authState.isLoggedIn;
bool get isLoggedOut => _authState.isLoggedOut;
bool get isAuthenticating => _authState.isAuthenticating;
bool get hasError => _authState.hasError;
Future<void> initialize() async {
await _loadStoredCredentials();
_checkLoginStatus();
}
Future<void> loginWithUsernameAndPassword(
String baseUrl,
String username,
String password,
) async {
try {
_setAuthenticating();
// Validate URL
if (!_isValidUrl(baseUrl)) {
_setError('Please enter a valid server URL');
return;
}
if (username.isEmpty || password.isEmpty) {
_setError('Username and password are required');
return;
}
// Make API call to login
final response = await http.post(
Uri.parse('$baseUrl/api/auth/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': username,
'password': password,
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
_accessToken = data['access_token'];
_refreshToken = data['refresh_token'];
_baseUrl = baseUrl;
// Store credentials
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', _accessToken!);
await prefs.setString('refresh_token', _refreshToken!);
await prefs.setString('base_url', _baseUrl);
_setAuthenticated();
_clearError();
} else {
_setError('Login failed: ${response.statusCode}');
}
} catch (e) {
_setError('Login error: $e');
}
}
Future<void> loginWithQrCode(String qrCode) async {
try {
_setAuthenticating();
// TODO: Implement QR code login logic
// For now, simulate successful login
await Future.delayed(const Duration(seconds: 1));
_accessToken = 'mock_token_from_qr';
_refreshToken = 'mock_refresh_from_qr';
// Store credentials
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', _accessToken!);
await prefs.setString('refresh_token', _refreshToken!);
_setAuthenticated();
_clearError();
} catch (e) {
_setError('QR code login error: $e');
}
}
Future<void> logout() async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('access_token');
await prefs.remove('refresh_token');
await prefs.remove('base_url');
_accessToken = null;
_refreshToken = null;
_setLoggedOut();
_clearError();
} catch (e) {
// Continue with logout even if storage fails
_accessToken = null;
_refreshToken = null;
_setLoggedOut();
_clearError();
}
}
Future<void> refreshTokens() async {
try {
if (_refreshToken == null) return;
// TODO: Implement token refresh logic
// For now, just check if current token is still valid
} catch (e) {
_setError('Token refresh failed: $e');
}
}
void _setAuthenticating() {
_authState = AuthState.authenticating;
_clearError();
notifyListeners();
}
void _setAuthenticated() {
_authState = AuthState.authenticated;
_clearError();
notifyListeners();
}
void _setLoggedOut() {
_authState = AuthState.loggedOut;
_clearError();
notifyListeners();
}
void _setError(String error) {
_errorMessage = error;
_authState = AuthState.error;
notifyListeners();
}
void _clearError() {
_errorMessage = null;
if (_authState == AuthState.error) {
_authState = AuthState.loggedOut;
}
notifyListeners();
}
bool _isValidUrl(String url) {
try {
final uri = Uri.parse(url);
return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https');
} catch (e) {
return false;
}
}
Future<void> _loadStoredCredentials() async {
try {
final prefs = await SharedPreferences.getInstance();
_accessToken = prefs.getString('access_token');
_refreshToken = prefs.getString('refresh_token');
_baseUrl = prefs.getString('base_url');
if (_accessToken != null) {
_setAuthenticated();
}
} catch (e) {
// Continue without stored credentials
debugPrint('Error loading stored credentials: $e');
}
}
Future<void> _checkLoginStatus() async {
// TODO: Implement token validation
// For now, assume stored token is valid
if (_accessToken != null) {
_setAuthenticated();
}
}
// HTTP client with auth headers
http.BaseClient get authenticatedHttpClient {
return http.BaseClient(
headers: {
'Authorization': 'Bearer $_accessToken',
'Content-Type': 'application/json',
},
);
}
}
@@ -0,0 +1,699 @@
import 'package:flutter/foundation.dart';
import '../../data/services/enhanced_api_service.dart';
import '../../data/models/track_model.dart';
import '../../data/models/album_model.dart';
import '../../data/models/artist_model.dart' as artist;
import '../../data/models/playlist_model.dart';
class EnhancedLibraryProvider extends ChangeNotifier {
final EnhancedApiService _apiService;
// State
List<TrackModel> _tracks = [];
List<AlbumModel> _albums = [];
List<artist.ArtistModel> _artists = [];
List<PlaylistModel> _playlists = [];
List<dynamic> _folders = [];
Map<String, dynamic> _userInfo = {};
Map<String, dynamic> _userPreferences = {};
Map<String, dynamic> _statistics = {};
List<TrackModel> _favoriteTracks = [];
List<AlbumModel> _favoriteAlbums = [];
List<artist.ArtistModel> _favoriteArtists = [];
List<TrackModel> _queue = [];
// Loading states
bool _isLoadingTracks = false;
bool _isLoadingAlbums = false;
bool _isLoadingArtists = false;
bool _isLoadingPlaylists = false;
bool _isLoadingFolders = false;
bool _isLoadingUserInfo = false;
bool _isLoadingStatistics = false;
bool _isLoadingFavorites = false;
bool _isLoadingQueue = false;
// Error states
String? _error;
EnhancedLibraryProvider({EnhancedApiService? apiService})
: _apiService = apiService ?? EnhancedApiService();
// Getters
List<TrackModel> get tracks => _tracks;
List<AlbumModel> get albums => _albums;
List<artist.ArtistModel> get artists => _artists;
List<PlaylistModel> get playlists => _playlists;
List<dynamic> get folders => _folders;
Map<String, dynamic> get userInfo => _userInfo;
Map<String, dynamic> get userPreferences => _userPreferences;
Map<String, dynamic> get statistics => _statistics;
List<TrackModel> get favoriteTracks => _favoriteTracks;
List<AlbumModel> get favoriteAlbums => _favoriteAlbums;
List<artist.ArtistModel> get favoriteArtists => _favoriteArtists;
List<TrackModel> get queue => _queue;
bool get isLoadingTracks => _isLoadingTracks;
bool get isLoadingAlbums => _isLoadingAlbums;
bool get isLoadingArtists => _isLoadingArtists;
bool get isLoadingPlaylists => _isLoadingPlaylists;
bool get isLoadingFolders => _isLoadingFolders;
bool get isLoadingUserInfo => _isLoadingUserInfo;
bool get isLoadingStatistics => _isLoadingStatistics;
bool get isLoadingFavorites => _isLoadingFavorites;
bool get isLoadingQueue => _isLoadingQueue;
String? get error => _error;
bool get hasError => _error != null;
// Initialize data
Future<void> initialize() async {
await loadUserInfo();
await loadStatistics();
}
// Track methods
Future<void> loadTracks({String? search, String? artist, String? album, String? folder}) async {
_setLoadingTracks(true);
_clearError();
try {
_tracks = await _apiService.getTracks(
search: search,
artist: artist,
album: album,
folder: folder,
);
_setLoadingTracks(false);
} catch (e) {
_setError('Failed to load tracks: $e');
_setLoadingTracks(false);
}
}
Future<void> refreshTracks() async {
await loadTracks();
}
Future<void> loadTrack(String trackHash) async {
try {
final track = await _apiService.getTrack(trackHash);
if (track != null) {
// Update track in current list if exists
final index = _tracks.indexWhere((t) => t.trackhash == trackHash);
if (index != -1) {
_tracks[index] = track;
}
}
} catch (e) {
_setError('Failed to load track: $e');
}
}
Future<void> toggleFavoriteTrack(String trackHash) async {
try {
await _apiService.toggleFavoriteTrack(trackHash);
// Update track in local list
final index = _tracks.indexWhere((t) => t.trackhash == trackHash);
if (index != -1) {
final track = _tracks[index];
_tracks[index] = track.copyWith(isFavorite: !track.isFavorite);
}
// Update in favorites list
final favIndex = _favoriteTracks.indexWhere((t) => t.trackhash == trackHash);
if (favIndex != -1) {
_favoriteTracks[favIndex] = _favoriteTracks[favIndex].copyWith(isFavorite: !_favoriteTracks[favIndex].isFavorite);
} else {
_favoriteTracks.add(_tracks.firstWhere((t) => t.trackhash == trackHash));
}
notifyListeners();
} catch (e) {
_setError('Failed to toggle favorite: $e');
}
}
// Album methods
Future<void> loadAlbums({String? search, String? artist}) async {
_setLoadingAlbums(true);
_clearError();
try {
_albums = await _apiService.getAlbums(search: search, artist: artist);
_setLoadingAlbums(false);
} catch (e) {
_setError('Failed to load albums: $e');
}
}
Future<void> refreshAlbums() async {
await loadAlbums();
}
Future<void> loadAlbum(String albumHash) async {
try {
final album = await _apiService.getAlbum(albumHash);
if (album != null) {
// Update album in current list if exists
final index = _albums.indexWhere((a) => a.albumhash == albumHash);
if (index != -1) {
_albums[index] = album;
}
}
} catch (e) {
_setError('Failed to load album: $e');
}
}
Future<void> loadAlbumTracks(String albumHash) async {
_setLoadingTracks(true);
_clearError();
try {
final tracks = await _apiService.getAlbumTracks(albumHash);
_tracks = tracks;
_setLoadingTracks(false);
} catch (e) {
_setError('Failed to load album tracks: $e');
}
}
Future<void> toggleFavoriteAlbum(String albumHash) async {
try {
await _apiService.toggleFavoriteAlbum(albumHash);
// Update album in local list
final index = _albums.indexWhere((a) => a.albumhash == albumHash);
if (index != -1) {
final album = _albums[index];
_albums[index] = album.copyWith(isFavorite: !album.isFavorite);
}
// Update in favorites list
final favIndex = _favoriteAlbums.indexWhere((a) => a.albumhash == albumHash);
if (favIndex != -1) {
_favoriteAlbums[favIndex] = _favoriteAlbums[favIndex].copyWith(isFavorite: !_favoriteAlbums[favIndex].isFavorite);
} else {
_favoriteAlbums.add(_albums.firstWhere((a) => a.albumhash == albumHash));
}
notifyListeners();
} catch (e) {
_setError('Failed to toggle favorite album: $e');
}
}
// Artist methods
Future<void> loadArtists({String? search}) async {
_setLoadingArtists(true);
_clearError();
try {
_artists = await _apiService.getArtists(search: search);
_setLoadingArtists(false);
} catch (e) {
_setError('Failed to load artists: $e');
}
}
Future<void> refreshArtists() async {
await loadArtists();
}
Future<void> loadArtist(String artistHash) async {
try {
final artist = await _apiService.getArtist(artistHash);
if (artist != null) {
// Update artist in current list if exists
final index = _artists.indexWhere((a) => a.artisthash == artistHash);
if (index != -1) {
_artists[index] = artist;
}
}
} catch (e) {
_setError('Failed to load artist: $e');
}
}
Future<void> loadArtistAlbums(String artistHash) async {
_setLoadingAlbums(true);
_clearError();
try {
final albums = await _apiService.getArtistAlbums(artistHash);
_albums = albums;
_setLoadingAlbums(false);
} catch (e) {
_setError('Failed to load artist albums: $e');
}
}
Future<void> loadArtistTracks(String artistHash) async {
_setLoadingTracks(true);
_clearError();
try {
final tracks = await _apiService.getArtistTracks(artistHash);
_tracks = tracks;
_setLoadingTracks(false);
} catch (e) {
_setError('Failed to load artist tracks: $e');
}
}
Future<void> toggleFavoriteArtist(String artistHash) async {
try {
await _apiService.toggleFavoriteArtist(artistHash);
// Update artist in local list
final index = _artists.indexWhere((a) => a.artisthash == artistHash);
if (index != -1) {
final artist = _artists[index];
_artists[index] = artist.copyWith(isFavorite: !artist.isFavorite);
}
// Update in favorites list
final favIndex = _favoriteArtists.indexWhere((a) => a.artisthash == artistHash);
if (favIndex != -1) {
_favoriteArtists[favIndex] = _favoriteArtists[favIndex].copyWith(isFavorite: !_favoriteArtists[favIndex].isFavorite);
} else {
_favoriteArtists.add(_artists.firstWhere((a) => a.artisthash == artistHash));
}
notifyListeners();
} catch (e) {
_setError('Failed to toggle favorite artist: $e');
}
}
// Playlist methods
Future<void> loadPlaylists() async {
_setLoadingPlaylists(true);
_clearError();
try {
_playlists = await _apiService.getPlaylists();
_setLoadingPlaylists(false);
} catch (e) {
_setError('Failed to load playlists: $e');
}
}
Future<void> refreshPlaylists() async {
await loadPlaylists();
}
Future<void> loadPlaylist(String playlistId) async {
try {
final playlist = await _apiService.getPlaylist(playlistId);
if (playlist != null) {
// Update playlist in current list if exists
final index = _playlists.indexWhere((p) => p.playlistId == playlistId);
if (index != -1) {
_playlists[index] = playlist;
}
}
} catch (e) {
_setError('Failed to load playlist: $e');
}
}
Future<void> createPlaylist(String name, String description) async {
_setLoadingPlaylists(true);
_clearError();
try {
final newPlaylist = await _apiService.createPlaylist(name, description);
_playlists.insert(0, newPlaylist);
_setLoadingPlaylists(false);
} catch (e) {
_setError('Failed to create playlist: $e');
}
}
Future<void> addToPlaylist(String playlistId, String trackHash) async {
try {
await _apiService.addToPlaylist(playlistId, trackHash);
} catch (e) {
_setError('Failed to add to playlist: $e');
}
}
Future<void> removeFromPlaylist(String playlistId, String trackHash) async {
try {
await _apiService.removeFromPlaylist(playlistId, trackHash);
} catch (e) {
_setError('Failed to remove from playlist: $e');
}
}
// Folder methods
Future<void> loadFolders() async {
_setLoadingFolders(true);
_clearError();
try {
_folders = await _apiService.getFolders();
_setLoadingFolders(false);
} catch (e) {
_setError('Failed to load folders: $e');
}
}
Future<void> loadFolderTracks(String folderHash) async {
_setLoadingTracks(true);
_clearError();
try {
final tracks = await _apiService.getFolderTracks(folderHash);
_tracks = tracks;
_setLoadingTracks(false);
} catch (e) {
_setError('Failed to load folder tracks: $e');
}
}
// Favorites methods
Future<void> loadFavoriteTracks() async {
_setLoadingFavorites(true);
_clearError();
try {
_favoriteTracks = await _apiService.getFavoriteTracks();
_setLoadingFavorites(false);
} catch (e) {
_setError('Failed to load favorite tracks: $e');
}
}
Future<void> loadFavoriteAlbums() async {
_setLoadingFavorites(true);
_clearError();
try {
_favoriteAlbums = await _apiService.getFavoriteAlbums();
_setLoadingFavorites(false);
} catch (e) {
_setError('Failed to load favorite albums: $e');
}
}
Future<void> loadFavoriteArtists() async {
_setLoadingFavorites(true);
_clearError();
try {
_favoriteArtists = await _apiService.getFavoriteArtists();
_setLoadingFavorites(false);
} catch (e) {
_setError('Failed to load favorite artists: $e');
}
}
// User methods
Future<void> loadUserInfo() async {
_setLoadingUserInfo(true);
_clearError();
try {
_userInfo = await _apiService.getUserInfo();
_setLoadingUserInfo(false);
} catch (e) {
_setError('Failed to load user info: $e');
}
}
Future<void> updateUserPreferences(Map<String, dynamic> preferences) async {
try {
await _apiService.updateUserPreferences(preferences);
_userPreferences = preferences;
} catch (e) {
_setError('Failed to update preferences: $e');
}
}
Future<void> loadUserPreferences() async {
_setLoadingUserInfo(true);
_clearError();
try {
_userPreferences = await _apiService.getUserPreferences();
_setLoadingUserInfo(false);
} catch (e) {
_setError('Failed to load preferences: $e');
}
}
// Statistics methods
Future<void> loadStatistics() async {
_setLoadingStatistics(true);
_clearError();
try {
_statistics = await _apiService.getStatistics();
_setLoadingStatistics(false);
} catch (e) {
_setError('Failed to load statistics: $e');
}
}
// Download methods
Future<void> loadDownloads() async {
_setLoadingDownloads(true);
_clearError();
try {
// TODO: Implement actual download loading from API
_downloads = [
{
'downloadId': '1',
'title': 'Example Track 1',
'artist': 'Example Artist',
'status': 'completed',
'progress': 1.0,
'speed': 2.5,
'eta': 0,
},
{
'downloadId': '2',
'title': 'Example Track 2',
'artist': 'Example Artist',
'status': 'downloading',
'progress': 0.5,
'speed': 1.8,
'eta': 120,
},
];
_setLoadingDownloads(false);
} catch (e) {
_setError('Failed to load downloads: $e');
_setLoadingDownloads(false);
}
}
Future<void> loadDownloadSettings() async {
_setLoadingUserInfo(true);
_clearError();
try {
// TODO: Implement actual settings loading from API
_downloadSettings = {
'downloadPath': '/storage/emulated/0/Android/data/com.example.swingmusic/files/Downloads',
'defaultQuality': '320kbps',
'wifiOnly': false,
'maxConcurrentDownloads': 3,
};
_setLoadingUserInfo(false);
} catch (e) {
_setError('Failed to load download settings: $e');
_setLoadingUserInfo(false);
}
}
Future<void> updateDownloadSettings(Map<String, dynamic> settings) async {
try {
// TODO: Implement actual settings update to API
_downloadSettings.addAll(settings);
} catch (e) {
_setError('Failed to update download settings: $e');
}
}
Future<void> pauseDownload(String downloadId) async {
try {
// TODO: Implement actual pause via API
final index = _downloads.indexWhere((d) => d['downloadId'] == downloadId);
if (index != -1) {
_downloads[index] = {..._downloads[index], 'status': 'paused'};
}
} catch (e) {
_setError('Failed to pause download: $e');
}
}
Future<void> resumeDownload(String downloadId) async {
try {
// TODO: Implement actual resume via API
final index = _downloads.indexWhere((d) => d['downloadId'] == downloadId);
if (index != -1) {
_downloads[index] = {..._downloads[index], 'status': 'downloading'};
}
} catch (e) {
_setError('Failed to resume download: $e');
}
}
Future<void> cancelDownload(String downloadId) async {
try {
// TODO: Implement actual cancel via API
_downloads.removeWhere((d) => d['downloadId'] == downloadId);
} catch (e) {
_setError('Failed to cancel download: $e');
}
}
Future<void> retryDownload(String downloadId) async {
try {
// TODO: Implement actual retry via API
final index = _downloads.indexWhere((d) => d['downloadId'] == downloadId);
if (index != -1) {
_downloads[index] = {..._downloads[index], 'status': 'downloading'};
}
} catch (e) {
_setError('Failed to retry download: $e');
}
}
Future<void> deleteDownload(String downloadId) async {
try {
// TODO: Implement actual delete via API
_downloads.removeWhere((d) => d['downloadId'] == downloadId);
} catch (e) {
_setError('Failed to delete download: $e');
}
}
Future<void> loadQueue() async {
_setLoadingQueue(true);
_clearError();
try {
_queue = await _apiService.getQueue();
_setLoadingQueue(false);
} catch (e) {
_setError('Failed to load queue: $e');
}
}
Future<void> addToQueue(String trackHash) async {
try {
await _apiService.addToQueue(trackHash);
// Add to queue from tracks if available
final track = _tracks.firstWhere((t) => t.trackhash == trackHash);
if (track != null && !_queue.any((q) => q.trackhash == trackHash)) {
_queue.add(track);
}
notifyListeners();
} catch (e) {
_setError('Failed to add to queue: $e');
}
}
Future<void> removeFromQueue(String trackHash) async {
try {
await _apiService.removeFromQueue(trackHash);
_queue.removeWhere((track) => track.trackhash == trackHash);
notifyListeners();
} catch (e) {
_setError('Failed to remove from queue: $e');
}
}
Future<void> clearQueue() async {
try {
await _apiService.clearQueue();
_queue.clear();
notifyListeners();
} catch (e) {
_setError('Failed to clear queue: $e');
}
}
Future<void> reorderQueue(List<String> trackHashes) async {
try {
await _apiService.reorderQueue(trackHashes);
// Reorder local queue
final reorderedQueue = <TrackModel>[];
for (final hash in trackHashes) {
final track = _queue.firstWhere((t) => t.trackhash == hash);
if (track != null) {
reorderedQueue.add(track);
}
}
_queue = reorderedQueue;
notifyListeners();
} catch (e) {
_setError('Failed to reorder queue: $e');
}
}
// Private helper methods
void _setLoadingTracks(bool loading) {
_isLoadingTracks = loading;
notifyListeners();
}
void _setLoadingAlbums(bool loading) {
_isLoadingAlbums = loading;
notifyListeners();
}
void _setLoadingArtists(bool loading) {
_isLoadingArtists = loading;
notifyListeners();
}
void _setLoadingPlaylists(bool loading) {
_isLoadingPlaylists = loading;
notifyListeners();
}
void _setLoadingFolders(bool loading) {
_isLoadingFolders = loading;
notifyListeners();
}
void _setLoadingUserInfo(bool loading) {
_isLoadingUserInfo = loading;
notifyListeners();
}
void _setLoadingStatistics(bool loading) {
_isLoadingStatistics = loading;
notifyListeners();
}
void _setLoadingFavorites(bool loading) {
_isLoadingFavorites = loading;
notifyListeners();
}
void _setLoadingQueue(bool loading) {
_isLoadingQueue = loading;
notifyListeners();
}
void _clearError() {
_error = null;
notifyListeners();
}
void _setError(String error) {
_error = error;
notifyListeners();
}
}
@@ -0,0 +1,297 @@
import 'package:flutter/foundation.dart';
import '../../data/services/api_service.dart';
import '../../data/models/track_model.dart';
import '../../data/models/album_model.dart';
import '../../data/models/playlist_model.dart';
class LibraryProvider extends ChangeNotifier {
final ApiService _apiService = ApiService();
// Library data
List<TrackModel> _tracks = [];
List<AlbumModel> _albums = [];
List<ArtistModel> _artists = [];
List<PlaylistModel> _playlists = [];
List<TrackModel> _favoriteTracks = [];
List<AlbumModel> _favoriteAlbums = [];
List<ArtistModel> _favoriteArtists = [];
// Loading states
bool _isLoadingTracks = false;
bool _isLoadingAlbums = false;
bool _isLoadingArtists = false;
bool _isLoadingPlaylists = false;
bool _isLoadingFavorites = false;
// Search state
List<TrackModel> _searchResults = [];
bool _isSearching = false;
String _searchQuery = '';
// Getters
List<TrackModel> get tracks => _tracks;
List<AlbumModel> get albums => _albums;
List<ArtistModel> get artists => _artists;
List<PlaylistModel> get playlists => _playlists;
List<TrackModel> get favoriteTracks => _favoriteTracks;
List<AlbumModel> get favoriteAlbums => _favoriteAlbums;
List<ArtistModel> get favoriteArtists => _favoriteArtists;
bool get isLoadingTracks => _isLoadingTracks;
bool get isLoadingAlbums => _isLoadingAlbums;
bool get isLoadingArtists => _isLoadingArtists;
bool get isLoadingPlaylists => _isLoadingPlaylists;
bool get isLoadingFavorites => _isLoadingFavorites;
List<TrackModel> get searchResults => _searchResults;
bool get isSearching => _isSearching;
String get searchQuery => _searchQuery;
// Tracks operations
Future<void> loadTracks({int limit = 20, int offset = 0}) async {
try {
_isLoadingTracks = true;
notifyListeners();
final response = await _apiService.getTracks(limit: limit, offset: offset);
_tracks = response.map((json) => TrackModel.fromJson(json)).toList();
_isLoadingTracks = false;
notifyListeners();
} catch (e) {
_isLoadingTracks = false;
notifyListeners();
throw Exception('Failed to load tracks: $e');
}
}
Future<void> loadMoreTracks({int limit = 20}) async {
try {
final response = await _apiService.getTracks(limit: limit, offset: _tracks.length);
final newTracks = response.map((json) => TrackModel.fromJson(json)).toList();
_tracks.addAll(newTracks);
notifyListeners();
} catch (e) {
throw Exception('Failed to load more tracks: $e');
}
}
// Albums operations
Future<void> loadAlbums({int limit = 20, int offset = 0}) async {
try {
_isLoadingAlbums = true;
notifyListeners();
final response = await _apiService.getAlbums(limit: limit, offset: offset);
_albums = response.map((json) => AlbumModel.fromJson(json)).toList();
_isLoadingAlbums = false;
notifyListeners();
} catch (e) {
_isLoadingAlbums = false;
notifyListeners();
throw Exception('Failed to load albums: $e');
}
}
Future<void> loadMoreAlbums({int limit = 20}) async {
try {
final response = await _apiService.getAlbums(limit: limit, offset: _albums.length);
final newAlbums = response.map((json) => AlbumModel.fromJson(json)).toList();
_albums.addAll(newAlbums);
notifyListeners();
} catch (e) {
throw Exception('Failed to load more albums: $e');
}
}
// Artists operations
Future<void> loadArtists({int limit = 20, int offset = 0}) async {
try {
_isLoadingArtists = true;
notifyListeners();
final response = await _apiService.getArtists(limit: limit, offset: offset);
_artists = response.map((json) => ArtistModel.fromJson(json)).toList();
_isLoadingArtists = false;
notifyListeners();
} catch (e) {
_isLoadingArtists = false;
notifyListeners();
throw Exception('Failed to load artists: $e');
}
}
Future<void> loadMoreArtists({int limit = 20}) async {
try {
final response = await _apiService.getArtists(limit: limit, offset: _artists.length);
final newArtists = response.map((json) => ArtistModel.fromJson(json)).toList();
_artists.addAll(newArtists);
notifyListeners();
} catch (e) {
throw Exception('Failed to load more artists: $e');
}
}
// Playlists operations
Future<void> loadPlaylists() async {
try {
_isLoadingPlaylists = true;
notifyListeners();
final response = await _apiService.getPlaylists();
_playlists = response.map((json) => PlaylistModel.fromJson(json)).toList();
_isLoadingPlaylists = false;
notifyListeners();
} catch (e) {
_isLoadingPlaylists = false;
notifyListeners();
throw Exception('Failed to load playlists: $e');
}
}
Future<PlaylistModel> createPlaylist(String name, {String description = ''}) async {
try {
final response = await _apiService.createPlaylist(name, description: description);
final newPlaylist = PlaylistModel.fromJson(response);
_playlists.add(newPlaylist);
notifyListeners();
return newPlaylist;
} catch (e) {
throw Exception('Failed to create playlist: $e');
}
}
// Favorites operations
Future<void> loadFavorites() async {
try {
_isLoadingFavorites = true;
notifyListeners();
final tracksResponse = await _apiService.getFavoriteTracks();
final albumsResponse = await _apiService.getFavoriteAlbums();
final artistsResponse = await _apiService.getFavoriteArtists();
_favoriteTracks = tracksResponse.map((json) => TrackModel.fromJson(json)).toList();
_favoriteAlbums = albumsResponse.map((json) => AlbumModel.fromJson(json)).toList();
_favoriteArtists = artistsResponse.map((json) => ArtistModel.fromJson(json)).toList();
_isLoadingFavorites = false;
notifyListeners();
} catch (e) {
_isLoadingFavorites = false;
notifyListeners();
throw Exception('Failed to load favorites: $e');
}
}
Future<void> toggleFavoriteTrack(String trackhash) async {
try {
await _apiService.toggleFavoriteTrack(trackhash);
// Update local state
final trackIndex = _favoriteTracks.indexWhere((track) => track.trackhash == trackhash);
if (trackIndex != -1) {
_favoriteTracks.removeAt(trackIndex);
} else {
final track = _tracks.firstWhere((t) => t.trackhash == trackhash);
_favoriteTracks.add(track);
}
notifyListeners();
} catch (e) {
throw Exception('Failed to toggle favorite track: $e');
}
}
Future<void> toggleFavoriteAlbum(String albumhash) async {
try {
await _apiService.toggleFavoriteAlbum(albumhash);
// Update local state
final albumIndex = _favoriteAlbums.indexWhere((album) => album.albumhash == albumhash);
if (albumIndex != -1) {
_favoriteAlbums.removeAt(albumIndex);
} else {
final album = _albums.firstWhere((a) => a.albumhash == albumhash);
_favoriteAlbums.add(album);
}
notifyListeners();
} catch (e) {
throw Exception('Failed to toggle favorite album: $e');
}
}
Future<void> toggleFavoriteArtist(String artisthash) async {
try {
await _apiService.toggleFavoriteArtist(artisthash);
// Update local state
final artistIndex = _favoriteArtists.indexWhere((artist) => artist.artisthash == artisthash);
if (artistIndex != -1) {
_favoriteArtists.removeAt(artistIndex);
} else {
final artist = _artists.firstWhere((a) => a.artisthash == artisthash);
_favoriteArtists.add(artist);
}
notifyListeners();
} catch (e) {
throw Exception('Failed to toggle favorite artist: $e');
}
}
// Search operations
Future<void> searchTracks(String query, {int limit = 15}) async {
if (query.isEmpty) {
_searchResults.clear();
_searchQuery = '';
notifyListeners();
return;
}
try {
_isSearching = true;
_searchQuery = query;
notifyListeners();
final response = await _apiService.searchTracks(query, limit: limit);
_searchResults = response.map((json) => TrackModel.fromJson(json)).toList();
_isSearching = false;
notifyListeners();
} catch (e) {
_isSearching = false;
notifyListeners();
throw Exception('Failed to search tracks: $e');
}
}
void clearSearch() {
_searchResults.clear();
_searchQuery = '';
_isSearching = false;
notifyListeners();
}
// Utility methods
void refresh() {
loadTracks();
loadAlbums();
loadArtists();
loadPlaylists();
loadFavorites();
}
bool isTrackFavorite(String trackhash) {
return _favoriteTracks.any((track) => track.trackhash == trackhash);
}
bool isAlbumFavorite(String albumhash) {
return _favoriteAlbums.any((album) => album.albumhash == albumhash);
}
bool isArtistFavorite(String artisthash) {
return _favoriteArtists.any((artist) => artist.artisthash == artisthash);
}
}
@@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../features/player/enhanced_player_screen_new.dart';
import '../../features/library/library_screen.dart';
import '../../features/search/search_screen.dart';
import '../../features/playlists/playlists_screen.dart';
import '../../features/settings/enhanced_settings_screen.dart';
import '../../features/auth/enhanced_auth_screen.dart';
import '../../features/downloads/downloads_screen.dart';
import '../../features/analytics/analytics_screen.dart';
import '../../features/home/home_screen.dart';
import '../widgets/main_navigation.dart';
import '../../core/constants/app_constants.dart';
class AppRouter {
static final GoRouter _router = GoRouter(
initialLocation: AppConstants.homeRoute,
routes: [
ShellRoute(
builder: (context, state, child) {
return MainNavigation(child: child);
},
routes: [
GoRoute(
path: AppConstants.homeRoute,
name: 'home',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: AppConstants.libraryRoute,
name: 'library',
builder: (context, state) => const LibraryScreen(),
),
GoRoute(
path: AppConstants.playerRoute,
name: 'player',
builder: (context, state) => const EnhancedPlayerScreen(),
),
GoRoute(
path: AppConstants.searchRoute,
name: 'search',
builder: (context, state) => const SearchScreen(),
),
GoRoute(
path: AppConstants.playlistsRoute,
name: 'playlists',
builder: (context, state) => const PlaylistsScreen(),
),
GoRoute(
path: AppConstants.authRoute,
name: 'auth',
builder: (context, state) => const EnhancedAuthScreen(),
),
GoRoute(
path: '/downloads',
name: 'downloads',
builder: (context, state) => const DownloadsScreen(),
),
GoRoute(
path: AppConstants.analyticsRoute,
name: 'analytics',
builder: (context, state) => const AnalyticsScreen(),
),
GoRoute(
path: AppConstants.settingsRoute,
name: 'settings',
builder: (context, state) => const EnhancedSettingsScreen(),
),
],
),
],
errorBuilder: (context, state) => Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
const SizedBox(height: 16),
Text(
'Page not found: ${state.uri}',
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
],
),
),
),
);
static GoRouter get router => _router;
}
@@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../core/constants/app_constants.dart';
import '../../core/constants/app_icons.dart';
import '../../core/widgets/mini_player.dart';
class MainNavigation extends StatefulWidget {
final Widget child;
const MainNavigation({
super.key,
required this.child,
});
@override
State<MainNavigation> createState() => _MainNavigationState();
}
class _MainNavigationState extends State<MainNavigation> {
int _currentIndex = 0;
final List<NavigationItem> _navigationItems = [
NavigationItem(
icon: AppIcons.home,
selectedIcon: AppIcons.homeFilled,
label: 'Home',
route: AppConstants.homeRoute,
),
NavigationItem(
icon: AppIcons.search,
selectedIcon: AppIcons.searchFilled,
label: 'Search',
route: AppConstants.searchRoute,
),
NavigationItem(
icon: AppIcons.library,
selectedIcon: AppIcons.libraryFilled,
label: 'Library',
route: AppConstants.libraryRoute,
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: widget.child,
),
const MiniPlayer(),
],
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Theme.of(context).shadowColor.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _navigationItems.map((item) {
final isSelected = _currentIndex == _navigationItems.indexOf(item);
return Expanded(
child: InkWell(
onTap: () => _onItemTapped(_navigationItems.indexOf(item)),
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isSelected ? item.selectedIcon : item.icon,
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
size: 24,
),
const SizedBox(height: 4),
Text(
item.label,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
fontSize: 12,
),
),
],
),
),
),
);
}).toList(),
),
),
),
),
);
}
void _onItemTapped(int index) {
final item = _navigationItems[index];
if (_currentIndex != index) {
setState(() {
_currentIndex = index;
});
context.go(item.route);
}
}
}
class NavigationItem {
final IconData icon;
final IconData selectedIcon;
final String label;
final String route;
NavigationItem({
required this.icon,
required this.selectedIcon,
required this.label,
required this.route,
});
}