25ed765a03
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
108 lines
3.5 KiB
Dart
108 lines
3.5 KiB
Dart
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/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<PyramidApp> createState() => _PyramidAppState();
|
|
}
|
|
|
|
class _PyramidAppState extends ConsumerState<PyramidApp>
|
|
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<void> _loadPersistedPrefs() async {
|
|
final saved = await loadThemePrefs();
|
|
if (!mounted) return;
|
|
ref.read(themeModeProvider.notifier).state = saved.isDark;
|
|
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 isDark = ref.watch(themeModeProvider);
|
|
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(
|
|
isDark: isDark,
|
|
accent: accent,
|
|
radius: radius,
|
|
density: density,
|
|
motion: motion,
|
|
child: MaterialApp.router(
|
|
title: 'Pyramid',
|
|
theme: buildPyramidThemeData(false),
|
|
darkTheme: buildPyramidThemeData(true),
|
|
themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
|
|
routerConfig: router,
|
|
localizationsDelegates: const [
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
supportedLocales: const [Locale('de'), Locale('en')],
|
|
debugShowCheckedModeBanner: false,
|
|
),
|
|
);
|
|
}
|
|
}
|