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:
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user