This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/lib/main.dart
T
Dustin 8b6f0925a6 v2.54 — Now-Playing-Redesign im Spotify-Stil
## Now-Playing-Fullscreen
- Kopfzeile: „JETZT LÄUFT"-Header + Like-Button + Queue-Button (Like aus der Titel-Zeile in den Header verschoben)
- Großes Cover zentriert (78 % Breite, Radius 24) mit Glow-Pulsation + Karten-Schatten
- Titel (24, w800) + Künstler (16) linksbündig in klarer Typografie
- Fortschrittsbalken: dickerer Slider (6 px, weiße Blende), Position links / Dauer rechts
- Controls-Reihenfolge wie Spotify: Shuffle, Prev, Play/Pause (74 px), Next, Repeat
- Neue untere Leiste: Schlaf-Timer links, Lyrics-Toggle + Geschwindigkeits-Chip rechts
- Geschwindigkeits-Chip: Tipp wechselt 0.5x–2.0x, wird wie in den Einstellungen persistiert (playback_speed)
- Blur-Hintergrund dezenter (Sigma 45 → 22)
- Versionsstring in main.dart auf 2.54 aktualisiert (Logger)
2026-08-05 12:16:15 +02:00

211 lines
7.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
import 'package:audio_service/audio_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'database/db_helper.dart';
import 'services/favoriten_service.dart';
import 'services/auth_service.dart';
import 'services/cloud_service.dart';
import 'services/melo_logger.dart';
import 'services/audio_handler.dart';
import 'services/musik_scanner.dart';
import 'services/sync_service.dart';
import 'utils/farb_theme.dart';
import 'utils/user_effekte.dart';
import 'screens/home_screen.dart';
import 'screens/login_screen.dart';
/// Globaler Notification-Plugin (wird in main() initialisiert)
final FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
/// Globaler Timer für periodischen Auto-Scan (wird in MeloAppState.dispose gecancelt)
Timer? _autoScanTimer;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Logger startet sofort zeichnet ALLES auf
MeloLogger().init('2.54');
// ── Notifications initialisieren (Issue #9) ──
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
const initSettings = InitializationSettings(android: androidInit);
await notificationsPlugin.initialize(
settings: initSettings,
// Tipp auf die Sync-Abschluss-/Fehler-Notification → Cloud-Tab öffnen
onDidReceiveNotificationResponse: (response) {
if (response.payload == 'sync_fertig' ||
response.payload == 'sync_fehler') {
SyncService.syncBenachrichtigungGetippt.value++;
}
},
);
// Notification-Channel für Downloads (Fortschritt + Abschluss)
const downloadChannel = AndroidNotificationChannel(
'de.baka.melo.downloads',
'Melo Downloads',
description: 'Download-Fortschritt und Abschluss',
importance: Importance.low,
playSound: false,
enableVibration: false,
);
await notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(downloadChannel);
// Notification-Channel für den Cloud-Sync (Fortschritt + Abschluss)
const syncChannel = AndroidNotificationChannel(
'de.baka.melo.sync',
'Melo Sync',
description: 'Cloud-Sync-Fortschritt und Abschluss',
importance: Importance.low,
playSound: false,
enableVibration: false,
);
await notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(syncChannel);
// ── Background Service initialisieren (Issue #3) ──
await FlutterBackgroundService().configure(
iosConfiguration: IosConfiguration(),
androidConfiguration: AndroidConfiguration(
onStart: _onServiceStart,
autoStart: false,
isForegroundMode: true,
),
);
try {
await DbHelper().db;
await FavoritenService().init();
// Hintergrund-Audio-Service starten
await AudioService.init(
builder: () => MeloAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'de.baka.melo.audio',
androidNotificationChannelName: 'Melo Wiedergabe',
androidStopForegroundOnPause: false,
),
);
MeloLogger().zustand('start_ok', {'db': 'ok', 'audio': 'ok'});
} catch (e, stack) {
MeloLogger().fehler('App-Start', e, stack);
}
// Auth initialisieren (Token aus SharedPreferences laden + online validieren)
await AuthService().initialisieren();
// Favoriten-Toggle → Server-Push (Feuer-und-Vergessen, deterministisch via
// `set` statt Toggle — verhindert Doppel-Toggle). Songs ohne cloud_id
// (nicht hochgeladen) werden übersprungen; ein Fehlschlag wird beim
// nächsten vollständigen Sync korrigiert (favoritenMerge-Vereinigung).
FavoritenService().onStatusGeaendert = (songId, neuerStatus) async {
try {
final song = await DbHelper().songNachId(songId);
final cid = song?.cloudId;
if (cid == null || cid.isEmpty) return;
await CloudService().setFavorite(cid, favorit: neuerStatus);
} catch (e) {
MeloLogger().fehler('favoriten_server_push', e);
}
};
// Android-Berechtigungen beim Start abfragen (Issue #4)
// Speicherzugriff für Musik-Scan (READ_MEDIA_AUDIO / Storage)
await MusikScanner().frageSpeicherZugriff();
// Auto-Scan: Musikordner beim Start scannen + periodisch alle 3h (Issue #6)
_starteAutoScan();
// POST_NOTIFICATIONS für Android 13+ (Notifications für Downloads, Player)
if (Platform.isAndroid) {
await Permission.notification.request();
}
// Per-User-Akzent (überschreibbar in den Einstellungen) + Begrüßungs-Sound
// beim App-Start (wenn Token vorhanden). Fehler sind unkritisch.
await UserEffekt.anwenden(AuthService().benutzer);
// App-weiter Auto-Sync-Timer (F3): Intervall aus `cloud_interval`
// (0 = Aus), erster Tick erst NACH dem Intervall — kein Sync beim Start.
unawaited(SyncService.starteAutoSyncTimer());
runApp(const MeloApp());
}
/// Führt Musik-Scan beim Start aus + startet Timer.periodic alle 3 Stunden (Issue #6)
/// Scannt nur /Music, /Download, /Musik, /Downloads — nicht ganz /storage.
void _starteAutoScan() {
// Sofort beim Start scannen (asynchron, blockiert nicht)
MusikScanner().scanneMusikOrdner().then((songs) {
MeloLogger().zustand('auto_scan_start', {'gefunden': songs.length});
}).catchError((e) {
MeloLogger().fehler('auto_scan_start', e);
});
// Periodischer Scan alle 3 Stunden Timer wird in MeloAppState.dispose() gecancelt
_autoScanTimer = Timer.periodic(const Duration(hours: 3), (_) {
MusikScanner().scanneMusikOrdner().then((songs) {
MeloLogger().zustand('auto_scan_periodisch', {'gefunden': songs.length});
}).catchError((e) {
MeloLogger().fehler('auto_scan_periodisch', e);
});
});
}
/// Background Service Callback hält App im Vordergrund beim Download (Issue #3)
@pragma('vm:entry-point')
Future<bool> _onServiceStart(ServiceInstance service) async {
WidgetsFlutterBinding.ensureInitialized();
// Service läuft als Foreground-Service und hält den Prozess am Leben.
// Der eigentliche Download läuft im Haupt-Isolate der Service dient nur
// als WakeLock / Foreground-Garantie.
service.on('stopService').listen((_) {
service.stopSelf();
});
return true;
}
class MeloApp extends StatefulWidget {
const MeloApp({super.key});
@override
State<MeloApp> createState() => _MeloAppState();
}
class _MeloAppState extends State<MeloApp> {
@override
Widget build(BuildContext context) {
final auth = AuthService();
// ValueListenableBuilder: Akzentfarbe (per User, in Einstellungen wählbar)
// wechselt das komplette Theme live — kein App-Neustart nötig.
return ValueListenableBuilder<Color>(
valueListenable: MeloTheme.akzentNotifier,
builder: (_, akzent, __) => MaterialApp(
title: 'Melo',
debugShowCheckedModeBanner: false,
theme: MeloTheme.theme,
home: auth.istEingeloggt
? const Scaffold(body: MeloHome())
: const LoginScreen(),
),
);
}
@override
void dispose() {
_autoScanTimer?.cancel();
_autoScanTimer = null;
super.dispose();
}
}