mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Remove hex logs and keep path data
This commit is contained in:
@@ -249,17 +249,14 @@ class AppProvider with ChangeNotifier {
|
||||
|
||||
/// Sync drawings from messages on app startup (before BLE connection)
|
||||
Future<void> _syncDrawingsOnStartup() async {
|
||||
// Wait for MessagesProvider to finish initializing
|
||||
// DrawingProvider loads around the same time
|
||||
// Wait for both MessagesProvider and DrawingProvider to finish initializing
|
||||
int attempts = 0;
|
||||
while (!messagesProvider.isInitialized && attempts < 20) {
|
||||
while ((!messagesProvider.isInitialized || !drawingProvider.isInitialized) &&
|
||||
attempts < 40) {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Give DrawingProvider a moment to finish loading too
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
_restoreSessionMetadataFromMessages();
|
||||
|
||||
debugPrint(
|
||||
@@ -975,9 +972,10 @@ class AppProvider with ChangeNotifier {
|
||||
if (AppProvider.shouldIgnoreSelfReplay(
|
||||
message: message,
|
||||
ownPublicKey: connectionProvider.deviceInfo.publicKey,
|
||||
ownName:
|
||||
connectionProvider.deviceInfo.deviceName ??
|
||||
connectionProvider.deviceInfo.selfName,
|
||||
ownName: _preferredSelfDisplayName(
|
||||
deviceName: connectionProvider.deviceInfo.deviceName,
|
||||
selfName: connectionProvider.deviceInfo.selfName,
|
||||
),
|
||||
)) {
|
||||
debugPrint('⏭️ [AppProvider] Ignoring self replay: ${message.id}');
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ class DrawingProvider with ChangeNotifier {
|
||||
|
||||
// Completed drawings
|
||||
final List<MapDrawing> _drawings = [];
|
||||
bool _isInitialized = false;
|
||||
|
||||
// In-progress drawing
|
||||
MapDrawing? _currentDrawing;
|
||||
@@ -53,6 +54,7 @@ class DrawingProvider with ChangeNotifier {
|
||||
List<LatLng> get currentLinePoints => List.unmodifiable(_currentLinePoints);
|
||||
LatLng? get rectangleStartPoint => _rectangleStartPoint;
|
||||
bool get isDrawing => _drawingMode != DrawingMode.none;
|
||||
bool get isInitialized => _isInitialized;
|
||||
LatLng? get measurementPoint1 => _measurementPoint1;
|
||||
LatLng? get measurementPoint2 => _measurementPoint2;
|
||||
double? get measuredDistance => _measuredDistance;
|
||||
@@ -61,6 +63,7 @@ class DrawingProvider with ChangeNotifier {
|
||||
Future<void> initialize() async {
|
||||
await _loadPreferences();
|
||||
await _loadDrawings();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// Set drawing mode
|
||||
|
||||
@@ -28,6 +28,8 @@ class MessagesProvider with ChangeNotifier {
|
||||
final MessageStorageService _storageService = MessageStorageService();
|
||||
final NotificationService _notificationService = NotificationService();
|
||||
bool _isInitialized = false;
|
||||
bool _isPersisting = false;
|
||||
bool _persistRequested = false;
|
||||
AppLocalizations? _localizations;
|
||||
final Map<String, MessageContactLocation> _messageContactLocations = {};
|
||||
final Map<String, MessageReceptionDetails> _messageReceptionDetails = {};
|
||||
@@ -1018,18 +1020,29 @@ class MessagesProvider with ChangeNotifier {
|
||||
return '${mib.toStringAsFixed(mib >= 10 ? 0 : 1)} MB';
|
||||
}
|
||||
|
||||
/// Persist messages to storage (async, non-blocking)
|
||||
/// Persist messages to storage (async, non-blocking, coalescing).
|
||||
///
|
||||
/// Multiple rapid calls are coalesced into a single write to avoid
|
||||
/// overlapping serialization and redundant SharedPreferences writes.
|
||||
Future<void> _persistMessages() async {
|
||||
_persistRequested = true;
|
||||
if (_isPersisting) return; // A write is in flight; it will pick up our changes.
|
||||
_isPersisting = true;
|
||||
try {
|
||||
await _storageService.saveMessages(
|
||||
_messages,
|
||||
messageContactLocations: _messageContactLocations,
|
||||
messageReceptionDetails: _messageReceptionDetails,
|
||||
messageTransferDetails: _messageTransferDetails,
|
||||
messageRouteMetadata: _messageRouteMetadata,
|
||||
);
|
||||
while (_persistRequested) {
|
||||
_persistRequested = false;
|
||||
await _storageService.saveMessages(
|
||||
_messages,
|
||||
messageContactLocations: _messageContactLocations,
|
||||
messageReceptionDetails: _messageReceptionDetails,
|
||||
messageTransferDetails: _messageTransferDetails,
|
||||
messageRouteMetadata: _messageRouteMetadata,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MessagesProvider] Error persisting messages: $e');
|
||||
} finally {
|
||||
_isPersisting = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import '../services/image_preferences.dart';
|
||||
import '../services/route_hash_preferences.dart';
|
||||
import '../services/image_codec_service.dart';
|
||||
import '../services/developer_mode_service.dart';
|
||||
import '../services/notification_service.dart';
|
||||
import '../utils/sample_data_generator.dart';
|
||||
import '../utils/image_message_parser.dart';
|
||||
import '../utils/voice_message_parser.dart';
|
||||
@@ -77,6 +78,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
bool _rotateMapWithHeading = false;
|
||||
bool _showMapDebugInfo = false;
|
||||
bool _openMapInFullscreen = false;
|
||||
bool _messageNotificationsEnabled = true;
|
||||
bool _sarNotificationsEnabled = true;
|
||||
bool _updateNotificationsEnabled = true;
|
||||
bool _muteForegroundNotifications = true;
|
||||
bool _isDeveloperModeEnabled = false;
|
||||
DateTime? _onlineTraceCacheUpdatedAt;
|
||||
bool _isClearingOnlineTraceCache = false;
|
||||
@@ -99,6 +104,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_loadDeveloperMode();
|
||||
_loadOnlineTraceCacheStatus();
|
||||
_loadMapPreferences();
|
||||
_loadNotificationPreferences();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -136,6 +142,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadNotificationPreferences() async {
|
||||
final service = NotificationService();
|
||||
await service.initialize();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_messageNotificationsEnabled = service.messageNotificationsEnabled;
|
||||
_sarNotificationsEnabled = service.sarNotificationsEnabled;
|
||||
_updateNotificationsEnabled = service.updateNotificationsEnabled;
|
||||
_muteForegroundNotifications = service.muteForegroundNotifications;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadOnlineTraceCacheStatus() async {
|
||||
final cachedAt = await MeshMapNodesService.cachedAt();
|
||||
if (!mounted) return;
|
||||
@@ -187,8 +205,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_rotateMapWithHeading =
|
||||
prefs.getBool('map_rotate_with_heading') ?? false;
|
||||
_rotateMapWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
||||
_showMapDebugInfo = prefs.getBool('map_show_debug_info') ?? false;
|
||||
_openMapInFullscreen = prefs.getBool('map_fullscreen') ?? false;
|
||||
});
|
||||
@@ -1002,6 +1019,72 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
]),
|
||||
|
||||
_buildSectionHeader('Notifications'),
|
||||
_buildSettingsCard([
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.chat_bubble_outline),
|
||||
title: const Text('Message notifications'),
|
||||
subtitle: const Text(
|
||||
'Notify for incoming direct and channel messages',
|
||||
),
|
||||
value: _messageNotificationsEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_messageNotificationsEnabled = value;
|
||||
});
|
||||
await NotificationService().setMessageNotificationsEnabled(
|
||||
value,
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.warning_amber_outlined),
|
||||
title: const Text('SAR alerts'),
|
||||
subtitle: const Text(
|
||||
'Notify for incoming SAR markers such as found person or fire',
|
||||
),
|
||||
value: _sarNotificationsEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_sarNotificationsEnabled = value;
|
||||
});
|
||||
await NotificationService().setSarNotificationsEnabled(value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.system_update),
|
||||
title: const Text('Update notifications'),
|
||||
subtitle: const Text(
|
||||
'Notify when a newer app version is available',
|
||||
),
|
||||
value: _updateNotificationsEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_updateNotificationsEnabled = value;
|
||||
});
|
||||
await NotificationService().setUpdateNotificationsEnabled(
|
||||
value,
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.visibility_off_outlined),
|
||||
title: const Text('Mute while app is open'),
|
||||
subtitle: const Text(
|
||||
'Do not show local notifications while the app is in the foreground',
|
||||
),
|
||||
value: _muteForegroundNotifications,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_muteForegroundNotifications = value;
|
||||
});
|
||||
await NotificationService().setMuteForegroundNotifications(
|
||||
value,
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
|
||||
_buildSectionHeader('Navigation'),
|
||||
_buildSettingsCard([
|
||||
Consumer<AppProvider>(
|
||||
@@ -1224,9 +1307,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
builder: (context, drawingProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.fmd_good_outlined),
|
||||
title: const Text('Show SAR markers'),
|
||||
subtitle: const Text(
|
||||
'Display SAR markers on the main map',
|
||||
),
|
||||
subtitle: const Text('Display SAR markers on the main map'),
|
||||
value: drawingProvider.showSarMarkers,
|
||||
onChanged: (value) {
|
||||
drawingProvider.toggleSarMarkers();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import '../models/sar_marker.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
@@ -13,9 +14,18 @@ class NotificationService {
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static const String _prefMessagesEnabled = 'notifications_messages_enabled';
|
||||
static const String _prefSarEnabled = 'notifications_sar_enabled';
|
||||
static const String _prefUpdatesEnabled = 'notifications_updates_enabled';
|
||||
static const String _prefMuteForeground = 'notifications_mute_foreground';
|
||||
|
||||
bool _isInitialized = false;
|
||||
bool _permissionGranted = false;
|
||||
bool _messageNotificationsEnabled = true;
|
||||
bool _sarNotificationsEnabled = true;
|
||||
bool _updateNotificationsEnabled = true;
|
||||
bool _muteForegroundNotifications = true;
|
||||
AppLifecycleState _lifecycleState = AppLifecycleState.resumed;
|
||||
|
||||
// Notification IDs
|
||||
static const int _sarNotificationId = 1000;
|
||||
@@ -38,12 +48,21 @@ class NotificationService {
|
||||
static const String _updateChannelDescription =
|
||||
'Notifications for available app updates';
|
||||
|
||||
bool get messageNotificationsEnabled => _messageNotificationsEnabled;
|
||||
bool get sarNotificationsEnabled => _sarNotificationsEnabled;
|
||||
bool get updateNotificationsEnabled => _updateNotificationsEnabled;
|
||||
bool get muteForegroundNotifications => _muteForegroundNotifications;
|
||||
bool get isAppInForeground => _lifecycleState == AppLifecycleState.resumed;
|
||||
|
||||
/// Initialize notification service
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
debugPrint('📬 [NotificationService] Initializing...');
|
||||
WidgetsBinding.instance.addObserver(_LifecycleObserver(this));
|
||||
_lifecycleState =
|
||||
WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed;
|
||||
|
||||
// Initialize timezone data
|
||||
tz.initializeTimeZones();
|
||||
@@ -74,6 +93,7 @@ class NotificationService {
|
||||
|
||||
// Request permissions
|
||||
await _requestPermissions();
|
||||
await _loadPreferences();
|
||||
|
||||
// Create notification channels (Android)
|
||||
await _createNotificationChannels();
|
||||
@@ -132,6 +152,46 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_messageNotificationsEnabled = prefs.getBool(_prefMessagesEnabled) ?? true;
|
||||
_sarNotificationsEnabled = prefs.getBool(_prefSarEnabled) ?? true;
|
||||
_updateNotificationsEnabled = prefs.getBool(_prefUpdatesEnabled) ?? true;
|
||||
_muteForegroundNotifications = prefs.getBool(_prefMuteForeground) ?? true;
|
||||
}
|
||||
|
||||
Future<void> setMessageNotificationsEnabled(bool value) async {
|
||||
_messageNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefMessagesEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setSarNotificationsEnabled(bool value) async {
|
||||
_sarNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefSarEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setUpdateNotificationsEnabled(bool value) async {
|
||||
_updateNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefUpdatesEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setMuteForegroundNotifications(bool value) async {
|
||||
_muteForegroundNotifications = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefMuteForeground, value);
|
||||
}
|
||||
|
||||
void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
||||
_lifecycleState = state;
|
||||
}
|
||||
|
||||
bool _shouldSuppressForegroundNotifications() {
|
||||
return _muteForegroundNotifications && isAppInForeground;
|
||||
}
|
||||
|
||||
/// Create notification channels for Android
|
||||
Future<void> _createNotificationChannels() async {
|
||||
try {
|
||||
@@ -222,6 +282,16 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_sarNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] SAR notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping SAR notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate unique notification ID based on timestamp
|
||||
@@ -394,6 +464,16 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_messageNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] Message notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping message notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate unique notification ID based on timestamp
|
||||
@@ -547,6 +627,16 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_updateNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] Update notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping update notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Build notification title and body
|
||||
@@ -618,3 +708,14 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _LifecycleObserver with WidgetsBindingObserver {
|
||||
_LifecycleObserver(this._service);
|
||||
|
||||
final NotificationService _service;
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_service.handleAppLifecycleStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user