v2.47 — Cloud-Verbindung beim App-Start + Live-Status-Anzeige (Verbinde→Verbunden)

## CloudService
- CloudStatus-Enum (verbinde/verbunden/fehler), CloudService extends ChangeNotifier
- verbinde(): Login+Status beim App-Start, setzt Status, notifyListeners
- Getter status/statusText/istVerbunden/verbindungGestartet
- Bestehende Methoden behalten — nur status() → statusDaten() umbenannt (Namenskonflikt mit neuem status-Getter)

## Home-Screen
- _cloud.verbinde() in initState (feuern-und-vergessen) — Verbindung steht beim App-Start, nicht erst beim Tab-Öffnen

## Cloud-Screen
- initState verbindet nicht mehr selbst — zeigt Service-Status via ListenableBuilder
- Status wechselt live: 'Verbinde...' (gelb) → 'Verbunden' (grün) / 'Keine Verbindung' (rot)
- Serverdaten (Playlists/Favoriten/Zähler) laden automatisch, sobald Service verbunden
- Refresh-Button re-verbindet + lädt Zähler neu
- Fallback: ungestarteter Service (Einstellungen-Screen) wird beim Öffnen gestartet
This commit is contained in:
Dustin
2026-08-03 03:08:45 +02:00
parent 20ef788178
commit 319b765d08
4 changed files with 169 additions and 83 deletions
+109 -80
View File
@@ -7,7 +7,6 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../utils/farb_theme.dart';
import '../utils/sanitize.dart';
import '../services/cloud_service.dart';
import '../services/auth_service.dart';
import '../database/db_helper.dart';
import '../services/melo_logger.dart';
import '../main.dart'; // notificationsPlugin
@@ -31,7 +30,7 @@ class _CloudScreenState extends State<CloudScreen> {
bool _ladt = false;
String? _status;
bool _statusOk = false;
bool _verbunden = false;
bool _serverDatenGeladen = false;
// ─── Sync-Einstellungen ───
bool _autoSync = true;
@@ -66,15 +65,24 @@ class _CloudScreenState extends State<CloudScreen> {
@override
void initState() {
super.initState();
_status = 'Verbinde...';
_verbindeCloud();
_ladeStatus();
// Verbindung stellt der CloudService beim App-Start her (MeloHome.initState).
// Hier nur Settings laden + Serverdaten, sobald der Service verbunden ist.
_ladeSettings();
_ladeServerDaten();
if (widget.cloud.istVerbunden) {
_serverDatenGeladen = true;
_ladeServerDaten();
_ladeStatus();
} else if (!widget.cloud.verbindungGestartet) {
// Fallback: Service wurde noch nicht gestartet (z.B. eigener Service
// aus den Einstellungen) — Verbindung hier anstoßen.
widget.cloud.verbinde();
}
widget.cloud.addListener(_onCloudStatus);
}
@override
void dispose() {
widget.cloud.removeListener(_onCloudStatus);
_syncTimer?.cancel();
_renameTitleCtrl.dispose();
_renameArtistCtrl.dispose();
@@ -83,16 +91,14 @@ class _CloudScreenState extends State<CloudScreen> {
// ─── Verbindung ───
Future<void> _verbindeCloud() async {
final user = AuthService().benutzer;
if (user.isNotEmpty) {
final ok = await widget.cloud.login(user);
if (mounted) {
setState(() {
_verbunden = ok;
if (!ok) _setzeStatus('Cloud-Login fehlgeschlagen', ok: false);
});
}
/// Reagiert auf Status-Änderungen des CloudService: sobald verbunden,
/// werden Serverdaten + Zähler nachgeladen.
void _onCloudStatus() {
if (!mounted) return;
if (widget.cloud.istVerbunden && !_serverDatenGeladen) {
_serverDatenGeladen = true;
_ladeServerDaten();
_ladeStatus();
}
}
@@ -115,7 +121,7 @@ class _CloudScreenState extends State<CloudScreen> {
}
Future<void> _ladeServerDaten() async {
if (!_verbunden) return;
if (!widget.cloud.istVerbunden) return;
// Paralleles Laden
await Future.wait([
_ladePlaylists(),
@@ -511,14 +517,11 @@ class _CloudScreenState extends State<CloudScreen> {
// ─── Status ───
Future<void> _ladeStatus() async {
final st = await widget.cloud.status();
final st = await widget.cloud.statusDaten();
final syncSt = await widget.cloud.syncStatus();
if (!mounted) return;
setState(() {
_serverCount = st?['total'] ?? 0;
_verbunden = st != null;
_statusOk = st != null;
_status = st != null ? 'Verbunden' : 'Keine Verbindung (Token?)';
if (syncSt != null) {
final counts = syncSt['counts'];
@@ -672,13 +675,20 @@ class _CloudScreenState extends State<CloudScreen> {
IconButton(
icon: const Icon(Icons.refresh, color: Colors.grey, size: 20),
onPressed: () async {
await widget.cloud.verbinde();
await _ladeStatus();
await _ladeServerDaten();
},
),
],
),
body: _syncLaeuft ? _syncFortschrittWidget() : _contentWidget(),
// ListenableBuilder: Status kommt live vom CloudService
// ('Verbinde...' → 'Verbunden'/'Keine Verbindung')
body: ListenableBuilder(
listenable: widget.cloud,
builder: (context, _) =>
_syncLaeuft ? _syncFortschrittWidget() : _contentWidget(),
),
);
}
@@ -774,47 +784,64 @@ class _CloudScreenState extends State<CloudScreen> {
child: CircularProgressIndicator(color: MeloTheme.rot)),
),
// Status-Text
if (_status != null && !_ladt)
Padding(
padding: const EdgeInsets.only(top: 12),
child: Container(
width: double.infinity,
padding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: _statusOk
? const Color(0xFF0D3B1E)
: const Color(0xFF3B0D0D),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(
_statusOk
? Icons.check_circle
: Icons.info_outline,
size: 16,
color: _statusOk
? const Color(0xFF4CAF50)
: const Color(0xFFEF5350),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_status!,
style: TextStyle(
color: _statusOk
? const Color(0xFFA5D6A7)
: const Color(0xFFEF9A9A),
fontSize: 13,
// Status-Text: transiente Meldungen (Upload-Fortschritt, Sync-Ergebnis)
// haben Vorrang — sonst kommt der Verbindungsstatus live vom Service
// ('Verbinde...' → 'Verbunden' / 'Keine Verbindung')
if (!_ladt) ...[
Builder(builder: (context) {
final hatTransientStatus = _status != null;
final verbindet = widget.cloud.status == CloudStatus.verbinde;
final statusOk =
hatTransientStatus ? _statusOk : widget.cloud.istVerbunden;
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Container(
width: double.infinity,
padding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: statusOk
? const Color(0xFF0D3B1E)
: verbindet
? const Color(0xFF3B2A0D)
: const Color(0xFF3B0D0D),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(
statusOk
? Icons.check_circle
: verbindet
? Icons.sync
: Icons.info_outline,
size: 16,
color: statusOk
? const Color(0xFF4CAF50)
: verbindet
? const Color(0xFFFFA726)
: const Color(0xFFEF5350),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_status ?? widget.cloud.statusText,
style: TextStyle(
color: statusOk
? const Color(0xFFA5D6A7)
: verbindet
? const Color(0xFFFFCC80)
: const Color(0xFFEF9A9A),
fontSize: 13,
),
),
),
),
],
],
),
),
),
),
);
}),
],
const SizedBox(height: 24),
@@ -947,26 +974,28 @@ class _CloudScreenState extends State<CloudScreen> {
],
),
),
// Verbindungsstatus-Indikator
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _verbunden
? const Color(0xFF4CAF50)
: const Color(0xFFEF5350),
boxShadow: [
BoxShadow(
color: (_verbunden
? const Color(0xFF4CAF50)
: const Color(0xFFEF5350))
.withValues(alpha: 0.5),
blurRadius: 8,
),
],
),
),
// Verbindungsstatus-Indikator (live vom CloudService)
Builder(builder: (context) {
final farbe = widget.cloud.istVerbunden
? const Color(0xFF4CAF50)
: widget.cloud.status == CloudStatus.verbinde
? const Color(0xFFFFA726)
: const Color(0xFFEF5350);
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: farbe,
boxShadow: [
BoxShadow(
color: farbe.withValues(alpha: 0.5),
blurRadius: 8,
),
],
),
);
}),
],
),
);
+4
View File
@@ -34,6 +34,10 @@ class _MeloHomeState extends State<MeloHome> {
void initState() {
super.initState();
_vm.ladeSongs();
// Cloud-Verbindung beim App-Start herstellen (feuern-und-vergessen).
// Der CloudService wechselt 'Verbinde...' → 'Verbunden' und benachrichtigt
// Listener — der CloudScreen zeigt den Status direkt vom Service an.
_cloud.verbinde();
}
@override