v2.56 — UI/Design-Rework: Melo Dark Fusion (neue Startseite, 2-stufige Einstellungen, Design-System)

- Design-System: MeloDesignTokens ThemeExtension, Outfit-Font, Farben #0B0B10/#14141C/#1C1C26
- Akzentfarbe-User-Einstellung behalten (MeloTheme.akzentNotifier), Gradient aus Akzent
- Startseite: Hero-Weiterhören-Karte mit Cover, Titel, Glow-Play-Button
- 2-stufige Einstellungen: Standard + Expertenmodus (SharedPreferences Key 'experten_modus')
- Komponenten: Pill-Buttons (radius 999), Card-Radius 20, Glow-Shadow, PressScale-Widget
- Login: Gradient-Button mit Akzent-Glow
- 199 Tests grün (194 bestehend + 5 neu): ThemeExtension, Expertenmodus, Akzent, PressScale, Hero
- flutter analyze lib/ + test/ = 0 Issues
- KEIN APK
This commit is contained in:
Dustin
2026-08-06 19:56:02 +02:00
parent 85615d253c
commit 79b15a3cb8
14 changed files with 1078 additions and 364 deletions
+24 -18
View File
@@ -40,11 +40,9 @@ class _MiniPlayerState extends State<MiniPlayer> {
});
}
});
// Sofortige UI-Aktualisierung bei jedem Songwechsel
_songSub = _player.onSongWechsel.listen((_) {
if (mounted) setState(() {});
});
// Shuffle-/Repeat-/Sleep-Zustand live halten
_player.addListener(_onPlayerChanged);
}
@@ -52,8 +50,6 @@ class _MiniPlayerState extends State<MiniPlayer> {
if (mounted) setState(() {});
}
/// Öffnet den Fullscreen-Player mit Slide-up/Slide-down-Animation.
/// Die Warteschlange ist von dort per Button erreichbar.
void _oeffneNowPlaying() {
Navigator.of(context).push(
PageRouteBuilder(
@@ -94,15 +90,23 @@ class _MiniPlayerState extends State<MiniPlayer> {
final progress = _dauer.inSeconds > 0
? _position.inSeconds / _dauer.inSeconds : 0.0;
final hatCover = song.coverPfad != null && File(song.coverPfad!).existsSync();
final akzent = MeloTheme.akzent;
return GestureDetector(
onTap: _oeffneNowPlaying,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: const Color(0xFF1A0A0A),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF2A1515)),
color: MeloTheme.dunkel1,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: MeloTheme.dunkel2),
boxShadow: [
BoxShadow(
color: akzent.withValues(alpha: 0.15),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -113,13 +117,17 @@ class _MiniPlayerState extends State<MiniPlayer> {
children: [
// Cover mit Fallback auf Notensymbol
ClipRRect(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(12),
child: Container(
width: 36, height: 36,
color: MeloTheme.rot,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [akzent, Color.lerp(akzent, Colors.black, 0.5)!],
),
),
child: hatCover
? Image.file(File(song.coverPfad!), fit: BoxFit.cover)
: const Center(child: Text('', style: TextStyle(fontSize: 16))),
: const Center(child: Text('', style: TextStyle(fontSize: 16, color: Colors.white))),
),
),
const SizedBox(width: 10),
@@ -151,8 +159,8 @@ class _MiniPlayerState extends State<MiniPlayer> {
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: progress,
backgroundColor: const Color(0xFF2A2A2A),
valueColor: const AlwaysStoppedAnimation(MeloTheme.rot),
backgroundColor: MeloTheme.dunkel2,
valueColor: AlwaysStoppedAnimation(akzent),
minHeight: 2,
),
),
@@ -163,23 +171,21 @@ class _MiniPlayerState extends State<MiniPlayer> {
);
}
/// Shuffle-Icon — rot wenn aktiv
Widget _shuffleBtn() {
final aktiv = _player.zufallsmodus;
return _btn(
Icons.shuffle,
() => _player.setZufallsmodus(!aktiv),
farbe: aktiv ? MeloTheme.rot : Colors.white,
farbe: aktiv ? MeloTheme.akzent : Colors.white,
);
}
/// Repeat-Icon — 3 Zustände (aus → Titel → Playlist), Wechsel bei Tipp
Widget _repeatBtn() {
final modus = _player.wiederholmodus;
final (icon, farbe) = switch (modus) {
Wiederholmodus.aus => (Icons.repeat, Colors.white),
Wiederholmodus.titel => (Icons.repeat_one, MeloTheme.rot),
Wiederholmodus.playlist => (Icons.repeat, MeloTheme.rot),
Wiederholmodus.titel => (Icons.repeat_one, MeloTheme.akzent),
Wiederholmodus.playlist => (Icons.repeat, MeloTheme.akzent),
};
return _btn(icon, _naechsterWiederholmodus, farbe: farbe);
}
@@ -210,7 +216,7 @@ class _MiniPlayerState extends State<MiniPlayer> {
Widget _playBtn() {
return Material(
color: MeloTheme.rot,
color: MeloTheme.akzent,
borderRadius: BorderRadius.circular(50),
child: InkWell(
borderRadius: BorderRadius.circular(50),