import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'app_state.dart'; import 'screens/home_screen.dart'; import 'screens/settings_screen.dart'; import 'screens/profiles_settings_screen.dart'; import 'screens/navigation_settings_screen.dart'; import 'screens/offline_settings_screen.dart'; import 'screens/advanced_settings_screen.dart'; import 'screens/language_settings_screen.dart'; import 'screens/about_screen.dart'; import 'screens/release_notes_screen.dart'; import 'screens/osm_account_screen.dart'; import 'screens/upload_queue_screen.dart'; import 'services/localization_service.dart'; import 'services/provider_tile_cache_manager.dart'; import 'services/version_service.dart'; import 'services/deep_link_service.dart'; import 'services/deflock_tile_provider.dart' show TileLoadCancelledException, TileNotAvailableOfflineException; /// Suppress console noise for expected, already-handled tile load errors. /// /// [TileLoadCancelledException] (tile scrolled off screen) and /// [TileNotAvailableOfflineException] (no cached data available) are thrown /// deliberately by DeflockOfflineTileImageProvider and are already handled — /// TileLayerManager.onTileLoadError() specifically skips retry logic for /// both. Flutter's image pipeline reports them via /// MultiFrameImageStreamCompleter with `silent: true`, but /// FlutterError.dumpErrorToConsole has a debug-mode assert() that ignores /// the silent flag and always prints to console anyway. This filters those /// two specific, expected exception types back out while leaving all other /// errors (including genuinely silent-but-unexpected ones) reported as /// normal. void _installTileErrorFilter() { final defaultOnError = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { final exception = details.exception; if (details.silent && (exception is TileLoadCancelledException || exception is TileNotAvailableOfflineException)) { return; // Already handled — don't spam the console. } if (defaultOnError != null) { defaultOnError(details); } else { FlutterError.presentError(details); } }; } Future main() async { WidgetsFlutterBinding.ensureInitialized(); _installTileErrorFilter(); // Initialize version service await VersionService().init(); // Initialize localization service await LocalizationService.instance.init(); // Resolve platform cache directory for per-provider tile caching await ProviderTileCacheManager.init(); // Initialize deep link service await DeepLinkService().init(); DeepLinkService().setNavigatorKey(_navigatorKey); runApp( ChangeNotifierProvider( create: (_) => AppState(), child: Consumer( builder: (context, appState, _) { if (!appState.isInitialized) { // You can customize this splash/loading screen as needed return MaterialApp( home: Scaffold( backgroundColor: Color(0xFF152131), body: Center( child: Image.asset( 'assets/app_icon.png', width: 240, height: 240, ), ), ), ); } return const DeFlockApp(); }, ), ), ); } class DeFlockApp extends StatelessWidget { const DeFlockApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'DeFlock', theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF0080BC), // DeFlock blue brightness: Brightness.dark, ), useMaterial3: true, ), navigatorKey: _navigatorKey, routes: { '/': (context) => const HomeScreen(), '/settings': (context) => const SettingsScreen(), '/settings/osm-account': (context) => const OSMAccountScreen(), '/settings/queue': (context) => const UploadQueueScreen(), '/settings/profiles': (context) => const ProfilesSettingsScreen(), '/settings/navigation': (context) => const NavigationSettingsScreen(), '/settings/offline': (context) => const OfflineSettingsScreen(), '/settings/advanced': (context) => const AdvancedSettingsScreen(), '/settings/language': (context) => const LanguageSettingsScreen(), '/settings/about': (context) => const AboutScreen(), '/settings/release-notes': (context) => const ReleaseNotesScreen(), }, initialRoute: '/', onUnknownRoute: (settings) { // Fallback for any genuinely unknown named route (e.g. a stale/bad // link). Deep links (deflockapp://...) are handled exclusively by // DeepLinkService via the app_links package - Flutter's own // implicit deep-link routing is disabled at the platform level // (see AndroidManifest.xml's flutter_deeplinking_enabled and // Info.plist's FlutterDeepLinkingEnabled) to avoid this handler // double-navigating and burying screens pushed by DeepLinkService. debugPrint('[Navigation] Unknown route intercepted: ${settings.name}'); return MaterialPageRoute( settings: settings, builder: (_) => const HomeScreen(), ); }, ); } } // Global navigator key for deep link navigation final GlobalKey _navigatorKey = GlobalKey();