import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_ui/material_ui.dart'; import 'package:get_it/get_it.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spark/src/core/design_system/theme/app_theme.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/l10n/app_localization_delegates.dart'; import 'package:spark/src/core/routing/app_router.dart'; import 'package:spark/src/core/ui/theme/providers/theme_provider.dart' show themeModeProvider, themeProvider; import 'package:spark/src/core/utils/logging/log_service.dart'; import 'package:spark/src/core/utils/logging/logger.dart'; import 'package:spark/src/features/feed/providers/feed_provider.dart'; import 'package:spark/src/features/settings/providers/settings_provider.dart'; class SprkApp extends ConsumerStatefulWidget { const SprkApp({required this.onFirstRouteReady, super.key}); final VoidCallback onFirstRouteReady; @override ConsumerState createState() => _SprkAppState(); } class _SprkAppState extends ConsumerState { final _appRouter = AppRouter(); final SparkLogger _logger = GetIt.instance().getLogger('SprkApp'); bool _didReleaseFirstFrame = false; @override void initState() { super.initState(); _appRouter.addListener(_releaseFirstFrameWhenRouteIsReady); // Register AppRouter globally for push notification navigation if (!GetIt.instance.isRegistered()) { GetIt.instance.registerSingleton(_appRouter); } ref.read(themeProvider.notifier).initialize(); // Defer initialization to after the widget tree is built // to avoid modifying providers during build WidgetsBinding.instance.addPostFrameCallback((_) { _initializeApp(); }); } @override void dispose() { _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady); super.dispose(); } void _releaseFirstFrameWhenRouteIsReady() { if (_didReleaseFirstFrame || !_appRouter.hasEntries) { return; } _didReleaseFirstFrame = true; _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady); WidgetsBinding.instance.addPostFrameCallback((_) { widget.onFirstRouteReady(); }); } Future _initializeApp() async { try { _logger.d('Syncing user preferences from server...'); final settingsNotifier = ref.read(settingsProvider.notifier); // syncPreferencesFromServer already calls loadSettings internally await settingsNotifier.syncPreferencesFromServer(); _logger.d('User preferences synced successfully'); if (!mounted) return; final activeFeed = ref.read(settingsProvider).activeFeed; _logger.d('Active feed: ${activeFeed.config.value}'); ref.read(feedProvider(activeFeed).notifier).loadAndUpdateFirstLoad(); _logger.d('Feed loading started'); } catch (e) { _logger.w('Error during app initialization', error: e); } } @override Widget build(BuildContext context) { // Force dark status bar and navigation bar icons SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, statusBarBrightness: Brightness.dark, systemNavigationBarIconBrightness: Brightness.light, ), ); final themeMode = ref.watch(themeModeProvider); return MaterialApp.router( title: 'Spark', builder: (context, child) => SkeletonizerConfig( data: SkeletonizerConfigData(brightness: Theme.of(context).brightness), child: child!, ), theme: AppTheme.light, darkTheme: AppTheme.dark, debugShowCheckedModeBanner: false, themeMode: themeMode, supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: appLocalizationDelegates, localeResolutionCallback: (locale, supportedLocales) { for (final supportedLocale in supportedLocales) { if (supportedLocale.languageCode == locale?.languageCode) { return supportedLocale; } } return supportedLocales.first; }, routerConfig: _appRouter.config( placeholder: (_) => const ColoredBox(color: Colors.black), navigatorObservers: () => [ PosthogObserver(nameExtractor: _postHogScreenName), ], ), ); } } String? _postHogScreenName(RouteSettings settings) { final name = settings.name?.trim(); if (name == null || name.isEmpty) { return null; } if (name == '/') { return 'Root'; } const routeSuffix = 'Route'; if (name.endsWith(routeSuffix)) { return name.substring(0, name.length - routeSuffix.length); } return name; }