v2.55.2 — Manueller Sync: Status-Popup, Hintergrund-Weiterlauf, Sperrbildschirm-Notification
- cloud_screen.dart: Sync-Popup mit Live-Fortschritt („12/325 synchronisiert"), „Im Hintergrund fortsetzen"-Button, Ergebnis-Snackbar nach Abschluss - sync_service.dart: Notification-Kanal auf HIGH + Visibility.public, Foreground-Service starten/stoppen während Sync (Display-aus-Garantie), statische syncNotificationDetails() für Tests - main.dart: Sync-Channel-Importance auf HIGH (überschreibt alte low-Einstellung) - Tests: +7 (syncNotificationDetails HIGH/public, Lifecycle-Unabhängigkeit)
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.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 '../models/song.dart';
|
||||
import '../utils/sanitize.dart';
|
||||
@@ -253,11 +254,19 @@ class SyncService {
|
||||
/// NICHT interaktiv gelöst — die Server-Metadaten gewinnen still.
|
||||
/// Rückgabe: true bei Erfolg, false bei Fehler oder wenn bereits ein
|
||||
/// Sync läuft (globaler Guard).
|
||||
///
|
||||
/// v2.55.2: Startet den Foreground-Service (FlutterBackgroundService),
|
||||
/// damit der Sync auch bei Display-aus und minimierter App weiterläuft.
|
||||
Future<bool> syncAlles({bool automatisch = false}) async {
|
||||
if (_laeuftGlobal) return false;
|
||||
_laeuftGlobal = true;
|
||||
laeuftNotifier.value = true;
|
||||
String? konfliktBatch;
|
||||
|
||||
// v2.55.2: Foreground-Service starten — hält den Prozess im Hintergrund
|
||||
// aktiv, auch wenn das Display aus ist oder die App minimiert wird.
|
||||
await _starteForegroundService();
|
||||
|
||||
try {
|
||||
_zeigeSyncNotification('Verbinde…', 0, 0);
|
||||
onFortschritt?.call('Verbinde...', 0);
|
||||
@@ -527,11 +536,41 @@ class SyncService {
|
||||
} catch (_) {
|
||||
// Plugin kann beim App-Exit bereits disposed sein
|
||||
}
|
||||
// v2.55.2: Foreground-Service stoppen, sobald der Sync beendet ist.
|
||||
await _stoppeForegroundService();
|
||||
onSyncEnde?.call();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Notifications ───
|
||||
// ─── Notifications (v2.55.2: HIGH + public für Sperrbildschirm) ───
|
||||
|
||||
/// Erzeugt die [AndroidNotificationDetails] für Sync-Notifications.
|
||||
/// Statisch → testbar ohne Platform-/Plugin-Zugriff.
|
||||
/// [ongoing]=true für Fortschritts-Notification (nicht wegwischbar),
|
||||
/// [ongoing]=false für Abschluss-/Fehler-Notification (automatisch gelöscht).
|
||||
@visibleForTesting
|
||||
static AndroidNotificationDetails syncNotificationDetails({
|
||||
required bool ongoing,
|
||||
int progress = 0,
|
||||
int maxProgress = 0,
|
||||
}) {
|
||||
return AndroidNotificationDetails(
|
||||
_syncChannelId,
|
||||
'Melo Sync',
|
||||
channelDescription: ongoing
|
||||
? 'Cloud-Sync-Fortschritt'
|
||||
: 'Cloud-Sync-Abschluss',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
visibility: NotificationVisibility.public,
|
||||
onlyAlertOnce: true,
|
||||
showProgress: ongoing,
|
||||
maxProgress: ongoing ? (maxProgress > 0 ? maxProgress : 1) : 0,
|
||||
progress: ongoing ? progress : 0,
|
||||
ongoing: ongoing,
|
||||
autoCancel: !ongoing,
|
||||
);
|
||||
}
|
||||
|
||||
/// Persistente Fortschritts-Notification (nicht wegwischbar, ongoing).
|
||||
void _zeigeSyncNotification(String body, int aktuell, int gesamt) {
|
||||
@@ -543,18 +582,10 @@ class SyncService {
|
||||
title: 'Synchronisiere…',
|
||||
body: gesamt > 0 ? '$aktuell / $gesamt Songs' : body,
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_syncChannelId,
|
||||
'Melo Sync',
|
||||
channelDescription: 'Cloud-Sync-Fortschritt',
|
||||
importance: Importance.low,
|
||||
priority: Priority.low,
|
||||
onlyAlertOnce: true,
|
||||
showProgress: true,
|
||||
maxProgress: maxP,
|
||||
progress: p,
|
||||
android: syncNotificationDetails(
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
progress: p,
|
||||
maxProgress: maxP,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -573,14 +604,7 @@ class SyncService {
|
||||
body: '$songs Songs, $favoriten Favoriten synchronisiert ✅',
|
||||
payload: 'sync_fertig',
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_syncChannelId,
|
||||
'Melo Sync',
|
||||
channelDescription: 'Cloud-Sync-Abschluss',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
autoCancel: true,
|
||||
),
|
||||
android: syncNotificationDetails(ongoing: false),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -597,18 +621,44 @@ class SyncService {
|
||||
body: 'Bitte erneut versuchen ❌',
|
||||
payload: 'sync_fehler',
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_syncChannelId,
|
||||
'Melo Sync',
|
||||
channelDescription: 'Cloud-Sync-Abschluss',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
autoCancel: true,
|
||||
),
|
||||
android: syncNotificationDetails(ongoing: false),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Foreground-Service (v2.55.2) ───
|
||||
|
||||
/// Startet den Foreground-Service, damit der Sync auch bei Display-aus
|
||||
/// und minimierter App weiterläuft (Android-Prozess-Garantie).
|
||||
Future<void> _starteForegroundService() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
final svc = FlutterBackgroundService();
|
||||
final laeuft = await svc.isRunning();
|
||||
if (!laeuft) {
|
||||
await svc.startService();
|
||||
MeloLogger().aktion('foreground_sync_start', {});
|
||||
}
|
||||
} catch (e) {
|
||||
MeloLogger().fehler('foreground_sync_start', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stoppt den Foreground-Service nach Sync-Ende.
|
||||
Future<void> _stoppeForegroundService() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
final svc = FlutterBackgroundService();
|
||||
final laeuft = await svc.isRunning();
|
||||
if (laeuft) {
|
||||
svc.invoke('stopService'); // Feuer-und-vergessen (Isolate-Nachricht)
|
||||
MeloLogger().aktion('foreground_sync_stop', {});
|
||||
}
|
||||
} catch (e) {
|
||||
MeloLogger().fehler('foreground_sync_stop', e);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatZeit(DateTime? dt) {
|
||||
if (dt == null) return 'Nie';
|
||||
return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||
|
||||
Reference in New Issue
Block a user