Files
deflock-app/lib/main.dart
T

144 lines
5.1 KiB
Dart

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<void> 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<AppState>(
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) {
// Handle deep link routes that Flutter tries to process before app_links
// This catches routes like "/?id=13939398601" from "deflockapp://node?id=13939398601"
debugPrint('[Navigation] Unknown route intercepted: ${settings.name} (likely deep link, will be handled by DeepLinkService)');
return MaterialPageRoute(
settings: settings,
builder: (_) => const HomeScreen(),
);
},
);
}
}
// Global navigator key for deep link navigation
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();