- cloud_service: echter Login gegen baka-auth, Token statt X-API-Key/X-User - app_config: hartcodierten API-Key-Default entfernt - download_service + melo_logger: Bearer-Token statt X-API-Key - navidrome: Passwort in flutter_secure_storage (Keychain/Keystore) - song: token-haltige stream_url wird nicht mehr in SQLite persistiert - cloud_screen: Pfad-Traversal beim Download-Dateinamen gefixt (p.basename) - home_screen: Login-Dialog mit Passwort-Feld, Auto-Sync nutzt restoreLogin
161 lines
5.0 KiB
Dart
161 lines
5.0 KiB
Dart
import 'dart:math';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../utils/farb_theme.dart';
|
||
import '../services/melo_logger.dart';
|
||
|
||
/// Animierte Ladeanzeige mit Easter Eggs für Melo
|
||
class MeloLoader extends StatefulWidget {
|
||
final String titel;
|
||
final String? subtitle;
|
||
|
||
const MeloLoader({super.key, required this.titel, this.subtitle});
|
||
|
||
@override
|
||
State<MeloLoader> createState() => _MeloLoaderState();
|
||
}
|
||
|
||
class _MeloLoaderState extends State<MeloLoader>
|
||
with TickerProviderStateMixin {
|
||
late AnimationController _pulsCtrl;
|
||
late AnimationController _equalCtrl;
|
||
late Animation<double> _pulsAnim;
|
||
int _downloadCount = 0;
|
||
String? _easterEgg;
|
||
|
||
static const _easterEggs = [
|
||
'🎵 Baka hat dich programmiert!',
|
||
'🔥 Melo > Spotify. Change my mind.',
|
||
'💀 Dieser Download kostet dich 5 Jahre Lebenszeit.',
|
||
'🎸 Fun Fact: Dieser Song wurde von einem Server-Proxy gerettet!',
|
||
'🐧 Linux > Windows. (Server-Fakt)',
|
||
'🍎 SideStore > iLoader. Immer.',
|
||
'🛡️ Geschützt von Caddy + Basic Auth.',
|
||
'🤫 Melo ist besser als YouTube Music.',
|
||
'⚡ Server: Aingrad, Netcup RS 1000.',
|
||
'💾 yt-dlp 2026.03.17 – Updates sind wichtig.',
|
||
'🎯 Der Proxy läuft auf Systemd – stabil.',
|
||
'🧠 Dustin hat das selbst gecoded!',
|
||
'📱 iOS + Android + Linux = schon 3/5!',
|
||
'🐛 Crash-Logs auf crash.baka-net.de',
|
||
'🎵 Navidrome: musik.baka-net.de',
|
||
'🗿 Du hast den Stein der Weisen gefunden.',
|
||
'🧙 Die Antwort ist 42.',
|
||
'⚔️ SDS Origin Guide im Vault.',
|
||
];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
|
||
_pulsCtrl = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 1200),
|
||
)..repeat(reverse: true);
|
||
|
||
_pulsAnim = Tween(begin: 0.6, end: 1.0).animate(
|
||
CurvedAnimation(parent: _pulsCtrl, curve: Curves.easeInOut),
|
||
);
|
||
|
||
_equalCtrl = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 600),
|
||
)..repeat();
|
||
|
||
_ladeCount();
|
||
}
|
||
|
||
Future<void> _ladeCount() async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
_downloadCount = prefs.getInt('dl_count') ?? 0;
|
||
_downloadCount++;
|
||
await prefs.setInt('dl_count', _downloadCount);
|
||
MeloLogger().aktion('dl_count', {'c': _downloadCount});
|
||
|
||
if (_downloadCount % 10 == 0 && mounted) {
|
||
final rng = Random(_downloadCount * 7);
|
||
final msg = _easterEggs[rng.nextInt(_easterEggs.length)];
|
||
setState(() => _easterEgg = msg);
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_pulsCtrl.dispose();
|
||
_equalCtrl.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
SizedBox(
|
||
height: 40,
|
||
child: AnimatedBuilder(
|
||
animation: _equalCtrl,
|
||
builder: (_, __) => Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: List.generate(5, (i) {
|
||
final phase = (i * 0.4 + _equalCtrl.value * 2 * pi);
|
||
final h = (sin(phase).abs() * 24 + 8);
|
||
return Container(
|
||
width: 4,
|
||
height: h,
|
||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||
decoration: BoxDecoration(
|
||
color: MeloTheme.rot.withValues(alpha: _pulsAnim.value),
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
FadeTransition(
|
||
opacity: _pulsAnim,
|
||
child: Text(
|
||
widget.titel,
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(color: Colors.white, fontSize: 15),
|
||
),
|
||
),
|
||
if (widget.subtitle != null) ...[
|
||
const SizedBox(height: 4),
|
||
Text(widget.subtitle!,
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||
],
|
||
if (_easterEgg != null) ...[
|
||
const SizedBox(height: 16),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: MeloTheme.dunkel1,
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(color: MeloTheme.rot.withValues(alpha: 0.3)),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Text('🥚', style: TextStyle(fontSize: 16)),
|
||
const SizedBox(width: 8),
|
||
Flexible(
|
||
child: Text(
|
||
_easterEgg!,
|
||
style: const TextStyle(color: MeloTheme.rot, fontSize: 12),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
}
|