import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:provider/provider.dart'; import 'dart:io' as io; import 'dart:convert'; import '../services/music_player_service.dart'; import '../widgets/bottom_sheet_music_player.dart'; class MusicLibraryScreen extends StatefulWidget { const MusicLibraryScreen({super.key}); @override State createState() => _MusicLibraryScreenState(); } class _MusicLibraryScreenState extends State { List _musicTracks = []; bool _isLoading = true; @override void initState() { super.initState(); _loadMusicTracks(); } Future _loadMusicTracks() async { setState(() { _isLoading = true; }); try { final prefs = await SharedPreferences.getInstance(); final fileIdMap = prefs.getStringList('file_id_to_path_map') ?? []; final List tracks = []; final musicMetadataJson = prefs.getString('music_metadata') ?? '{}'; final Map musicMetadata = jsonDecode(musicMetadataJson); for (final mapping in fileIdMap) { final parts = mapping.split(':'); if (parts.length >= 2) { final fileId = parts[0]; final filePath = parts.skip(1).join(':'); final file = io.File(filePath); if (await file.exists()) { final extension = filePath.split('.').last.toLowerCase(); if ([ 'mp3', 'wav', 'flac', 'm4a', 'aac', 'ogg', ].contains(extension)) { final metadata = musicMetadata[fileId] as Map?; if (metadata != null) { tracks.add( MusicTrack.fromJson({ ...metadata, 'filePath': filePath, 'fileId': int.tryParse(fileId), }), ); } else { final fileName = filePath.split('/').last; final nameWithoutExt = fileName.substring( 0, fileName.lastIndexOf('.'), ); tracks.add( MusicTrack( id: fileId, title: nameWithoutExt, artist: 'Unknown Artist', filePath: filePath, fileId: int.tryParse(fileId), ), ); } } } } } tracks.sort((a, b) => a.title.compareTo(b.title)); setState(() { _musicTracks = tracks; _isLoading = false; }); } catch (e) { print('Error loading music tracks: $e'); setState(() { _isLoading = false; }); } } Future _playTrack(MusicTrack track) async { final musicPlayer = MusicPlayerService(); await musicPlayer.playTrack(track, playlist: _musicTracks); } String _formatFileSize(int bytes) { if (bytes < 1024) { return '$bytes B'; } else if (bytes < 1024 * 1024) { return '${(bytes / 1024).toStringAsFixed(1)} KB'; } else if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } else { return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; } } String _formatDuration(int? milliseconds) { if (milliseconds == null) return '--:--'; final duration = Duration(milliseconds: milliseconds); final minutes = duration.inMinutes; final seconds = duration.inSeconds.remainder(60); return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } // If you're touching ( you doing this rignt now ) this file, STOP IMEDIATLY! This is violiating TeamKomet code revision policy! // If you're touching ( you doing this rignt now ) this file, AND DONT WANT TO STOP! REFACTOR IT IMMEDIATLY TO MAKE SOME SENSE AND DONT LOOK LIKE SHIT BY AI @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final musicPlayer = context.watch(); return ValueListenableBuilder( valueListenable: BottomSheetMusicPlayer.isExpandedNotifier, builder: (context, isPlayerExpanded, child) { return ValueListenableBuilder( valueListenable: BottomSheetMusicPlayer.isFullscreenNotifier, builder: (context, isFullscreen, _) { return PopScope( canPop: !isPlayerExpanded, onPopInvoked: (didPop) { if (!didPop && isPlayerExpanded) { BottomSheetMusicPlayer.isExpandedNotifier.value = false; BottomSheetMusicPlayer.isFullscreenNotifier.value = false; } }, child: Scaffold( appBar: isFullscreen ? null : AppBar(title: const Text('Музыка')), body: Stack( children: [ _isLoading ? const Center(child: CircularProgressIndicator()) : _musicTracks.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.music_off, size: 64, color: colorScheme.onSurface.withOpacity(0.5), ), const SizedBox(height: 16), Text( 'Нет музыки', style: theme.textTheme.titleLarge?.copyWith( color: colorScheme.onSurface.withOpacity(0.7), ), ), const SizedBox(height: 8), Text( 'Скачайте музыку из чатов', style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurface.withOpacity(0.5), ), ), ], ), ) : ListView.builder( padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: musicPlayer.currentTrack != null ? 120 : 16, ), itemCount: _musicTracks.length, itemBuilder: (context, index) { final track = _musicTracks[index]; return Card( margin: const EdgeInsets.only(bottom: 12), child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), leading: ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( width: 56, height: 56, color: colorScheme.primaryContainer, child: track.albumArtUrl != null ? Image.network( track.albumArtUrl!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Icon( Icons.music_note, color: colorScheme .onPrimaryContainer, ), ) : Icon( Icons.music_note, color: colorScheme.onPrimaryContainer, ), ), ), title: Text( track.title, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Text( track.artist, style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurface.withOpacity( 0.7, ), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (track.album != null) ...[ const SizedBox(height: 2), Text( track.album!, style: theme.textTheme.bodySmall ?.copyWith( color: colorScheme.onSurface .withOpacity(0.5), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], const SizedBox(height: 4), Row( children: [ if (track.duration != null) ...[ Text( _formatDuration(track.duration), style: theme.textTheme.bodySmall ?.copyWith( color: colorScheme.onSurface .withOpacity(0.5), ), ), const SizedBox(width: 8), Text( '•', style: theme.textTheme.bodySmall ?.copyWith( color: colorScheme.onSurface .withOpacity(0.5), ), ), const SizedBox(width: 8), ], if (track.filePath != null) ...[ FutureBuilder( future: io.File( track.filePath!, ).stat(), builder: (context, snapshot) { if (snapshot.hasData) { return Text( _formatFileSize( snapshot.data!.size, ), style: theme.textTheme.bodySmall ?.copyWith( color: colorScheme .onSurface .withOpacity(0.5), ), ); } return const SizedBox.shrink(); }, ), ], ], ), ], ), trailing: IconButton( onPressed: () => _playTrack(track), icon: const Icon(Icons.play_arrow), style: IconButton.styleFrom( backgroundColor: colorScheme.primaryContainer, foregroundColor: colorScheme.onPrimaryContainer, ), ), onTap: () => _playTrack(track), ), ); }, ), if (musicPlayer.currentTrack != null) const Positioned( bottom: 0, left: 0, right: 0, child: BottomSheetMusicPlayer(), ), ], ), ), ); }, ); }, ); } }