import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pyramid/core/app_state.dart'; import 'package:pyramid/core/appearance.dart'; import 'package:pyramid/core/notification_service.dart' show checkPendingReply, setAppForeground; import 'package:pyramid/core/router.dart'; import 'package:pyramid/core/settings_prefs.dart'; import 'package:pyramid/core/theme.dart'; import 'package:pyramid/features/auth/login_notifier.dart'; import 'package:pyramid/widgets/pyramid_loader.dart'; class PyramidApp extends ConsumerStatefulWidget { const PyramidApp({super.key}); @override ConsumerState createState() => _PyramidAppState(); } class _PyramidAppState extends ConsumerState with WidgetsBindingObserver { bool _prefsLoaded = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _loadPersistedPrefs(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: setAppForeground(true); checkPendingReply(ref); case AppLifecycleState.paused: case AppLifecycleState.detached: // Clears heartbeat so PushService.kt shows FCM notifications immediately. // Background Matrix syncs must not re-set the heartbeat (see notification_service.dart). setAppForeground(false); default: break; } } Future _loadPersistedPrefs() async { final saved = await loadThemePrefs(); if (!mounted) return; ref.read(themeVariantProvider.notifier).state = saved.variant; ref.read(textScaleProvider.notifier).state = saved.textScale; ref.read(messageStyleProvider.notifier).state = saved.messageStyle; ref.read(accentProvider.notifier).state = saved.accentIdx; ref.read(radiusProvider.notifier).state = saved.radius; ref.read(motionProvider.notifier).state = saved.motion; setState(() => _prefsLoaded = true); } @override Widget build(BuildContext context) { // Block the router from rendering until auth state is known so the // /server page never flashes before the redirect to /app fires. final authAsync = ref.watch(isLoggedInProvider); if (authAsync.isLoading || !_prefsLoaded) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( backgroundColor: Color(0xFF1A1B1E), body: Center(child: PyramidLoader(size: 80)), ), ); } final router = ref.watch(routerProvider); final variant = ref.watch(themeVariantProvider); final textScale = ref.watch(textScaleProvider); final accentIdx = ref.watch(accentProvider); final radius = ref.watch(radiusProvider); final density = ref.watch(densityProvider); final motion = ref.watch(motionProvider); final accent = accentPresets[accentIdx].color; return PyramidTheme( variant: variant, accent: accent, radius: radius, density: density, motion: motion, child: MaterialApp.router( title: 'Pyramid', theme: buildPyramidThemeData(ThemeVariant.light), darkTheme: buildPyramidThemeData( variant.isDark ? variant : ThemeVariant.soft), themeMode: variant.isDark ? ThemeMode.dark : ThemeMode.light, routerConfig: router, // Schriftgröße: Pyramid-Regler × Handy-/Systemeinstellung, für alle // Seiten und Dialoge (liegt über dem Navigator). builder: (context, child) { final mq = MediaQuery.of(context); final factor = textScale ?? defaultTextScale(mobile: isMobileLayout(mq.size)); return MediaQuery( data: mq.copyWith( textScaler: PyramidTextScaler(mq.textScaler, factor), ), child: child!, ); }, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('de'), Locale('en')], debugShowCheckedModeBanner: false, ), ); } }