refactor: Improve connection and room login state management by utilizing helper methods

This commit is contained in:
Janez T
2025-10-15 10:34:50 +02:00
parent e831612a1a
commit 86711dbb27
3 changed files with 87 additions and 52 deletions

View File

@@ -29,27 +29,58 @@ lib/
│ ├── message.dart # Messages and SAR markers
│ ├── sar_marker.dart # SAR tactical markers
│ ├── device_info.dart # BLE device connection state
│ ├── room_login_state.dart # Room login status tracking
│ └── map_layer.dart # Map tile layer definitions
├── services/ # Business logic services
│ ├── meshcore_ble_service.dart # BLE communication
│ ├── meshcore_ble_service.dart # BLE service coordinator (399 lines)
│ ├── meshcore_constants.dart # Protocol constants
│ ├── buffer_reader.dart # Binary protocol reader
│ ├── buffer_writer.dart # Binary protocol writer
│ ├── 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
│ ├── connection_provider.dart # BLE connection state
│ ├── connection_provider.dart # BLE connection state (957 lines)
│ ├── contacts_provider.dart # Contact list management
│ ├── messages_provider.dart # Message history + SAR markers
│ ├── 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
│ ├── home_screen.dart # Main screen with tabs
│ ├── messages_tab.dart # Message list view
│ ├── contacts_tab.dart # Contact list view
── map_tab.dart # Interactive map view
│ ├── messages_tab.dart # Message list view (767 lines)
│ ├── contacts_tab.dart # Contact list view (208 lines)
── 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
── 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
│ └── sar_message_parser.dart # Parse S:<emoji>:lat,lon format
└── main.dart # App entry point
@@ -1011,28 +1042,57 @@ flutter build apk --split-per-abi
### 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`:
```dart
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
Future<void> yourCommand() async {
/// Build YOUR_COMMAND frame
static Uint8List buildYourCommand({required String param}) {
final writer = BufferWriter();
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
case MeshCoreConstants.respYourResponse:
// Parse response data
onYourCallback?.call(data);
final parsed = FrameParser.parseYourResponse(data);
_bleService.onYourCallback?.call(parsed);
break;
```
6. **Add callback** in `lib/services/meshcore_ble_service.dart`:
```dart
Function(Map<String, dynamic>)? onYourCallback;
```
### Adding a New SAR Marker Type
1. **Update enum** in `lib/models/sar_marker.dart`:

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import '../models/device_info.dart';
@@ -7,7 +6,6 @@ import '../models/contact.dart';
import '../models/message.dart';
import '../models/room_login_state.dart';
import '../services/meshcore_ble_service.dart';
import '../services/cayenne_lpp_parser.dart';
import '../utils/sar_message_parser.dart';
import 'helpers/room_login_manager.dart';
import 'helpers/message_delivery_tracker.dart';
@@ -193,14 +191,14 @@ class ConnectionProvider with ChangeNotifier {
_bleService.onMessageSent = (expectedAckTag, suggestedTimeoutMs, isFloodMode) {
print('📥 [Provider] Message sent - ACK tag: $expectedAckTag, timeout: ${suggestedTimeoutMs}ms');
// Pop the first pending message ID from the queue (FIFO)
// This assumes messages are sent sequentially and SENT responses arrive in order
if (_pendingSentMessageIds.isNotEmpty) {
final messageId = _pendingSentMessageIds.removeAt(0);
// Pop the first pending message ID from the queue (FIFO) via helper
final messageId = _messageDeliveryTracker.popPendingMessageId();
if (messageId != null) {
print(' Matched with message ID: $messageId');
// Store the ACK tag to message ID mapping for delivery confirmation
_ackTagToMessageId[expectedAckTag] = messageId;
_messageDeliveryTracker.mapAckTagToMessageId(expectedAckTag, messageId);
// Notify callback with message ID
onMessageSent?.call(messageId, expectedAckTag, suggestedTimeoutMs);
@@ -398,7 +396,7 @@ class ConnectionProvider with ChangeNotifier {
_deviceInfo = DeviceInfo(
connectionState: ConnectionState.disconnected,
);
clearRoomLoginStates(); // Clear login states on disconnect
_roomLoginManager.clearRoomLoginStates(); // Clear login states on disconnect
notifyListeners();
}
@@ -462,11 +460,11 @@ class ConnectionProvider with ChangeNotifier {
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
// Note: Messages must be sent sequentially for this to work correctly
if (messageId != null) {
_pendingSentMessageIds.add(messageId);
_messageDeliveryTracker.trackPendingMessage(messageId);
print(' Added message ID to pending queue: $messageId');
}
@@ -498,9 +496,9 @@ class ConnectionProvider with ChangeNotifier {
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) {
_pendingSentMessageIds.add(messageId);
_messageDeliveryTracker.trackPendingMessage(messageId);
print(' Added message ID to pending queue: $messageId');
}
} catch (e) {
@@ -939,36 +937,14 @@ class ConnectionProvider with ChangeNotifier {
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
RoomLoginState? getRoomLoginState(Uint8List publicKeyPrefix) {
final prefixHex = publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':');
return _roomLoginStates[prefixHex];
return _roomLoginManager.getRoomLoginState(publicKeyPrefix);
}
/// Check if logged into a specific room
bool isLoggedIntoRoom(Uint8List publicKeyPrefix) {
final state = getRoomLoginState(publicKeyPrefix);
return state?.isLoggedIn ?? false;
}
/// Clear all room login states (call on disconnect)
void clearRoomLoginStates() {
_roomLoginStates.clear();
notifyListeners();
return _roomLoginManager.isLoggedIntoRoom(publicKeyPrefix);
}
@override

View File

@@ -1,4 +1,3 @@
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../models/room_login_state.dart';
@@ -6,7 +5,7 @@ import '../../models/room_login_state.dart';
/// Room login state management helper
///
/// 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
/// - Login success/fail state updates
class RoomLoginManager {