diff --git a/assets/sounds/baka_fanfare.wav b/assets/sounds/baka_fanfare.wav new file mode 100644 index 0000000..2ae4b70 Binary files /dev/null and b/assets/sounds/baka_fanfare.wav differ diff --git a/assets/sounds/tinker_chime.wav b/assets/sounds/tinker_chime.wav new file mode 100644 index 0000000..c1bb256 Binary files /dev/null and b/assets/sounds/tinker_chime.wav differ diff --git a/assets/sounds/willkommen_ping.wav b/assets/sounds/willkommen_ping.wav new file mode 100644 index 0000000..5e98d41 Binary files /dev/null and b/assets/sounds/willkommen_ping.wav differ diff --git a/lib/main.dart b/lib/main.dart index f891c54..1d6952d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:audio_service/audio_service.dart'; import 'database/db_helper.dart'; import 'services/favoriten_service.dart'; import 'services/melo_logger.dart'; +import 'services/audio_handler.dart'; import 'utils/farb_theme.dart'; import 'screens/home_screen.dart'; @@ -9,12 +11,21 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); // Logger startet sofort – zeichnet ALLES auf - MeloLogger().init('2.7'); + MeloLogger().init('2.38'); try { await DbHelper().db; await FavoritenService().init(); - MeloLogger().zustand('start_ok', {'db': 'ok'}); + // Hintergrund-Audio-Service starten + await AudioService.init( + builder: () => MeloAudioHandler(), + config: const AudioServiceConfig( + androidNotificationChannelId: 'de.baka.melo.audio', + androidNotificationChannelName: 'Melo Wiedergabe', + androidNotificationOngoing: true, + ), + ); + MeloLogger().zustand('start_ok', {'db': 'ok', 'audio': 'ok'}); } catch (e, stack) { MeloLogger().fehler('App-Start', e, stack); } @@ -27,11 +38,15 @@ class MeloApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Melo', - debugShowCheckedModeBanner: false, - theme: MeloTheme.theme, - home: const Scaffold(body: MeloHome()), + // Reagiert live auf Akzent-Änderungen (Login-Effekt pro Person) + return ValueListenableBuilder( + valueListenable: MeloTheme.akzentNotifier, + builder: (context, akzent, _) => MaterialApp( + title: 'Melo', + debugShowCheckedModeBanner: false, + theme: MeloTheme.theme, + home: const Scaffold(body: MeloHome()), + ), ); } } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 0754a81..2482583 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -6,6 +6,8 @@ import '../viewmodels/melo_home_viewmodel.dart'; import '../models/song.dart'; import '../models/playlist.dart'; import '../utils/farb_theme.dart'; +import '../utils/user_effekte.dart'; +import '../services/cloud_service.dart'; import '../widgets/mini_player.dart'; import '../widgets/melo_header.dart'; import '../widgets/statistik_card.dart'; @@ -18,7 +20,6 @@ import 'cloud_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:permission_handler/permission_handler.dart'; import '../widgets/cloud_einstellungen.dart'; -import '../services/cloud_service.dart'; import '../config/app_config.dart'; class MeloHome extends StatefulWidget { @@ -50,6 +51,15 @@ class _MeloHomeState extends State { setState(() => _nutzer = n); _vm.setzeNutzer(n); } + // Login-Effekt beim App-Start: nur wenn ein Token vorhanden ist (eingeloggt) + final ok = await CloudService().restoreLogin(); + if (ok && mounted) { + UserEffekt.anwenden(_nutzer); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(UserEffekt.fuer(_nutzer).begruessung), + duration: const Duration(seconds: 3), + )); + } } void _zeigeProfil() { @@ -153,6 +163,13 @@ class _MeloHomeState extends State { await p.setString('melo_nutzer', name); if (mounted) setState(() => _nutzer = name); _vm.setzeNutzer(name); + // Login-Effekt: Akzentfarbe + Sound + Begrüßung pro Person + UserEffekt.anwenden(name); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(UserEffekt.fuer(name).begruessung), + duration: const Duration(seconds: 3))); + } } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( diff --git a/lib/utils/farb_theme.dart b/lib/utils/farb_theme.dart index 956ea97..a0e2259 100644 --- a/lib/utils/farb_theme.dart +++ b/lib/utils/farb_theme.dart @@ -9,12 +9,18 @@ class MeloTheme { static const Color textPrimaer = Color(0xFFFFFFFF); static const Color textSekundaer = Color(0xFF888888); + /// Akzentfarbe – wechselt pro angemeldeter Person (Login-Effekt). + /// UI hängt per ValueListenableBuilder an [akzentNotifier]. + static final ValueNotifier akzentNotifier = ValueNotifier(rot); + static Color get akzent => akzentNotifier.value; + static set akzent(Color farbe) => akzentNotifier.value = farbe; + static ThemeData get theme => ThemeData( brightness: Brightness.dark, scaffoldBackgroundColor: schwarz, - colorScheme: const ColorScheme.dark( - primary: rot, - secondary: rot, + colorScheme: ColorScheme.dark( + primary: akzent, + secondary: akzent, surface: schwarz, ), appBarTheme: const AppBarTheme( @@ -22,9 +28,9 @@ class MeloTheme { foregroundColor: textPrimaer, elevation: 0, ), - bottomNavigationBarTheme: const BottomNavigationBarThemeData( + bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: schwarz, - selectedItemColor: rot, + selectedItemColor: akzent, unselectedItemColor: textSekundaer, ), cardTheme: CardThemeData( @@ -35,7 +41,7 @@ class MeloTheme { ), chipTheme: ChipThemeData( backgroundColor: dunkel1, - selectedColor: rot, + selectedColor: akzent, labelStyle: const TextStyle(color: textPrimaer), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), diff --git a/lib/utils/user_effekte.dart b/lib/utils/user_effekte.dart new file mode 100644 index 0000000..cd8e355 --- /dev/null +++ b/lib/utils/user_effekte.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:just_audio/just_audio.dart'; +import 'farb_theme.dart'; + +/// Login-Effekte: Pro Person eigener Akzent, Willkommens-Sound und Begrüßung. +/// Wird nach erfolgreichem Login und beim App-Start (wenn Token vorhanden) ausgelöst. +class UserEffekt { + final Color akzent; + final String begruessung; + final String soundAsset; + + const UserEffekt(this.akzent, this.begruessung, this.soundAsset); + + /// Baka = königlich-rot + Fanfare 👑 + static const baka = UserEffekt( + Color(0xFFCC0000), + 'Willkommen zurück, Baka! 👑', + 'assets/sounds/baka_fanfare.wav', + ); + + /// Tinker = türkis + heller Chime 🐱 + static const tinker = UserEffekt( + Color(0xFF00B8A9), + 'Tinker ist da! 🐱✨', + 'assets/sounds/tinker_chime.wav', + ); + + static const standard = UserEffekt( + Color(0xFFCC0000), + 'Willkommen! 🎵', + 'assets/sounds/willkommen_ping.wav', + ); + + static UserEffekt fuer(String? user) { + final u = (user ?? '').toLowerCase().trim(); + if (u == 'baka' || u == 'dustin') return baka; + if (u == 'tinker') return tinker; + return standard; + } + + /// Setzt die Akzentfarbe (Thema wechselt live) und spielt den Sound ab. + static Future anwenden(String? user) async { + final e = fuer(user); + MeloTheme.akzent = e.akzent; + try { + final p = AudioPlayer(); + await p.setAsset(e.soundAsset); + unawaited(p.play()); + // Player nach dem Abspielen sauber freigeben (kein Memory-Leak) + Future.delayed(const Duration(seconds: 4), () { + if (p.playing) p.stop(); + p.dispose(); + }); + } catch (_) { + // Sound-Fehler ignorieren – Akzent bleibt trotzdem aktiv + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index b020b7d..4f1ccad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,3 +37,5 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/sounds/