2025-12-26 11:42:02 -07:00
|
|
|
import 'package:flutter/material.dart';
|
2026-01-11 17:13:50 -07:00
|
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
2026-02-21 01:08:23 -05:00
|
|
|
import 'package:flutter/foundation.dart';
|
2026-01-11 17:13:50 -07:00
|
|
|
import 'l10n/app_localizations.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
2026-02-22 07:40:40 -08:00
|
|
|
import 'screens/chrome_required_screen.dart';
|
|
|
|
|
import 'utils/platform_info.dart';
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
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';
|
2026-01-02 14:22:39 -07:00
|
|
|
import 'services/app_debug_log_service.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import 'services/background_service.dart';
|
|
|
|
|
import 'services/map_tile_cache_service.dart';
|
2026-02-23 22:41:32 -05:00
|
|
|
import 'services/chat_text_scale_service.dart';
|
2026-03-15 00:34:09 +01:00
|
|
|
import 'services/ui_view_state_service.dart';
|
2026-03-14 16:56:11 -07:00
|
|
|
import 'services/timeout_prediction_service.dart';
|
2025-12-30 19:27:25 -07:00
|
|
|
import 'storage/prefs_manager.dart';
|
2026-01-02 14:22:39 -07:00
|
|
|
import 'utils/app_logger.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
void main() async {
|
|
|
|
|
WidgetsFlutterBinding.ensureInitialized();
|
2025-12-30 19:27:25 -07:00
|
|
|
|
|
|
|
|
// Initialize SharedPreferences cache
|
|
|
|
|
await PrefsManager.initialize();
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
// Initialize services
|
|
|
|
|
final storage = StorageService();
|
|
|
|
|
final connector = MeshCoreConnector();
|
|
|
|
|
final pathHistoryService = PathHistoryService(storage);
|
2026-01-17 11:00:34 -05:00
|
|
|
final retryService = MessageRetryService();
|
2025-12-26 11:42:02 -07:00
|
|
|
final appSettingsService = AppSettingsService();
|
|
|
|
|
final bleDebugLogService = BleDebugLogService();
|
2026-01-02 14:22:39 -07:00
|
|
|
final appDebugLogService = AppDebugLogService();
|
2025-12-27 15:32:32 -07:00
|
|
|
final backgroundService = BackgroundService();
|
|
|
|
|
final mapTileCacheService = MapTileCacheService();
|
2026-02-23 22:41:32 -05:00
|
|
|
final chatTextScaleService = ChatTextScaleService();
|
2026-03-15 00:34:09 +01:00
|
|
|
final uiViewStateService = UiViewStateService();
|
2026-03-14 16:56:11 -07:00
|
|
|
final timeoutPredictionService = TimeoutPredictionService(storage);
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
// Load settings
|
|
|
|
|
await appSettingsService.loadSettings();
|
|
|
|
|
|
2026-01-02 14:22:39 -07:00
|
|
|
// Initialize app logger
|
|
|
|
|
appLogger.initialize(
|
|
|
|
|
appDebugLogService,
|
|
|
|
|
enabled: appSettingsService.settings.appDebugLogEnabled,
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
// Initialize notification service
|
|
|
|
|
final notificationService = NotificationService();
|
|
|
|
|
await notificationService.initialize();
|
2025-12-27 15:32:32 -07:00
|
|
|
await backgroundService.initialize();
|
2026-02-21 01:08:23 -05:00
|
|
|
_registerThirdPartyLicenses();
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2026-02-23 22:41:32 -05:00
|
|
|
await chatTextScaleService.initialize();
|
2026-03-15 00:34:09 +01:00
|
|
|
await uiViewStateService.initialize();
|
2026-03-14 16:56:11 -07:00
|
|
|
await timeoutPredictionService.initialize();
|
2026-02-23 22:41:32 -05:00
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
// Wire up connector with services
|
|
|
|
|
connector.initialize(
|
|
|
|
|
retryService: retryService,
|
|
|
|
|
pathHistoryService: pathHistoryService,
|
|
|
|
|
appSettingsService: appSettingsService,
|
|
|
|
|
bleDebugLogService: bleDebugLogService,
|
2026-01-02 14:22:39 -07:00
|
|
|
appDebugLogService: appDebugLogService,
|
2025-12-27 15:32:32 -07:00
|
|
|
backgroundService: backgroundService,
|
2026-03-14 16:56:11 -07:00
|
|
|
timeoutPredictionService: timeoutPredictionService,
|
2025-12-26 11:42:02 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await connector.loadContactCache();
|
|
|
|
|
await connector.loadChannelSettings();
|
2026-02-04 20:34:03 -07:00
|
|
|
await connector.loadCachedChannels();
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
// Load persisted channel messages
|
|
|
|
|
await connector.loadAllChannelMessages();
|
2025-12-26 13:33:03 -07:00
|
|
|
await connector.loadUnreadState();
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2026-02-04 08:32:35 -08:00
|
|
|
runApp(
|
|
|
|
|
MeshCoreApp(
|
|
|
|
|
connector: connector,
|
|
|
|
|
retryService: retryService,
|
|
|
|
|
pathHistoryService: pathHistoryService,
|
|
|
|
|
storage: storage,
|
|
|
|
|
appSettingsService: appSettingsService,
|
|
|
|
|
bleDebugLogService: bleDebugLogService,
|
|
|
|
|
appDebugLogService: appDebugLogService,
|
|
|
|
|
mapTileCacheService: mapTileCacheService,
|
2026-02-23 22:41:32 -05:00
|
|
|
chatTextScaleService: chatTextScaleService,
|
2026-03-15 00:34:09 +01:00
|
|
|
uiViewStateService: uiViewStateService,
|
2026-03-14 16:56:11 -07:00
|
|
|
timeoutPredictionService: timeoutPredictionService,
|
2026-02-04 08:32:35 -08:00
|
|
|
),
|
|
|
|
|
);
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 01:08:23 -05:00
|
|
|
void _registerThirdPartyLicenses() {
|
|
|
|
|
LicenseRegistry.addLicense(() async* {
|
|
|
|
|
yield const LicenseEntryWithLineBreaks(
|
|
|
|
|
<String>['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/
|
|
|
|
|
''',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
class MeshCoreApp extends StatelessWidget {
|
|
|
|
|
final MeshCoreConnector connector;
|
|
|
|
|
final MessageRetryService retryService;
|
|
|
|
|
final PathHistoryService pathHistoryService;
|
|
|
|
|
final StorageService storage;
|
|
|
|
|
final AppSettingsService appSettingsService;
|
|
|
|
|
final BleDebugLogService bleDebugLogService;
|
2026-01-02 14:22:39 -07:00
|
|
|
final AppDebugLogService appDebugLogService;
|
2025-12-27 15:32:32 -07:00
|
|
|
final MapTileCacheService mapTileCacheService;
|
2026-02-23 22:41:32 -05:00
|
|
|
final ChatTextScaleService chatTextScaleService;
|
2026-03-15 00:34:09 +01:00
|
|
|
final UiViewStateService uiViewStateService;
|
2026-03-14 16:56:11 -07:00
|
|
|
final TimeoutPredictionService timeoutPredictionService;
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
const MeshCoreApp({
|
|
|
|
|
super.key,
|
|
|
|
|
required this.connector,
|
|
|
|
|
required this.retryService,
|
|
|
|
|
required this.pathHistoryService,
|
|
|
|
|
required this.storage,
|
|
|
|
|
required this.appSettingsService,
|
|
|
|
|
required this.bleDebugLogService,
|
2026-01-02 14:22:39 -07:00
|
|
|
required this.appDebugLogService,
|
2025-12-27 15:32:32 -07:00
|
|
|
required this.mapTileCacheService,
|
2026-02-23 22:41:32 -05:00
|
|
|
required this.chatTextScaleService,
|
2026-03-15 00:34:09 +01:00
|
|
|
required this.uiViewStateService,
|
2026-03-14 16:56:11 -07:00
|
|
|
required this.timeoutPredictionService,
|
2025-12-26 11:42:02 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@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),
|
2026-01-02 14:22:39 -07:00
|
|
|
ChangeNotifierProvider.value(value: appDebugLogService),
|
2026-02-23 22:41:32 -05:00
|
|
|
ChangeNotifierProvider.value(value: chatTextScaleService),
|
2026-03-15 00:34:09 +01:00
|
|
|
ChangeNotifierProvider.value(value: uiViewStateService),
|
2025-12-26 11:42:02 -07:00
|
|
|
Provider.value(value: storage),
|
2025-12-27 15:32:32 -07:00
|
|
|
Provider.value(value: mapTileCacheService),
|
2026-03-14 16:56:11 -07:00
|
|
|
ChangeNotifierProvider.value(value: timeoutPredictionService),
|
2025-12-26 11:42:02 -07:00
|
|
|
],
|
|
|
|
|
child: Consumer<AppSettingsService>(
|
|
|
|
|
builder: (context, settingsService, child) {
|
|
|
|
|
return MaterialApp(
|
|
|
|
|
title: 'MeshCore Open',
|
|
|
|
|
debugShowCheckedModeBanner: false,
|
2026-01-11 17:13:50 -07:00
|
|
|
localizationsDelegates: const [
|
|
|
|
|
AppLocalizations.delegate,
|
|
|
|
|
GlobalMaterialLocalizations.delegate,
|
|
|
|
|
GlobalWidgetsLocalizations.delegate,
|
|
|
|
|
GlobalCupertinoLocalizations.delegate,
|
|
|
|
|
],
|
|
|
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
2026-02-04 08:32:35 -08:00
|
|
|
locale: _localeFromSetting(
|
|
|
|
|
settingsService.settings.languageOverride,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
theme: ThemeData(
|
|
|
|
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
|
|
|
|
useMaterial3: true,
|
2026-02-01 18:37:14 -07:00
|
|
|
snackBarTheme: const SnackBarThemeData(
|
|
|
|
|
behavior: SnackBarBehavior.floating,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
darkTheme: ThemeData(
|
|
|
|
|
colorScheme: ColorScheme.fromSeed(
|
|
|
|
|
seedColor: Colors.blue,
|
|
|
|
|
brightness: Brightness.dark,
|
|
|
|
|
),
|
|
|
|
|
useMaterial3: true,
|
2026-02-01 18:37:14 -07:00
|
|
|
snackBarTheme: const SnackBarThemeData(
|
|
|
|
|
behavior: SnackBarBehavior.floating,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
themeMode: _themeModeFromSetting(
|
|
|
|
|
settingsService.settings.themeMode,
|
|
|
|
|
),
|
Notification rate limiting (#110)
* Add notification rate limiting with privacy-safe debug logging
- Add batching system to prevent notification storms (3s rate limit, 5s batch window)
- Queue rapid notifications and show batch summaries
- Debug logs show device names for adverts, sender/channel for messages (no content leaks)
- Remove unused _maxBatchSize constant
Context: Added after getting notification-flooded while evaluating RF flood management. The irony.
* Update notification_service.dart
I made a mistake and removed this
* Add l10n support for notification strings
Addresses PR #110 review feedback to use the translations system:
- Add notification strings to app_en.arb (plurals for batch summary)
- Update NotificationService to use lookupAppLocalizations()
- Wire locale from MaterialApp to NotificationService
- Regenerate localization files
New strings added (English only, translations needed):
- notification_activityTitle: "MeshCore Activity"
- notification_messagesCount: "{count} message(s)"
- notification_channelMessagesCount: "{count} channel message(s)"
- notification_newNodesCount: "{count} new node(s)"
- notification_newTypeDiscovered: "New {type} discovered"
- notification_receivedNewMessage: "Received new message"
* Add notification string translations for all supported languages
Translated notification_activityTitle, notification_messagesCount,
notification_channelMessagesCount, notification_newNodesCount,
notification_newTypeDiscovered, and notification_receivedNewMessage
to: bg, de, es, fr, it, nl, pl, pt, ru, sk, sl, sv, uk, zh
Includes proper ICU plural forms for Slavic languages (few/many/other)
and Slovenian dual form.
* Apply dart format to notification_service.dart
---------
Co-authored-by: Winston Lowe <wel97459@gmail.com>
2026-02-08 19:42:15 -07:00
|
|
|
builder: (context, child) {
|
|
|
|
|
// Update notification service with resolved locale
|
|
|
|
|
final locale = Localizations.localeOf(context);
|
|
|
|
|
NotificationService().setLocale(locale);
|
|
|
|
|
return child ?? const SizedBox.shrink();
|
|
|
|
|
},
|
2026-02-22 07:40:40 -08:00
|
|
|
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
|
|
|
|
|
? const ChromeRequiredScreen()
|
|
|
|
|
: const ScannerScreen(),
|
2025-12-26 11:42:02 -07:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ThemeMode _themeModeFromSetting(String value) {
|
|
|
|
|
switch (value) {
|
|
|
|
|
case 'light':
|
|
|
|
|
return ThemeMode.light;
|
|
|
|
|
case 'dark':
|
|
|
|
|
return ThemeMode.dark;
|
|
|
|
|
default:
|
|
|
|
|
return ThemeMode.system;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-11 17:13:50 -07:00
|
|
|
|
|
|
|
|
Locale? _localeFromSetting(String? languageCode) {
|
|
|
|
|
if (languageCode == null) return null;
|
|
|
|
|
return Locale(languageCode);
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|