import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:frontend/theme/alucard_theme.dart'; import 'package:frontend/theme/catppuccin_latte_theme.dart'; import 'package:frontend/theme/catppuccin_mocha_theme.dart'; import 'package:frontend/theme/dracula_theme.dart'; import 'package:frontend/theme/nord_theme.dart'; import 'package:frontend/theme/tokyo_night_theme.dart'; import 'package:frontend/utils/local_network_permission.dart'; import 'package:frontend/utils/pref_utils.dart'; import 'package:frontend/utils/push_service.dart'; import 'package:frontend/utils/ui_snackbars.dart'; import 'package:provider/provider.dart'; import 'navigation.dart'; import 'theme/gruvbox_theme.dart'; void main() { runZonedGuarded( () async { WidgetsFlutterBinding.ensureInitialized(); FlutterError.onError = (details) { FlutterError.presentError(details); debugPrint('FlutterError: ${details.exceptionAsString()}'); }; PlatformDispatcher.instance.onError = (error, stack) { debugPrint('Uncaught platform error: $error\n$stack'); return true; }; try { await PrefUtil.init(); } catch (e, stack) { debugPrint( 'PrefUtil.init failed, continuing with defaults: $e\n$stack', ); } // Resume push at launch (Firebase init + foreground handler + token // re-registration when already enabled); see initPushOnLaunch. try { await initPushOnLaunch(); } catch (e, stack) { debugPrint('Push launch init failed, continuing: $e\n$stack'); } // Settle iOS's local-network permission now, at launch, so it isn't // still being decided when the user first taps "Test" in settings. unawaited(requestLocalNetworkPermission()); runApp(const MainApp()); }, (error, stack) { debugPrint('Uncaught zone error: $error\n$stack'); }, ); } final class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => AppState(), child: Consumer( builder: (context, appState, _) => MaterialApp.router( title: 'OOTT', theme: appState.theme, routerConfig: router, // Host snackbars above the router's Navigator so they render on top // of dialogs (their barrier no longer dims them). See buildSnackbarHost. builder: buildSnackbarHost, ), ), ); } } final _themes = { 'alucard': alucardLightTheme, 'catppuccin_latte': catppuccinLatteLightTheme, 'catppuccin_mocha': catppuccinMochaDarkTheme, 'dracula': draculaDarkTheme, 'gruvbox_dark': gruvboxDarkTheme, 'nord': nordDarkTheme, 'tokyo_night': tokyoNightDarkTheme, }; class AppState extends ChangeNotifier { AppState() { final saved = PrefUtil.getValue('theme', 'gruvbox_dark') as String; _themeKey = _themes.containsKey(saved) ? saved : 'gruvbox_dark'; } late String _themeKey; String get themeKey => _themeKey; ThemeData get theme => _themes[_themeKey]!; Future setTheme(String key) async { if (!_themes.containsKey(key)) return false; if (key == _themeKey) return true; _themeKey = key; notifyListeners(); return PrefUtil.setValue('theme', key); } }