Security: Cloud-Auth auf Bearer-JWT umgestellt (IDOR-Luecke geschlossen)
- 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
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user