Feature: Login-Effekte pro Person (Akzentfarbe + Sound + Begruessung)
- UserEffekt: Baka = rot + Fanfare, Tinker = tuerkis + Chime, Standard-Ping - MeloTheme: dynamische Akzentfarbe via ValueNotifier (Theme wechselt live) - Ausloeser: App-Start (wenn Token vorhanden) + nach erfolgreichem Login - Assets: 3 kurze WAV-Sounds (assets/sounds/) - Web: /whoami-Endpoint (Caddy) + Willkommens-Banner auf baka-net.de
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+22
-7
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'database/db_helper.dart';
|
import 'database/db_helper.dart';
|
||||||
import 'services/favoriten_service.dart';
|
import 'services/favoriten_service.dart';
|
||||||
import 'services/melo_logger.dart';
|
import 'services/melo_logger.dart';
|
||||||
|
import 'services/audio_handler.dart';
|
||||||
import 'utils/farb_theme.dart';
|
import 'utils/farb_theme.dart';
|
||||||
import 'screens/home_screen.dart';
|
import 'screens/home_screen.dart';
|
||||||
|
|
||||||
@@ -9,12 +11,21 @@ void main() async {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Logger startet sofort – zeichnet ALLES auf
|
// Logger startet sofort – zeichnet ALLES auf
|
||||||
MeloLogger().init('2.7');
|
MeloLogger().init('2.38');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DbHelper().db;
|
await DbHelper().db;
|
||||||
await FavoritenService().init();
|
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) {
|
} catch (e, stack) {
|
||||||
MeloLogger().fehler('App-Start', e, stack);
|
MeloLogger().fehler('App-Start', e, stack);
|
||||||
}
|
}
|
||||||
@@ -27,11 +38,15 @@ class MeloApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
// Reagiert live auf Akzent-Änderungen (Login-Effekt pro Person)
|
||||||
title: 'Melo',
|
return ValueListenableBuilder<Color>(
|
||||||
debugShowCheckedModeBanner: false,
|
valueListenable: MeloTheme.akzentNotifier,
|
||||||
theme: MeloTheme.theme,
|
builder: (context, akzent, _) => MaterialApp(
|
||||||
home: const Scaffold(body: MeloHome()),
|
title: 'Melo',
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: MeloTheme.theme,
|
||||||
|
home: const Scaffold(body: MeloHome()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import '../viewmodels/melo_home_viewmodel.dart';
|
|||||||
import '../models/song.dart';
|
import '../models/song.dart';
|
||||||
import '../models/playlist.dart';
|
import '../models/playlist.dart';
|
||||||
import '../utils/farb_theme.dart';
|
import '../utils/farb_theme.dart';
|
||||||
|
import '../utils/user_effekte.dart';
|
||||||
|
import '../services/cloud_service.dart';
|
||||||
import '../widgets/mini_player.dart';
|
import '../widgets/mini_player.dart';
|
||||||
import '../widgets/melo_header.dart';
|
import '../widgets/melo_header.dart';
|
||||||
import '../widgets/statistik_card.dart';
|
import '../widgets/statistik_card.dart';
|
||||||
@@ -18,7 +20,6 @@ import 'cloud_screen.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../widgets/cloud_einstellungen.dart';
|
import '../widgets/cloud_einstellungen.dart';
|
||||||
import '../services/cloud_service.dart';
|
|
||||||
import '../config/app_config.dart';
|
import '../config/app_config.dart';
|
||||||
|
|
||||||
class MeloHome extends StatefulWidget {
|
class MeloHome extends StatefulWidget {
|
||||||
@@ -50,6 +51,15 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
setState(() => _nutzer = n);
|
setState(() => _nutzer = n);
|
||||||
_vm.setzeNutzer(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() {
|
void _zeigeProfil() {
|
||||||
@@ -153,6 +163,13 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
await p.setString('melo_nutzer', name);
|
await p.setString('melo_nutzer', name);
|
||||||
if (mounted) setState(() => _nutzer = name);
|
if (mounted) setState(() => _nutzer = name);
|
||||||
_vm.setzeNutzer(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 {
|
} else {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||||
|
|||||||
@@ -9,12 +9,18 @@ class MeloTheme {
|
|||||||
static const Color textPrimaer = Color(0xFFFFFFFF);
|
static const Color textPrimaer = Color(0xFFFFFFFF);
|
||||||
static const Color textSekundaer = Color(0xFF888888);
|
static const Color textSekundaer = Color(0xFF888888);
|
||||||
|
|
||||||
|
/// Akzentfarbe – wechselt pro angemeldeter Person (Login-Effekt).
|
||||||
|
/// UI hängt per ValueListenableBuilder an [akzentNotifier].
|
||||||
|
static final ValueNotifier<Color> akzentNotifier = ValueNotifier<Color>(rot);
|
||||||
|
static Color get akzent => akzentNotifier.value;
|
||||||
|
static set akzent(Color farbe) => akzentNotifier.value = farbe;
|
||||||
|
|
||||||
static ThemeData get theme => ThemeData(
|
static ThemeData get theme => ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
scaffoldBackgroundColor: schwarz,
|
scaffoldBackgroundColor: schwarz,
|
||||||
colorScheme: const ColorScheme.dark(
|
colorScheme: ColorScheme.dark(
|
||||||
primary: rot,
|
primary: akzent,
|
||||||
secondary: rot,
|
secondary: akzent,
|
||||||
surface: schwarz,
|
surface: schwarz,
|
||||||
),
|
),
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
@@ -22,9 +28,9 @@ class MeloTheme {
|
|||||||
foregroundColor: textPrimaer,
|
foregroundColor: textPrimaer,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||||
backgroundColor: schwarz,
|
backgroundColor: schwarz,
|
||||||
selectedItemColor: rot,
|
selectedItemColor: akzent,
|
||||||
unselectedItemColor: textSekundaer,
|
unselectedItemColor: textSekundaer,
|
||||||
),
|
),
|
||||||
cardTheme: CardThemeData(
|
cardTheme: CardThemeData(
|
||||||
@@ -35,7 +41,7 @@ class MeloTheme {
|
|||||||
),
|
),
|
||||||
chipTheme: ChipThemeData(
|
chipTheme: ChipThemeData(
|
||||||
backgroundColor: dunkel1,
|
backgroundColor: dunkel1,
|
||||||
selectedColor: rot,
|
selectedColor: akzent,
|
||||||
labelStyle: const TextStyle(color: textPrimaer),
|
labelStyle: const TextStyle(color: textPrimaer),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
|||||||
@@ -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<void> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,3 +37,5 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/sounds/
|
||||||
|
|||||||
Reference in New Issue
Block a user