Das Umschalten vom Warte-Bildschirm auf die App remountete den ganzen App-Baum, während Riverpod noch Provider-Abhängigkeiten nachzog — das löste die 'setState() or markNeedsBuild() called during build'-Warnung aus. Der Splash liegt jetzt als deckende Stack-Ebene über der durchgehend gemounteten App. Handoff: Bug vom Nutzer als behoben bestätigt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.1 KiB
Dart
87 lines
3.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../core/firebase/firebase_providers.dart';
|
|
import '../../../l10n/generated/app_localizations.dart';
|
|
import '../data/household_bootstrap_provider.dart';
|
|
|
|
/// Hält die App zurück, solange zum angemeldeten Nutzer noch kein
|
|
/// Haushalt existiert.
|
|
///
|
|
/// Beim ersten Login (z. B. mit Apple) legt [householdBootstrapProvider]
|
|
/// Profil + Haushalt an; bis dahin liegt ein deckender Warte-Bildschirm
|
|
/// ÜBER der App (Stack statt Austausch — ein Remount des App-Baums mitten
|
|
/// im Provider-Update löst „markNeedsBuild during build“ aus). Schlägt der
|
|
/// Bootstrap fehl, wird der Fehler hier sichtbar — vorher konnte man in
|
|
/// der App landen, deren Schreibzugriffe dann alle an den Rules scheiterten.
|
|
class BootstrapGate extends ConsumerWidget {
|
|
const BootstrapGate({super.key, required this.child});
|
|
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final user = ref.watch(authStateProvider).value;
|
|
if (user == null) return child; // Login-Screen übernimmt.
|
|
|
|
final profile = ref.watch(userProfileProvider).value;
|
|
// householdId statt bloßer Dokument-Existenz: die Push-Registrierung
|
|
// legt das users-Dokument auch ohne Haushalt an.
|
|
final profileReady = profile != null &&
|
|
profile.id == user.uid &&
|
|
profile.data()?['householdId'] != null;
|
|
if (profileReady) return child;
|
|
|
|
return Stack(
|
|
children: [
|
|
child,
|
|
_BootstrapSplash(),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Deckender Warte-/Fehler-Bildschirm, solange der Bootstrap läuft.
|
|
class _BootstrapSplash extends ConsumerWidget {
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final bootstrap = ref.watch(householdBootstrapProvider);
|
|
final l10n = AppLocalizations.of(context);
|
|
final theme = Theme.of(context);
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: bootstrap.hasError
|
|
? Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.cloud_off,
|
|
size: 48, color: theme.colorScheme.error),
|
|
const SizedBox(height: 16),
|
|
Text(l10n.bootstrapFailed, textAlign: TextAlign.center),
|
|
const SizedBox(height: 16),
|
|
FilledButton(
|
|
onPressed: () => ref
|
|
.read(householdBootstrapProvider.notifier)
|
|
.retry(),
|
|
child: Text(l10n.bootstrapRetry),
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const CircularProgressIndicator(),
|
|
const SizedBox(height: 16),
|
|
Text(l10n.bootstrapPreparing,
|
|
textAlign: TextAlign.center),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|