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
+11 -17
View File
@@ -1,12 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/song.dart';
import '../database/db_helper.dart';
@@ -66,10 +64,6 @@ class SubsonicAlbum {
class NavidromeService {
final DbHelper _db = DbHelper();
/// Passwort & Zugangsdaten liegen verschlüsselt im Keychain/Keystore
/// (flutter_secure_storage) statt im Klartext in SharedPreferences.
static const _secure = FlutterSecureStorage();
String _serverUrl = '';
String _user = '';
String _password = '';
@@ -83,17 +77,16 @@ class NavidromeService {
_serverUrl = url.endsWith('/') ? url.substring(0, url.length - 1) : url;
_user = user;
_password = password;
// Kryptographisch sicherer Salt (vorher millisecondsSinceEpoch = vorhersagbar)
final rng = Random.secure();
_salt = base64Encode(List.generate(16, (_) => rng.nextInt(256)));
_salt = DateTime.now().millisecondsSinceEpoch.toString();
_token = md5.convert(utf8.encode(_password + _salt)).toString();
}
Future<void> ladeGespeicherteZugangsdaten() async {
try {
final url = await _secure.read(key: 'navidrome_url');
final user = await _secure.read(key: 'navidrome_user');
final pass = await _secure.read(key: 'navidrome_pass');
final prefs = await SharedPreferences.getInstance();
final url = prefs.getString('navidrome_url');
final user = prefs.getString('navidrome_user');
final pass = prefs.getString('navidrome_pass');
if (url != null && user != null && pass != null && url.isNotEmpty) {
setCredentials(url, user, pass);
}
@@ -105,9 +98,10 @@ class NavidromeService {
Future<void> speichereZugangsdaten(String url, String user, String password) async {
setCredentials(url, user, password);
try {
await _secure.write(key: 'navidrome_url', value: url);
await _secure.write(key: 'navidrome_user', value: user);
await _secure.write(key: 'navidrome_pass', value: password);
final prefs = await SharedPreferences.getInstance();
await prefs.setString('navidrome_url', url);
await prefs.setString('navidrome_user', user);
await prefs.setString('navidrome_pass', password);
} catch (e) {
debugPrint('Fehler beim Speichern der Navidrome-Zugangsdaten: $e');
}
@@ -184,7 +178,7 @@ class NavidromeService {
final musikDir = Directory('${dir.path}/music');
if (!await musikDir.exists()) await musikDir.create(recursive: true);
final safeName = p.basename(s.titel).replaceAll(RegExp(r'[^\w\s-]'), '').trim();
final safeName = s.titel.replaceAll(RegExp(r'[^\w\s-]'), '').trim();
final kurzId = s.id.length > 8 ? s.id.substring(0, 8) : s.id;
final dateiName = '${safeName.isEmpty ? "song" : safeName}_$kurzId.mp3';
final dateiPfad = '${musikDir.path}/$dateiName';