v2.52.3 — F3-Rest: ☁️-Chip-Puls + Auto-Sync-Timer app-weit
## Chip-Puls (home_screen)
- ListenableBuilder hört auf Listenable.merge([_vm, _cloud, SyncService.laeuftNotifier]) — der ☁️-Musikserver-Chip pulsiert jetzt bei laufendem Sync (auch im Hintergrund/Auto-Sync), nicht nur beim Verbinden/Laden
- serverSyncLaeuft = SyncService.laeuftNotifier.value || CloudStatus.verbinde || serverLadt
## Auto-Sync-Timer app-weit (main.dart)
- _starteAutoSyncFallsNoetig() entfernt (F3a: kein Sync beim App-Start); stattdessen SyncService.starteAutoSyncTimer() — Timer tickt erst nach dem Intervall, Fälligkeit via istSyncFaellig
- shared_preferences-Import aus main.dart entfernt (Intervall-Logik lebt in SyncService/CloudScreen)
## Sync-Notification
- Sync-Kanal de.baka.melo.sync wird in main() angelegt (Android 8+: Channel nötig, sonst erscheint die Notification nicht)
- onDidReceiveNotificationResponse: Tipp auf 'sync_fertig'/'sync_fehler' → SyncService.syncBenachrichtigungGetippt++ → MeloHome öffnet den Cloud-Tab
## Version
- Versionsanzeige 2.51 → 2.52.3 (settings/home/erweitert), MeloLogger.init('2.35') → '2.52.3'
This commit is contained in:
+31
-48
@@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
@@ -13,7 +12,9 @@ import 'services/cloud_service.dart';
|
|||||||
import 'services/melo_logger.dart';
|
import 'services/melo_logger.dart';
|
||||||
import 'services/audio_handler.dart';
|
import 'services/audio_handler.dart';
|
||||||
import 'services/musik_scanner.dart';
|
import 'services/musik_scanner.dart';
|
||||||
|
import 'services/sync_service.dart';
|
||||||
import 'utils/farb_theme.dart';
|
import 'utils/farb_theme.dart';
|
||||||
|
import 'utils/user_effekte.dart';
|
||||||
import 'screens/home_screen.dart';
|
import 'screens/home_screen.dart';
|
||||||
import 'screens/login_screen.dart';
|
import 'screens/login_screen.dart';
|
||||||
|
|
||||||
@@ -28,13 +29,20 @@ void main() async {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Logger startet sofort – zeichnet ALLES auf
|
// Logger startet sofort – zeichnet ALLES auf
|
||||||
MeloLogger().init('2.35');
|
MeloLogger().init('2.52.3');
|
||||||
|
|
||||||
// ── Notifications initialisieren (Issue #9) ──
|
// ── Notifications initialisieren (Issue #9) ──
|
||||||
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
const initSettings = InitializationSettings(android: androidInit);
|
const initSettings = InitializationSettings(android: androidInit);
|
||||||
await notificationsPlugin.initialize(
|
await notificationsPlugin.initialize(
|
||||||
settings: initSettings,
|
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)
|
// Notification-Channel für Downloads (Fortschritt + Abschluss)
|
||||||
@@ -51,6 +59,20 @@ void main() async {
|
|||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.createNotificationChannel(downloadChannel);
|
?.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) ──
|
// ── Background Service initialisieren (Issue #3) ──
|
||||||
await FlutterBackgroundService().configure(
|
await FlutterBackgroundService().configure(
|
||||||
iosConfiguration: IosConfiguration(),
|
iosConfiguration: IosConfiguration(),
|
||||||
@@ -108,8 +130,13 @@ void main() async {
|
|||||||
await Permission.notification.request();
|
await Permission.notification.request();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-Sync beim Start (wenn eingeloggt und Auto-Sync aktiv)
|
// Per-User-Akzent (überschreibbar in den Einstellungen) + Begrüßungs-Sound
|
||||||
_starteAutoSyncFallsNoetig();
|
// 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());
|
runApp(const MeloApp());
|
||||||
}
|
}
|
||||||
@@ -134,50 +161,6 @@ void _starteAutoScan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Führt Cloud-Auto-Sync beim App-Start aus, falls konfiguriert
|
|
||||||
Future<void> _starteAutoSyncFallsNoetig() async {
|
|
||||||
try {
|
|
||||||
final auth = AuthService();
|
|
||||||
if (!auth.istEingeloggt) return;
|
|
||||||
|
|
||||||
// Prüfen, ob Auto-Sync aktiviert ist
|
|
||||||
final SharedPreferences prefs =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
final autoSync = prefs.getBool('cloud_auto') ?? true;
|
|
||||||
if (!autoSync) return;
|
|
||||||
|
|
||||||
final cloud = CloudService();
|
|
||||||
final ok = await cloud.login(auth.benutzer);
|
|
||||||
if (!ok) return;
|
|
||||||
|
|
||||||
// Letzten Sync prüfen — nur syncen wenn nötig
|
|
||||||
final lastSync = prefs.getString('cloud_last_sync_ts');
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
if (lastSync != null) {
|
|
||||||
final last = DateTime.tryParse(lastSync);
|
|
||||||
if (last != null) {
|
|
||||||
final interval = prefs.getInt('cloud_interval') ?? 6;
|
|
||||||
if (now.difference(last).inHours < interval) {
|
|
||||||
return; // Noch nicht fällig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync ausführen
|
|
||||||
MeloLogger().aktion('auto_sync_start', {});
|
|
||||||
final st = await cloud.statusDaten();
|
|
||||||
if (st != null) {
|
|
||||||
final serverSongs = await cloud.listSongs();
|
|
||||||
MeloLogger().aktion('auto_sync_done',
|
|
||||||
{'songs': serverSongs.length});
|
|
||||||
}
|
|
||||||
await prefs.setString('cloud_last_sync_ts', now.toIso8601String());
|
|
||||||
} catch (e) {
|
|
||||||
MeloLogger().fehler('auto_sync_init', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Background Service Callback – hält App im Vordergrund beim Download (Issue #3)
|
/// Background Service Callback – hält App im Vordergrund beim Download (Issue #3)
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<bool> _onServiceStart(ServiceInstance service) async {
|
Future<bool> _onServiceStart(ServiceInstance service) async {
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ class _EntwicklerScreenState extends State<EntwicklerScreen> {
|
|||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
children: [
|
||||||
_infoKarte([
|
_infoKarte([
|
||||||
('Version', 'v2.51'),
|
('Version', 'v2.52.3'),
|
||||||
('Session', MeloLogger().sessionId.isEmpty ? '—' : MeloLogger().sessionId),
|
('Session', MeloLogger().sessionId.isEmpty ? '—' : MeloLogger().sessionId),
|
||||||
('Songs in DB', _songAnzahl < 0 ? '…' : '$_songAnzahl'),
|
('Songs in DB', _songAnzahl < 0 ? '…' : '$_songAnzahl'),
|
||||||
('Auth-Server', AppConfig.authUrl),
|
('Auth-Server', AppConfig.authUrl),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'erweitert_screen.dart';
|
|||||||
import '../services/cloud_service.dart';
|
import '../services/cloud_service.dart';
|
||||||
import '../services/player_service.dart';
|
import '../services/player_service.dart';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
|
import '../services/sync_service.dart';
|
||||||
import '../config/app_config.dart';
|
import '../config/app_config.dart';
|
||||||
|
|
||||||
class MeloHome extends StatefulWidget {
|
class MeloHome extends StatefulWidget {
|
||||||
@@ -54,14 +55,23 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
});
|
});
|
||||||
// Cloud-Verbindung beim App-Start herstellen (feuern-und-vergessen).
|
// Cloud-Verbindung beim App-Start herstellen (feuern-und-vergessen).
|
||||||
_cloud.verbinde();
|
_cloud.verbinde();
|
||||||
|
// Tipp auf die Sync-Abschluss-/Fehler-Notification → Cloud-Tab öffnen
|
||||||
|
SyncService.syncBenachrichtigungGetippt.addListener(_syncNotifGetippt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
SyncService.syncBenachrichtigungGetippt.removeListener(_syncNotifGetippt);
|
||||||
_vm.dispose();
|
_vm.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Öffnet den Cloud-Tab, wenn die Sync-Notification getippt wurde.
|
||||||
|
void _syncNotifGetippt() {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _aktiverTab = 3);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Navigation ─────────────────────────────────────
|
// ─── Navigation ─────────────────────────────────────
|
||||||
|
|
||||||
/// Öffnet den Now-Playing-Fullscreen mit Slide-up-Animation
|
/// Öffnet den Now-Playing-Fullscreen mit Slide-up-Animation
|
||||||
@@ -415,7 +425,9 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: _vm,
|
// _vm (Songs/Status), _cloud (Verbindung) UND SyncService.laeuftNotifier
|
||||||
|
// (☁️-Chip pulsiert bei laufendem Sync, auch im Hintergrund)
|
||||||
|
listenable: Listenable.merge([_vm, _cloud, SyncService.laeuftNotifier]),
|
||||||
builder: (_, _) {
|
builder: (_, _) {
|
||||||
if (_vm.ladt) {
|
if (_vm.ladt) {
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
@@ -454,7 +466,11 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
onSearch: _zeigeSuche,
|
onSearch: _zeigeSuche,
|
||||||
onServer: _zeigeMusikserverDetail,
|
onServer: _zeigeMusikserverDetail,
|
||||||
serverVerbunden: _vm.navidrome.istVerbunden,
|
serverVerbunden: _vm.navidrome.istVerbunden,
|
||||||
serverSyncLaeuft: _cloud.status == CloudStatus.verbinde || _vm.serverLadt,
|
// Chip pulsiert während des Syncs (SyncService-Läuft-Signal),
|
||||||
|
// beim Verbinden und solange der Server lädt.
|
||||||
|
serverSyncLaeuft: SyncService.laeuftNotifier.value ||
|
||||||
|
_cloud.status == CloudStatus.verbinde ||
|
||||||
|
_vm.serverLadt,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
@@ -1059,7 +1075,7 @@ class _MeloHomeState extends State<MeloHome> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_infoZeile('Version', '2.51'),
|
_infoZeile('Version', '2.52.3'),
|
||||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||||
_infoZeile('Theme', 'Schwarz + Rot'),
|
_infoZeile('Theme', 'Schwarz + Rot'),
|
||||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_infoZeile('Version', '2.51'),
|
_infoZeile('Version', '2.52.3'),
|
||||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||||
_infoZeile('Theme', 'Schwarz + Rot'),
|
_infoZeile('Theme', 'Schwarz + Rot'),
|
||||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ class SyncService {
|
|||||||
/// ValueNotifier, damit der Home-Screen (ListenableBuilder) mitrebuildet.
|
/// ValueNotifier, damit der Home-Screen (ListenableBuilder) mitrebuildet.
|
||||||
static final ValueNotifier<bool> laeuftNotifier = ValueNotifier<bool>(false);
|
static final ValueNotifier<bool> laeuftNotifier = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
/// Wird inkrementiert, wenn die Sync-Abschluss-/Fehler-Notification getippt
|
||||||
|
/// wird — MeloHome hört darauf und öffnet den Cloud-Tab (main.dart
|
||||||
|
/// verdrahtet den Tipp via `onDidReceiveNotificationResponse`).
|
||||||
|
static final ValueNotifier<int> syncBenachrichtigungGetippt =
|
||||||
|
ValueNotifier<int>(0);
|
||||||
|
|
||||||
// ─── Auto-Sync-Timer (app-weit, F3) ───
|
// ─── Auto-Sync-Timer (app-weit, F3) ───
|
||||||
|
|
||||||
static Timer? _autoSyncTimer;
|
static Timer? _autoSyncTimer;
|
||||||
|
|||||||
Reference in New Issue
Block a user