v2.31 — Login, Einstellungen & Cloud Sync Redesign

- Login-Screen im Melo-Design (Schwarz+Rot) mit Baka-Auth JWT
- AuthService fuer Token-Management ueber baka-net.de/auth
- Registrierungs-Modus + 'Ohne Login fortfahren'
- Navidrome-Credentials optional im Login aufklappbar

- SettingsScreen ersetzt AlertDialog
- BottomNav navigiert statt Dialog zu triggern
- Cloud Sync, Server verbinden, Diagnosedaten, App-Info, Abmelden

- CloudEinstellungen-Dialog entfernt, alles in CloudScreen konsolidiert
- Hardcode login('Baka','') entfernt → AuthService.benutzer
- Gradient-Statuskarte, animierte Segment-Intervall-Auswahl
- JWT Authorization statt Klartext-Passwort

- AppConfig: API-Key defaultValue entfernt (kein Fallback-Leak)
- Build crasht ohne --dart-define MELO_API_KEY

Build: split APK (arm64 19.6M, armeabi 17.1M, x86_64 21.1M)
This commit is contained in:
Dustin
2026-08-01 15:03:22 +02:00
parent 77a20422be
commit 36c744d6e3
77 changed files with 5211 additions and 1067 deletions
+19 -68
View File
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import '../database/db_helper.dart';
import '../services/player_service.dart';
import '../services/musik_scanner.dart';
@@ -8,11 +7,6 @@ import '../services/favoriten_service.dart';
import '../services/download_service.dart';
import '../services/navidrome_service.dart';
import '../services/playlist_service.dart';
import '../services/cloud_service.dart';
import '../services/melo_logger.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import '../models/song.dart';
import '../models/tag.dart';
@@ -24,8 +18,6 @@ class MeloHomeViewModel extends ChangeNotifier {
final DownloadService downloader = DownloadService();
final NavidromeService navidrome = NavidromeService();
final PlaylistService playlists = PlaylistService();
final CloudService cloud = CloudService();
Timer? _autoSyncTimer;
List<Song> songs = [];
Set<String> aktiveTags = {};
@@ -42,19 +34,6 @@ class MeloHomeViewModel extends ChangeNotifier {
int _playCount = 0;
static const int _botschaftSchwellwert = 10;
String? _nutzerBotschaft;
/// Per-User Easteregg-Botschaften
static const _botschaften = {
'Baka': '💌 Seit 2008 Danke, dass du immer da bist ♥',
'Tinker': '🎀 Für meine beste Freundin Melo & Melo 💕',
};
String? get nutzerBotschaft => _nutzerBotschaft;
void setzeNutzer(String name) {
_nutzerBotschaft = _botschaften[name];
}
StreamSubscription<Duration>? _positionsSub;
int _letzteGespeicherteSekunde = -1;
@@ -87,10 +66,10 @@ class MeloHomeViewModel extends ChangeNotifier {
if (aktiveTags.contains('★ Favoriten')) {
return songs.where((s) => s.id != null && favoritenIds.contains(s.id)).toList();
}
// Filter: Songs mit ALLEN aktiven Tags (AND-Logik)
// Filter: Songs mit mindestens einem der aktiven Tags
return songs.where((s) {
if (s.tagIds == null) return false;
return aktiveTags.every((tagName) {
return aktiveTags.any((tagName) {
final tag = _tagsMap[tagName];
return tag != null && s.tagIds!.contains(tag.id);
});
@@ -102,22 +81,29 @@ class MeloHomeViewModel extends ChangeNotifier {
notifyListeners();
try {
// Auto-Restore der Navidrome-Session beim Start
// Dummies bereinigen
await db.alteDummiesLoeschen();
var alle = await db.alleSongs();
await navidrome.ladeGespeicherteZugangsdaten();
if (navidrome.istVerbunden) {
ladeNavidromeAlben();
if (alle.isEmpty) {
final beispiele = [
Song(titel: 'Leichtes Gepäck', kuenstler: 'Silbermond', album: 'Schritte',
dauerSekunden: 232, dateiPfad: '', groesseBytes: 5242880, istHeruntergeladen: true),
Song(titel: 'Auf uns', kuenstler: 'Andreas Bourani', album: 'Hey',
dauerSekunden: 241, dateiPfad: '', groesseBytes: 4819200, istHeruntergeladen: true),
Song(titel: '80 Millionen', kuenstler: 'Max Giesinger', album: 'Der Junge',
dauerSekunden: 225, dateiPfad: '', groesseBytes: 5107200, istHeruntergeladen: true),
Song(titel: 'Tage wie diese', kuenstler: 'Die Toten Hosen', album: 'Ballast',
dauerSekunden: 252, dateiPfad: '', groesseBytes: 4300800, istHeruntergeladen: true),
Song(titel: 'Atlantis', kuenstler: 'Frida Gold', album: 'Liebe',
dauerSekunden: 228, dateiPfad: '', groesseBytes: 3987200, istHeruntergeladen: true),
];
await db.songsEinfuegen(beispiele);
alle = await db.alleSongs();
}
// Echte Songs aus SQLite laden (ohne unspielbare Fake-Dummies)
songs = await db.alleSongs();
songs = alle;
favoritenIds = await favoriten.favoritenIds();
letzteSongs = await db.letzteWiedergaben();
await ladeTags();
_starteCloudSyncScheduler();
} catch (e, stack) {
debugPrint('ladeSongs Fehler: $e\n$stack');
}
@@ -282,43 +268,8 @@ class MeloHomeViewModel extends ChangeNotifier {
@override
void dispose() {
_autoSyncTimer?.cancel();
_positionsSub?.cancel();
player.dispose();
super.dispose();
}
Future<void> _starteCloudSyncScheduler() async {
_autoSyncTimer?.cancel();
final prefs = await SharedPreferences.getInstance();
final autoSync = prefs.getBool('cloud_auto') ?? true;
final intervalStunden = prefs.getInt('cloud_interval') ?? 6;
if (!autoSync || intervalStunden <= 0) return;
_autoSyncTimer = Timer.periodic(Duration(hours: intervalStunden), (_) async {
try {
await cloud.restoreLogin();
if (!cloud.istAngemeldet) return;
final serverSongs = await cloud.listSongs();
if (serverSongs.isEmpty) return;
// Nur neue Songs herunterladen
final dir = Directory('${(await getApplicationDocumentsDirectory()).path}/music');
if (!await dir.exists()) await dir.create(recursive: true);
final localFiles = dir.listSync().whereType<File>()
.map((f) => f.path.split('/').last).toSet();
for (final s in serverSongs) {
final title = p.basename(s['title'].toString());
if (localFiles.contains(title)) continue; // schon lokal
await cloud.download(s['id'].toString(), '${dir.path}/$title');
}
await ladeSongs(); // UI aktualisieren
} catch (e) {
MeloLogger().fehler('background_auto_sync', e);
}
});
}
}