mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
refactor: Improve connection and room login state management by utilizing helper methods
This commit is contained in:
88
CLAUDE.md
88
CLAUDE.md
@@ -29,27 +29,58 @@ lib/
|
|||||||
│ ├── message.dart # Messages and SAR markers
|
│ ├── message.dart # Messages and SAR markers
|
||||||
│ ├── sar_marker.dart # SAR tactical markers
|
│ ├── sar_marker.dart # SAR tactical markers
|
||||||
│ ├── device_info.dart # BLE device connection state
|
│ ├── device_info.dart # BLE device connection state
|
||||||
|
│ ├── room_login_state.dart # Room login status tracking
|
||||||
│ └── map_layer.dart # Map tile layer definitions
|
│ └── map_layer.dart # Map tile layer definitions
|
||||||
├── services/ # Business logic services
|
├── services/ # Business logic services
|
||||||
│ ├── meshcore_ble_service.dart # BLE communication
|
│ ├── meshcore_ble_service.dart # BLE service coordinator (399 lines)
|
||||||
│ ├── meshcore_constants.dart # Protocol constants
|
│ ├── meshcore_constants.dart # Protocol constants
|
||||||
│ ├── buffer_reader.dart # Binary protocol reader
|
│ ├── buffer_reader.dart # Binary protocol reader
|
||||||
│ ├── buffer_writer.dart # Binary protocol writer
|
│ ├── buffer_writer.dart # Binary protocol writer
|
||||||
│ ├── cayenne_lpp_parser.dart # Telemetry decoder
|
│ ├── cayenne_lpp_parser.dart # Telemetry decoder
|
||||||
│ └── tile_cache_service.dart # Offline map tiles
|
│ ├── tile_cache_service.dart # Offline map tiles
|
||||||
|
│ ├── protocol/ # Protocol layer (628 lines)
|
||||||
|
│ │ ├── frame_parser.dart # Parse incoming BLE frames
|
||||||
|
│ │ └── frame_builder.dart # Build outgoing BLE frames
|
||||||
|
│ └── ble/ # BLE layer (963 lines)
|
||||||
|
│ ├── ble_connection_manager.dart # Connection lifecycle
|
||||||
|
│ ├── ble_command_sender.dart # Command transmission
|
||||||
|
│ └── ble_response_handler.dart # Response processing
|
||||||
├── providers/ # State management
|
├── providers/ # State management
|
||||||
│ ├── connection_provider.dart # BLE connection state
|
│ ├── connection_provider.dart # BLE connection state (957 lines)
|
||||||
│ ├── contacts_provider.dart # Contact list management
|
│ ├── contacts_provider.dart # Contact list management
|
||||||
│ ├── messages_provider.dart # Message history + SAR markers
|
│ ├── messages_provider.dart # Message history + SAR markers
|
||||||
│ ├── map_provider.dart # Map navigation state
|
│ ├── map_provider.dart # Map navigation state
|
||||||
│ └── app_provider.dart # Coordinator provider
|
│ ├── app_provider.dart # Coordinator provider
|
||||||
|
│ └── helpers/ # Provider helpers (155 lines)
|
||||||
|
│ ├── room_login_manager.dart # Room login state management
|
||||||
|
│ └── message_delivery_tracker.dart # Message delivery tracking
|
||||||
├── screens/ # UI screens
|
├── screens/ # UI screens
|
||||||
│ ├── home_screen.dart # Main screen with tabs
|
│ ├── home_screen.dart # Main screen with tabs
|
||||||
│ ├── messages_tab.dart # Message list view
|
│ ├── messages_tab.dart # Message list view (767 lines)
|
||||||
│ ├── contacts_tab.dart # Contact list view
|
│ ├── contacts_tab.dart # Contact list view (208 lines)
|
||||||
│ └── map_tab.dart # Interactive map view
|
│ ├── map_tab.dart # Interactive map view (1,217 lines)
|
||||||
|
│ ├── settings_screen.dart # App settings (875 lines)
|
||||||
|
│ ├── device_config_screen.dart # Device configuration (718 lines)
|
||||||
|
│ ├── map_management_screen.dart # Map tile management (728 lines)
|
||||||
|
│ └── packet_log_screen.dart # BLE packet diagnostics (565 lines)
|
||||||
├── widgets/ # Reusable UI components
|
├── widgets/ # Reusable UI components
|
||||||
│ └── map_markers.dart # Custom map marker widgets
|
│ ├── map_markers.dart # Custom map marker widgets
|
||||||
|
│ ├── messages/ # Message components (698 lines)
|
||||||
|
│ │ └── sar_update_sheet.dart # SAR marker creation modal
|
||||||
|
│ ├── contacts/ # Contact components (1,578 lines)
|
||||||
|
│ │ ├── contact_tile.dart # Contact list tile + details dialog
|
||||||
|
│ │ ├── direct_message_sheet.dart # Direct messaging modal
|
||||||
|
│ │ ├── room_login_sheet.dart # Room login modal
|
||||||
|
│ │ └── section_header.dart # Section header component
|
||||||
|
│ └── map/ # Map components (2,867 lines)
|
||||||
|
│ ├── detailed_compass_dialog.dart # Main compass dialog (570 lines)
|
||||||
|
│ ├── map_legend.dart # Map legend with counts
|
||||||
|
│ ├── compass_widget.dart # Small compass widget
|
||||||
|
│ └── compass/ # Compass subcomponents (1,150 lines)
|
||||||
|
│ ├── compass_header.dart # Compass rose + location display
|
||||||
|
│ ├── compass_filters.dart # Filter controls
|
||||||
|
│ ├── compass_sar_list.dart # SAR marker list
|
||||||
|
│ └── compass_contact_list.dart # Contact list
|
||||||
├── utils/ # Utilities
|
├── utils/ # Utilities
|
||||||
│ └── sar_message_parser.dart # Parse S:<emoji>:lat,lon format
|
│ └── sar_message_parser.dart # Parse S:<emoji>:lat,lon format
|
||||||
└── main.dart # App entry point
|
└── main.dart # App entry point
|
||||||
@@ -1011,28 +1042,57 @@ flutter build apk --split-per-abi
|
|||||||
|
|
||||||
### Adding a New BLE Command
|
### Adding a New BLE Command
|
||||||
|
|
||||||
|
The BLE service is now split into modular components. Follow these steps:
|
||||||
|
|
||||||
1. **Add command code** to `lib/services/meshcore_constants.dart`:
|
1. **Add command code** to `lib/services/meshcore_constants.dart`:
|
||||||
```dart
|
```dart
|
||||||
static const int cmdYourCommand = 42;
|
static const int cmdYourCommand = 42;
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Create command method** in `lib/services/meshcore_ble_service.dart`:
|
2. **Add frame builder** in `lib/services/protocol/frame_builder.dart`:
|
||||||
```dart
|
```dart
|
||||||
Future<void> yourCommand() async {
|
/// Build YOUR_COMMAND frame
|
||||||
|
static Uint8List buildYourCommand({required String param}) {
|
||||||
final writer = BufferWriter();
|
final writer = BufferWriter();
|
||||||
writer.writeByte(MeshCoreConstants.cmdYourCommand);
|
writer.writeByte(MeshCoreConstants.cmdYourCommand);
|
||||||
await _sendCommand(writer.toBytes());
|
writer.writeString(param);
|
||||||
|
return writer.toBytes();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Handle response** in `_onDataReceived()`:
|
3. **Add public API method** in `lib/services/meshcore_ble_service.dart`:
|
||||||
|
```dart
|
||||||
|
Future<void> yourCommand({required String param}) async {
|
||||||
|
final frame = FrameBuilder.buildYourCommand(param: param);
|
||||||
|
await _commandSender.sendCommand(frame);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Add response parser** in `lib/services/protocol/frame_parser.dart`:
|
||||||
|
```dart
|
||||||
|
static Map<String, dynamic> parseYourResponse(Uint8List data) {
|
||||||
|
final reader = BufferReader(data);
|
||||||
|
reader.readByte(); // Skip response code
|
||||||
|
return {
|
||||||
|
'yourField': reader.readString(),
|
||||||
|
// ... parse other fields
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Handle response** in `lib/services/ble/ble_response_handler.dart`:
|
||||||
```dart
|
```dart
|
||||||
case MeshCoreConstants.respYourResponse:
|
case MeshCoreConstants.respYourResponse:
|
||||||
// Parse response data
|
final parsed = FrameParser.parseYourResponse(data);
|
||||||
onYourCallback?.call(data);
|
_bleService.onYourCallback?.call(parsed);
|
||||||
break;
|
break;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
6. **Add callback** in `lib/services/meshcore_ble_service.dart`:
|
||||||
|
```dart
|
||||||
|
Function(Map<String, dynamic>)? onYourCallback;
|
||||||
|
```
|
||||||
|
|
||||||
### Adding a New SAR Marker Type
|
### Adding a New SAR Marker Type
|
||||||
|
|
||||||
1. **Update enum** in `lib/models/sar_marker.dart`:
|
1. **Update enum** in `lib/models/sar_marker.dart`:
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
import '../models/device_info.dart';
|
import '../models/device_info.dart';
|
||||||
@@ -7,7 +6,6 @@ import '../models/contact.dart';
|
|||||||
import '../models/message.dart';
|
import '../models/message.dart';
|
||||||
import '../models/room_login_state.dart';
|
import '../models/room_login_state.dart';
|
||||||
import '../services/meshcore_ble_service.dart';
|
import '../services/meshcore_ble_service.dart';
|
||||||
import '../services/cayenne_lpp_parser.dart';
|
|
||||||
import '../utils/sar_message_parser.dart';
|
import '../utils/sar_message_parser.dart';
|
||||||
import 'helpers/room_login_manager.dart';
|
import 'helpers/room_login_manager.dart';
|
||||||
import 'helpers/message_delivery_tracker.dart';
|
import 'helpers/message_delivery_tracker.dart';
|
||||||
@@ -193,14 +191,14 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
_bleService.onMessageSent = (expectedAckTag, suggestedTimeoutMs, isFloodMode) {
|
_bleService.onMessageSent = (expectedAckTag, suggestedTimeoutMs, isFloodMode) {
|
||||||
print('📥 [Provider] Message sent - ACK tag: $expectedAckTag, timeout: ${suggestedTimeoutMs}ms');
|
print('📥 [Provider] Message sent - ACK tag: $expectedAckTag, timeout: ${suggestedTimeoutMs}ms');
|
||||||
|
|
||||||
// Pop the first pending message ID from the queue (FIFO)
|
// Pop the first pending message ID from the queue (FIFO) via helper
|
||||||
// This assumes messages are sent sequentially and SENT responses arrive in order
|
final messageId = _messageDeliveryTracker.popPendingMessageId();
|
||||||
if (_pendingSentMessageIds.isNotEmpty) {
|
|
||||||
final messageId = _pendingSentMessageIds.removeAt(0);
|
if (messageId != null) {
|
||||||
print(' Matched with message ID: $messageId');
|
print(' Matched with message ID: $messageId');
|
||||||
|
|
||||||
// Store the ACK tag to message ID mapping for delivery confirmation
|
// Store the ACK tag to message ID mapping for delivery confirmation
|
||||||
_ackTagToMessageId[expectedAckTag] = messageId;
|
_messageDeliveryTracker.mapAckTagToMessageId(expectedAckTag, messageId);
|
||||||
|
|
||||||
// Notify callback with message ID
|
// Notify callback with message ID
|
||||||
onMessageSent?.call(messageId, expectedAckTag, suggestedTimeoutMs);
|
onMessageSent?.call(messageId, expectedAckTag, suggestedTimeoutMs);
|
||||||
@@ -398,7 +396,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
_deviceInfo = DeviceInfo(
|
_deviceInfo = DeviceInfo(
|
||||||
connectionState: ConnectionState.disconnected,
|
connectionState: ConnectionState.disconnected,
|
||||||
);
|
);
|
||||||
clearRoomLoginStates(); // Clear login states on disconnect
|
_roomLoginManager.clearRoomLoginStates(); // Clear login states on disconnect
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,11 +460,11 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
text: text,
|
text: text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If message ID provided, add it to the pending queue
|
// If message ID provided, add it to the pending queue via helper
|
||||||
// When the SENT response arrives, it will be matched with this message ID
|
// When the SENT response arrives, it will be matched with this message ID
|
||||||
// Note: Messages must be sent sequentially for this to work correctly
|
// Note: Messages must be sent sequentially for this to work correctly
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
_pendingSentMessageIds.add(messageId);
|
_messageDeliveryTracker.trackPendingMessage(messageId);
|
||||||
print(' Added message ID to pending queue: $messageId');
|
print(' Added message ID to pending queue: $messageId');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,9 +496,9 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
text: text,
|
text: text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If message ID provided, add it to the pending queue
|
// If message ID provided, add it to the pending queue via helper
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
_pendingSentMessageIds.add(messageId);
|
_messageDeliveryTracker.trackPendingMessage(messageId);
|
||||||
print(' Added message ID to pending queue: $messageId');
|
print(' Added message ID to pending queue: $messageId');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -939,36 +937,14 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a password exists for a room (by public key prefix)
|
|
||||||
Future<bool> _hasPasswordForRoom(Uint8List publicKeyPrefix) async {
|
|
||||||
try {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
// Convert prefix to hex string for storage key
|
|
||||||
final prefixHex = publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':');
|
|
||||||
final roomKey = 'room_password_$prefixHex';
|
|
||||||
return prefs.getString(roomKey) != null;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Error checking password for room: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get login state for a room by public key prefix
|
/// Get login state for a room by public key prefix
|
||||||
RoomLoginState? getRoomLoginState(Uint8List publicKeyPrefix) {
|
RoomLoginState? getRoomLoginState(Uint8List publicKeyPrefix) {
|
||||||
final prefixHex = publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':');
|
return _roomLoginManager.getRoomLoginState(publicKeyPrefix);
|
||||||
return _roomLoginStates[prefixHex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if logged into a specific room
|
/// Check if logged into a specific room
|
||||||
bool isLoggedIntoRoom(Uint8List publicKeyPrefix) {
|
bool isLoggedIntoRoom(Uint8List publicKeyPrefix) {
|
||||||
final state = getRoomLoginState(publicKeyPrefix);
|
return _roomLoginManager.isLoggedIntoRoom(publicKeyPrefix);
|
||||||
return state?.isLoggedIn ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all room login states (call on disconnect)
|
|
||||||
void clearRoomLoginStates() {
|
|
||||||
_roomLoginStates.clear();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../../models/room_login_state.dart';
|
import '../../models/room_login_state.dart';
|
||||||
@@ -6,7 +5,7 @@ import '../../models/room_login_state.dart';
|
|||||||
/// Room login state management helper
|
/// Room login state management helper
|
||||||
///
|
///
|
||||||
/// Manages login state tracking for room contacts, including:
|
/// Manages login state tracking for room contacts, including:
|
||||||
/// - Room login state per contact (Map<String, RoomLoginState>)
|
/// - Room login state per contact (Map of String to RoomLoginState)
|
||||||
/// - Password checking logic
|
/// - Password checking logic
|
||||||
/// - Login success/fail state updates
|
/// - Login success/fail state updates
|
||||||
class RoomLoginManager {
|
class RoomLoginManager {
|
||||||
|
|||||||
Reference in New Issue
Block a user