Files
fuckKomet/lib/screens/music_library_screen.dart
2025-11-22 17:29:31 +03:00

330 lines
14 KiB
Dart

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<MusicLibraryScreen> createState() => _MusicLibraryScreenState();
}
class _MusicLibraryScreenState extends State<MusicLibraryScreen> {
List<MusicTrack> _musicTracks = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadMusicTracks();
}
Future<void> _loadMusicTracks() async {
setState(() {
_isLoading = true;
});
try {
final prefs = await SharedPreferences.getInstance();
final fileIdMap = prefs.getStringList('file_id_to_path_map') ?? [];
final List<MusicTrack> tracks = [];
final musicMetadataJson = prefs.getString('music_metadata') ?? '{}';
final Map<String, dynamic> 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<String, dynamic>?;
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<void> _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')}';
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final musicPlayer = context.watch<MusicPlayerService>();
return ValueListenableBuilder<bool>(
valueListenable: BottomSheetMusicPlayer.isExpandedNotifier,
builder: (context, isPlayerExpanded, child) {
return PopScope(
canPop: !isPlayerExpanded,
onPopInvoked: (didPop) {
if (!didPop && isPlayerExpanded) {
BottomSheetMusicPlayer.isExpandedNotifier.value = false;
}
},
child: Scaffold(
appBar: 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<io.FileStat>(
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(),
),
],
),
),
);
},
);
}
}