mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
408 lines
14 KiB
Dart
408 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../shared/providers/audio_provider.dart';
|
|
import '../../shared/providers/library_provider.dart';
|
|
import '../../core/widgets/album_card.dart';
|
|
import '../../core/widgets/track_list_tile.dart';
|
|
import '../../data/models/track_model.dart';
|
|
|
|
class HomeScreen extends StatefulWidget {
|
|
const HomeScreen({super.key});
|
|
|
|
@override
|
|
State<HomeScreen> createState() => _HomeScreenState();
|
|
}
|
|
|
|
class _HomeScreenState extends State<HomeScreen> {
|
|
late LibraryProvider _libraryProvider;
|
|
late AudioProvider _audioProvider;
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_libraryProvider = Provider.of<LibraryProvider>(context, listen: false);
|
|
_audioProvider = Provider.of<AudioProvider>(context, listen: false);
|
|
_loadHomeData();
|
|
}
|
|
|
|
Future<void> _loadHomeData() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
// Load recent data from library
|
|
await Future.wait([
|
|
_libraryProvider.loadTracks(limit: 10),
|
|
_libraryProvider.loadAlbums(limit: 5),
|
|
_libraryProvider.loadArtists(limit: 5),
|
|
]);
|
|
} catch (e) {
|
|
// Handle error silently for now
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: CustomScrollView(
|
|
slivers: [
|
|
// App Bar
|
|
SliverAppBar(
|
|
floating: true,
|
|
snap: true,
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
elevation: 0,
|
|
title: Text(
|
|
'SwingMusic',
|
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
onPressed: () {
|
|
// Show notifications
|
|
},
|
|
icon: const Icon(Icons.notifications_outlined),
|
|
),
|
|
PopupMenuButton<String>(
|
|
icon: CircleAvatar(
|
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
|
child: Icon(
|
|
Icons.person,
|
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
|
),
|
|
),
|
|
onSelected: (value) {
|
|
if (value == 'settings') {
|
|
Navigator.pushNamed(context, '/settings');
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
const PopupMenuItem(
|
|
value: 'settings',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.settings),
|
|
SizedBox(width: 8),
|
|
Text('Settings'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// Quick Actions Section
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Quick Actions',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildQuickActionCard(
|
|
icon: Icons.search,
|
|
label: 'Search',
|
|
color: Theme.of(context).colorScheme.primary,
|
|
onTap: () {
|
|
Navigator.pushNamed(context, '/search');
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildQuickActionCard(
|
|
icon: Icons.library_music,
|
|
label: 'Library',
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
onTap: () {
|
|
Navigator.pushNamed(context, '/library');
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildQuickActionCard(
|
|
icon: Icons.favorite,
|
|
label: 'Favorites',
|
|
color: Colors.red,
|
|
onTap: () {
|
|
Navigator.pushNamed(context, '/library');
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildQuickActionCard(
|
|
icon: Icons.download,
|
|
label: 'Downloads',
|
|
color: Colors.green,
|
|
onTap: () {
|
|
// Navigate to downloads
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Recently Played Section
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Recently Played',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, '/library');
|
|
},
|
|
child: const Text('See all'),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (_isLoading)
|
|
const Center(child: CircularProgressIndicator())
|
|
else if (_libraryProvider.tracks.isEmpty)
|
|
_buildEmptySection('No recently played tracks')
|
|
else
|
|
SizedBox(
|
|
height: 200,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: _libraryProvider.tracks.length.clamp(0, 10),
|
|
itemBuilder: (context, index) {
|
|
final track = _libraryProvider.tracks[index];
|
|
return Container(
|
|
width: 160,
|
|
margin: const EdgeInsets.only(right: 12),
|
|
child: TrackListTile(
|
|
track: track,
|
|
onTap: () => _playTrack(track),
|
|
onPlay: () => _playTrack(track),
|
|
showAlbumArt: true,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Recent Albums Section
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Recent Albums',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, '/library');
|
|
},
|
|
child: const Text('See all'),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (_isLoading)
|
|
const Center(child: CircularProgressIndicator())
|
|
else if (_libraryProvider.albums.isEmpty)
|
|
_buildEmptySection('No recent albums')
|
|
else
|
|
GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 2,
|
|
childAspectRatio: 0.8,
|
|
crossAxisSpacing: 16,
|
|
mainAxisSpacing: 16,
|
|
),
|
|
itemCount: _libraryProvider.albums.length.clamp(0, 4),
|
|
itemBuilder: (context, index) {
|
|
final album = _libraryProvider.albums[index];
|
|
return AlbumCard(
|
|
album: album,
|
|
onTap: () {
|
|
// Navigate to album details
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Top Artists Section
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Top Artists',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (_isLoading)
|
|
const Center(child: CircularProgressIndicator())
|
|
else if (_libraryProvider.artists.isEmpty)
|
|
_buildEmptySection('No top artists')
|
|
else
|
|
ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: _libraryProvider.artists.length.clamp(0, 5),
|
|
itemBuilder: (context, index) {
|
|
final artist = _libraryProvider.artists[index];
|
|
return ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
|
child: Text(
|
|
artist.name.isNotEmpty ? artist.name[0].toUpperCase() : '?',
|
|
style: TextStyle(
|
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
title: Text(artist.name),
|
|
trailing: const Icon(Icons.arrow_forward_ios),
|
|
onTap: () {
|
|
// Navigate to artist details
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Bottom padding
|
|
const SliverToBoxAdapter(
|
|
child: SizedBox(height: 100), // Space for mini player
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildQuickActionCard({
|
|
required IconData icon,
|
|
required String label,
|
|
required Color color,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return Card(
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: color,
|
|
size: 28,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
label,
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptySection(String message) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(32),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(
|
|
Icons.music_note_outlined,
|
|
size: 48,
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
message,
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _playTrack(TrackModel track) {
|
|
_audioProvider.setQueue([track]);
|
|
_audioProvider.loadTrack(track);
|
|
_audioProvider.play();
|
|
|
|
Navigator.pushNamed(context, '/player');
|
|
}
|
|
}
|