import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter/foundation.dart'; import 'l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'screens/chrome_required_screen.dart'; import 'utils/platform_info.dart'; import 'connector/meshcore_connector.dart'; import 'screens/scanner_screen.dart'; import 'services/storage_service.dart'; import 'services/message_retry_service.dart'; import 'services/path_history_service.dart'; import 'services/app_settings_service.dart'; import 'services/notification_service.dart'; import 'services/ble_debug_log_service.dart'; import 'services/app_debug_log_service.dart'; import 'services/background_service.dart'; import 'services/map_tile_cache_service.dart'; import 'services/chat_text_scale_service.dart'; import 'services/ui_view_state_service.dart'; import 'services/timeout_prediction_service.dart'; import 'storage/prefs_manager.dart'; import 'utils/app_logger.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize SharedPreferences cache await PrefsManager.initialize(); // Initialize services final storage = StorageService(); final connector = MeshCoreConnector(); final pathHistoryService = PathHistoryService(storage); final retryService = MessageRetryService(); final appSettingsService = AppSettingsService(); final bleDebugLogService = BleDebugLogService(); final appDebugLogService = AppDebugLogService(); final backgroundService = BackgroundService(); final mapTileCacheService = MapTileCacheService(); final chatTextScaleService = ChatTextScaleService(); final uiViewStateService = UiViewStateService(); final timeoutPredictionService = TimeoutPredictionService(storage); // Load settings await appSettingsService.loadSettings(); // Initialize app logger appLogger.initialize( appDebugLogService, enabled: appSettingsService.settings.appDebugLogEnabled, ); // Initialize notification service final notificationService = NotificationService(); await notificationService.initialize(); await backgroundService.initialize(); _registerThirdPartyLicenses(); await chatTextScaleService.initialize(); await uiViewStateService.initialize(); await timeoutPredictionService.initialize(); // Wire up connector with services connector.initialize( retryService: retryService, pathHistoryService: pathHistoryService, appSettingsService: appSettingsService, bleDebugLogService: bleDebugLogService, appDebugLogService: appDebugLogService, backgroundService: backgroundService, timeoutPredictionService: timeoutPredictionService, ); await connector.loadContactCache(); await connector.loadChannelSettings(); await connector.loadCachedChannels(); // Load persisted channel messages await connector.loadAllChannelMessages(); await connector.loadUnreadState(); runApp( MeshCoreApp( connector: connector, retryService: retryService, pathHistoryService: pathHistoryService, storage: storage, appSettingsService: appSettingsService, bleDebugLogService: bleDebugLogService, appDebugLogService: appDebugLogService, mapTileCacheService: mapTileCacheService, chatTextScaleService: chatTextScaleService, uiViewStateService: uiViewStateService, timeoutPredictionService: timeoutPredictionService, ), ); } void _registerThirdPartyLicenses() { LicenseRegistry.addLicense(() async* { yield const LicenseEntryWithLineBreaks( ['Open-Meteo Elevation API Data'], ''' Data used by LOS elevation lookups is provided by Open-Meteo. Open-Meteo terms and attribution: https://open-meteo.com/en/terms Elevation API: https://open-meteo.com/en/docs/elevation-api Attribution license reference: Creative Commons Attribution 4.0 International (CC BY 4.0) https://creativecommons.org/licenses/by/4.0/ ''', ); }); } class MeshCoreApp extends StatelessWidget { final MeshCoreConnector connector; final MessageRetryService retryService; final PathHistoryService pathHistoryService; final StorageService storage; final AppSettingsService appSettingsService; final BleDebugLogService bleDebugLogService; final AppDebugLogService appDebugLogService; final MapTileCacheService mapTileCacheService; final ChatTextScaleService chatTextScaleService; final UiViewStateService uiViewStateService; final TimeoutPredictionService timeoutPredictionService; const MeshCoreApp({ super.key, required this.connector, required this.retryService, required this.pathHistoryService, required this.storage, required this.appSettingsService, required this.bleDebugLogService, required this.appDebugLogService, required this.mapTileCacheService, required this.chatTextScaleService, required this.uiViewStateService, required this.timeoutPredictionService, }); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: connector), ChangeNotifierProvider.value(value: retryService), ChangeNotifierProvider.value(value: pathHistoryService), ChangeNotifierProvider.value(value: appSettingsService), ChangeNotifierProvider.value(value: bleDebugLogService), ChangeNotifierProvider.value(value: appDebugLogService), ChangeNotifierProvider.value(value: chatTextScaleService), ChangeNotifierProvider.value(value: uiViewStateService), Provider.value(value: storage), Provider.value(value: mapTileCacheService), ChangeNotifierProvider.value(value: timeoutPredictionService), ], child: Consumer( builder: (context, settingsService, child) { return MaterialApp( title: 'MeshCore Open', debugShowCheckedModeBanner: false, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, locale: _localeFromSetting( settingsService.settings.languageOverride, ), theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, snackBarTheme: const SnackBarThemeData( behavior: SnackBarBehavior.floating, ), ), darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), useMaterial3: true, snackBarTheme: const SnackBarThemeData( behavior: SnackBarBehavior.floating, ), ), themeMode: _themeModeFromSetting( settingsService.settings.themeMode, ), builder: (context, child) { // Update notification service with resolved locale final locale = Localizations.localeOf(context); NotificationService().setLocale(locale); return child ?? const SizedBox.shrink(); }, home: (PlatformInfo.isWeb && !PlatformInfo.isChrome) ? const ChromeRequiredScreen() : const ScannerScreen(), ); }, ), ); } ThemeMode _themeModeFromSetting(String value) { switch (value) { case 'light': return ThemeMode.light; case 'dark': return ThemeMode.dark; default: return ThemeMode.system; } } Locale? _localeFromSetting(String? languageCode) { if (languageCode == null) return null; return Locale(languageCode); } }