feat(ui): finales 5-Tab-Layout (Player/Bibliothek/Playlisten/Suche/Settings)
- "Zuletzt hinzugefügt" aus dem alten HomeScreen in die Bibliothek verschoben: horizontal scrollende Leiste über der vollen Songliste, nur sichtbar wenn nicht leer und nicht gerade gescannt wird - home_screen.dart entfernt (Funktionalität jetzt Teil von LibraryScreen) - HomeShell-Tabs auf die finale Struktur umgestellt: NowPlayingScreen an Position 0, Playlisten- und Settings-Platzhalter an ihren finalen Positionen (werden in späteren Tasks durch echte Screens ersetzt) - BottomNavigationBar-Icons/Labels an die neue Reihenfolge angepasst - MiniPlayer wird ausgeblendet, solange der Now-Playing-Tab aktiv ist (redundant, da der Tab selbst schon die volle Wiedergabe-Ansicht zeigt) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
be1248d7ba
commit
3a040b4661
@@ -1,76 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'library/database.dart';
|
|
||||||
import 'library/library_service.dart';
|
|
||||||
import 'library/song_list.dart';
|
|
||||||
|
|
||||||
/// Startseite: zuletzt hinzugefügte Songs, sonst ein Willkommens-Hinweis.
|
|
||||||
class HomeScreen extends StatelessWidget {
|
|
||||||
const HomeScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final db = context.read<MeloDb>();
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Melo',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26)),
|
|
||||||
),
|
|
||||||
body: StreamBuilder<List<Song>>(
|
|
||||||
stream: db.watchRecent(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return Center(
|
|
||||||
child: Text('Fehler: ${snapshot.error}',
|
|
||||||
style: const TextStyle(color: Colors.white54)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final songs = snapshot.data ?? const [];
|
|
||||||
if (songs.isEmpty) return const _Welcome();
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
|
|
||||||
child: Text('Zuletzt hinzugefügt',
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
|
||||||
),
|
|
||||||
Expanded(child: SongList(songs)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Welcome extends StatelessWidget {
|
|
||||||
const _Welcome();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final lib = context.read<LibraryService>();
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.music_note, size: 72, color: Colors.white24),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const Text('Willkommen bei Melo',
|
|
||||||
style: TextStyle(fontSize: 18)),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
const Text('Füge deine Musik hinzu, um loszulegen',
|
|
||||||
style: TextStyle(color: Colors.white54)),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
FilledButton.icon(
|
|
||||||
icon: const Icon(Icons.create_new_folder_outlined),
|
|
||||||
label: Text(addMusicLabel),
|
|
||||||
onPressed: lib.pickFolderAndScan,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../player/audio_handler.dart';
|
||||||
|
import '../shared/cover.dart';
|
||||||
import 'database.dart';
|
import 'database.dart';
|
||||||
import 'library_service.dart';
|
import 'library_service.dart';
|
||||||
import 'permissions.dart';
|
import 'permissions.dart';
|
||||||
import 'song_list.dart';
|
import 'song_list.dart';
|
||||||
|
import 'song_media.dart';
|
||||||
|
|
||||||
class LibraryScreen extends StatelessWidget {
|
class LibraryScreen extends StatelessWidget {
|
||||||
const LibraryScreen({super.key});
|
const LibraryScreen({super.key});
|
||||||
@@ -69,6 +72,14 @@ class LibraryScreen extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
StreamBuilder<List<Song>>(
|
||||||
|
stream: db.watchRecent(limit: 10),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final recent = snapshot.data ?? const [];
|
||||||
|
if (recent.isEmpty || lib.scanning) return const SizedBox.shrink();
|
||||||
|
return _RecentlyAdded(songs: recent);
|
||||||
|
},
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StreamBuilder<List<Song>>(
|
child: StreamBuilder<List<Song>>(
|
||||||
stream: db.watchSongs(),
|
stream: db.watchSongs(),
|
||||||
@@ -93,6 +104,76 @@ class LibraryScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Horizontal scrollende Leiste mit den zuletzt hinzugefügten Songs.
|
||||||
|
class _RecentlyAdded extends StatelessWidget {
|
||||||
|
const _RecentlyAdded({required this.songs});
|
||||||
|
final List<Song> songs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final handler = context.read<MeloAudioHandler>();
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||||
|
child: Text('Zuletzt hinzugefügt',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 140,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
itemCount: songs.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final s = songs[i];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 96,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
await playSongs(handler, songs, i);
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Wiedergabe fehlgeschlagen: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CoverImage(
|
||||||
|
artUri:
|
||||||
|
s.coverPath != null ? Uri.file(s.coverPath!) : null,
|
||||||
|
size: 96,
|
||||||
|
radius: 8,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
s.title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _Empty extends StatelessWidget {
|
class _Empty extends StatelessWidget {
|
||||||
const _Empty();
|
const _Empty();
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -2,7 +2,6 @@ import 'package:audio_service/audio_service.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'home_screen.dart';
|
|
||||||
import 'library/database.dart';
|
import 'library/database.dart';
|
||||||
import 'library/library_screen.dart';
|
import 'library/library_screen.dart';
|
||||||
import 'library/library_service.dart';
|
import 'library/library_service.dart';
|
||||||
@@ -10,6 +9,7 @@ import 'library/playlist_service.dart';
|
|||||||
import 'library/search_screen.dart';
|
import 'library/search_screen.dart';
|
||||||
import 'player/audio_handler.dart';
|
import 'player/audio_handler.dart';
|
||||||
import 'player/mini_player.dart';
|
import 'player/mini_player.dart';
|
||||||
|
import 'player/now_playing_screen.dart';
|
||||||
import 'shared/theme.dart';
|
import 'shared/theme.dart';
|
||||||
|
|
||||||
late final MeloAudioHandler _handler;
|
late final MeloAudioHandler _handler;
|
||||||
@@ -66,11 +66,11 @@ class _HomeShellState extends State<HomeShell> {
|
|||||||
int _index = 0;
|
int _index = 0;
|
||||||
|
|
||||||
static const _tabs = <Widget>[
|
static const _tabs = <Widget>[
|
||||||
HomeScreen(),
|
NowPlayingScreen(),
|
||||||
LibraryScreen(),
|
LibraryScreen(),
|
||||||
|
_Placeholder(label: 'Playlisten', hint: 'Kommt in Kürze'),
|
||||||
SearchScreen(),
|
SearchScreen(),
|
||||||
_Placeholder(label: 'Playlists', hint: 'Playlists & Favoriten (P3)'),
|
_Placeholder(label: 'Settings', hint: 'Kommt in Kürze'),
|
||||||
_Placeholder(label: 'Recaps', hint: 'Wöchentlich/Monatlich/Jährlich (P5)'),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -79,20 +79,21 @@ class _HomeShellState extends State<HomeShell> {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: IndexedStack(index: _index, children: _tabs)),
|
Expanded(child: IndexedStack(index: _index, children: _tabs)),
|
||||||
const MiniPlayer(),
|
if (_index != 0) const MiniPlayer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: _index,
|
currentIndex: _index,
|
||||||
onTap: (i) => setState(() => _index = i),
|
onTap: (i) => setState(() => _index = i),
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.play_circle_outline), label: 'Player'),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.library_music), label: 'Bibliothek'),
|
icon: Icon(Icons.library_music), label: 'Bibliothek'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suche'),
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.playlist_play), label: 'Playlists'),
|
icon: Icon(Icons.playlist_play), label: 'Playlisten'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.bar_chart), label: 'Recaps'),
|
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suche'),
|
||||||
|
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user