diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 01e4a58..b492a0b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -27,9 +27,9 @@
-
+
diff --git a/lib/bluetooth/bluetooth_manager.dart b/lib/bluetooth/bluetooth_manager.dart
index d0c3ec8..1bf27d5 100644
--- a/lib/bluetooth/bluetooth_manager.dart
+++ b/lib/bluetooth/bluetooth_manager.dart
@@ -80,7 +80,7 @@ class BluetoothManager {
}
/// Starts a periodic timer that requests data automatically.
- void startAutoReading({Duration interval = const Duration(seconds: 5)}) {
+ void startAutoReading({Duration interval = const Duration(seconds: 2)}) {
// Cancel any existing timer to prevent duplicates.
stopAutoReading();
diff --git a/lib/screens/marine/manual/widgets/in_situ_step_3_data_capture.dart b/lib/screens/marine/manual/widgets/in_situ_step_3_data_capture.dart
index 509e495..db08f3e 100644
--- a/lib/screens/marine/manual/widgets/in_situ_step_3_data_capture.dart
+++ b/lib/screens/marine/manual/widgets/in_situ_step_3_data_capture.dart
@@ -34,6 +34,12 @@ class _InSituStep3DataCaptureState extends State with Wi
bool _isAutoReading = false;
StreamSubscription? _dataSubscription;
+ // --- START MODIFICATION: Countdown Timer State ---
+ Timer? _lockoutTimer;
+ int _lockoutSecondsRemaining = 30;
+ bool _isLockedOut = false;
+ // --- END MODIFICATION ---
+
// --- START FIX: Declare service variable ---
late final MarineInSituSamplingService _samplingService;
// --- END FIX ---
@@ -85,6 +91,7 @@ class _InSituStep3DataCaptureState extends State with Wi
@override
void dispose() {
_dataSubscription?.cancel();
+ _lockoutTimer?.cancel(); // --- MODIFICATION: Cancel timer on dispose ---
// --- START FIX: Use the pre-fetched service instance ---
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
@@ -251,12 +258,40 @@ class _InSituStep3DataCaptureState extends State with Wi
);
}
+ // --- START MODIFICATION: Countdown Timer Logic ---
+ void _startLockoutTimer() {
+ _lockoutTimer?.cancel();
+ setState(() {
+ _isLockedOut = true;
+ _lockoutSecondsRemaining = 30;
+ });
+
+ _lockoutTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
+ if (_lockoutSecondsRemaining > 0) {
+ if (mounted) {
+ setState(() {
+ _lockoutSecondsRemaining--;
+ });
+ }
+ } else {
+ timer.cancel();
+ if (mounted) {
+ setState(() {
+ _isLockedOut = false;
+ });
+ }
+ }
+ });
+ }
+ // --- END MODIFICATION ---
+
void _toggleAutoReading(String activeType) {
final service = context.read();
setState(() {
_isAutoReading = !_isAutoReading;
if (_isAutoReading) {
if (activeType == 'bluetooth') service.startBluetoothAutoReading(); else service.startSerialAutoReading();
+ _startLockoutTimer(); // --- MODIFICATION: Start countdown
} else {
if (activeType == 'bluetooth') service.stopBluetoothAutoReading(); else service.stopSerialAutoReading();
}
@@ -272,8 +307,12 @@ class _InSituStep3DataCaptureState extends State with Wi
}
_dataSubscription?.cancel();
_dataSubscription = null;
+ _lockoutTimer?.cancel(); // --- MODIFICATION: Cancel timer on disconnect ---
if (mounted) {
- setState(() => _isAutoReading = false);
+ setState(() {
+ _isAutoReading = false;
+ _isLockedOut = false; // --- MODIFICATION: Reset lockout state ---
+ });
}
}
@@ -304,6 +343,13 @@ class _InSituStep3DataCaptureState extends State with Wi
}
void _validateAndProceed() {
+ // --- START MODIFICATION: Add lockout check ---
+ if (_isLockedOut) {
+ _showSnackBar("Please wait for the initial reading period to complete.", isError: true);
+ return;
+ }
+ // --- END MODIFICATION ---
+
if (_isAutoReading) {
_showStopReadingDialog();
return;
@@ -480,87 +526,100 @@ class _InSituStep3DataCaptureState extends State with Wi
final activeConnection = _getActiveConnectionDetails();
final String? activeType = activeConnection?['type'] as String?;
- return Form(
- key: _formKey,
- child: ListView(
- padding: const EdgeInsets.all(24.0),
- children: [
- Text("Data Capture", style: Theme.of(context).textTheme.headlineSmall),
- const SizedBox(height: 16),
- Row(
- children: [
- Expanded(
- child: activeType == 'bluetooth'
- ? FilledButton.icon(icon: const Icon(Icons.bluetooth_connected), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth'))
- : OutlinedButton.icon(icon: const Icon(Icons.bluetooth), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth')),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: activeType == 'serial'
- ? FilledButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial'))
- : OutlinedButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial')),
- ),
- ],
- ),
- const SizedBox(height: 16),
- if (activeConnection != null)
- _buildConnectionCard(type: activeConnection['type'], connectionState: activeConnection['state'], deviceName: activeConnection['name']),
- const SizedBox(height: 24),
- // START FIX: Restored ValueListenableBuilder to listen for Sonde ID updates.
- ValueListenableBuilder(
- valueListenable: service.sondeId,
- builder: (context, sondeId, child) {
- final newSondeId = sondeId ?? '';
- // Use a post-frame callback to safely update the controller after the build.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted && _sondeIdController.text != newSondeId) {
- _sondeIdController.text = newSondeId;
- widget.data.sondeId = newSondeId;
- }
- });
- return TextFormField(
- controller: _sondeIdController,
- decoration: const InputDecoration(labelText: 'Sonde ID *', hintText: 'Connect device or enter manually'),
- validator: (v) => v == null || v.isEmpty ? 'Sonde ID is required' : null,
- onChanged: (value) => widget.data.sondeId = value,
- onSaved: (v) => widget.data.sondeId = v,
- );
- },
- ),
- // END FIX
- const SizedBox(height: 16),
- Row(
- children: [
- Expanded(child: TextFormField(controller: _dateController, readOnly: true, decoration: const InputDecoration(labelText: 'Date'))),
- const SizedBox(width: 16),
- Expanded(child: TextFormField(controller: _timeController, readOnly: true, decoration: const InputDecoration(labelText: 'Time'))),
- ],
- ),
+ // --- START MODIFICATION: Add WillPopScope to block back navigation ---
+ return WillPopScope(
+ onWillPop: () async {
+ if (_isLockedOut) {
+ _showSnackBar("Please wait for the initial reading period to complete.", isError: true);
+ return false; // Prevent back navigation
+ }
+ return true; // Allow back navigation
+ },
+ child: Form(
+ key: _formKey,
+ child: ListView(
+ padding: const EdgeInsets.all(24.0),
+ children: [
+ Text("Data Capture", style: Theme.of(context).textTheme.headlineSmall),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ Expanded(
+ child: activeType == 'bluetooth'
+ ? FilledButton.icon(icon: const Icon(Icons.bluetooth_connected), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth'))
+ : OutlinedButton.icon(icon: const Icon(Icons.bluetooth), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth')),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: activeType == 'serial'
+ ? FilledButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial'))
+ : OutlinedButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial')),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ if (activeConnection != null)
+ _buildConnectionCard(type: activeConnection['type'], connectionState: activeConnection['state'], deviceName: activeConnection['name']),
+ const SizedBox(height: 24),
+ // START FIX: Restored ValueListenableBuilder to listen for Sonde ID updates.
+ ValueListenableBuilder(
+ valueListenable: service.sondeId,
+ builder: (context, sondeId, child) {
+ final newSondeId = sondeId ?? '';
+ // Use a post-frame callback to safely update the controller after the build.
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (mounted && _sondeIdController.text != newSondeId) {
+ _sondeIdController.text = newSondeId;
+ widget.data.sondeId = newSondeId;
+ }
+ });
+ return TextFormField(
+ controller: _sondeIdController,
+ decoration: const InputDecoration(labelText: 'Sonde ID *', hintText: 'Connect device or enter manually'),
+ validator: (v) => v == null || v.isEmpty ? 'Sonde ID is required' : null,
+ onChanged: (value) => widget.data.sondeId = value,
+ onSaved: (v) => widget.data.sondeId = v,
+ );
+ },
+ ),
+ // END FIX
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ Expanded(child: TextFormField(controller: _dateController, readOnly: true, decoration: const InputDecoration(labelText: 'Date'))),
+ const SizedBox(width: 16),
+ Expanded(child: TextFormField(controller: _timeController, readOnly: true, decoration: const InputDecoration(labelText: 'Time'))),
+ ],
+ ),
- if (_previousReadingsForComparison != null)
- _buildComparisonView(),
+ if (_previousReadingsForComparison != null)
+ _buildComparisonView(),
- const Divider(height: 32),
- Column(
- children: _parameters.map((param) {
- return _buildParameterListItem(
- icon: param['icon'] as IconData,
- label: param['label'] as String,
- unit: param['unit'] as String,
- controller: param['controller'] as TextEditingController,
- isOutOfBounds: _outOfBoundsKeys.contains(param['key']),
- );
- }).toList(),
- ),
- const SizedBox(height: 32),
- ElevatedButton(
- onPressed: _validateAndProceed,
- style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
- child: const Text('Next'),
- ),
- ],
+ const Divider(height: 32),
+ Column(
+ children: _parameters.map((param) {
+ return _buildParameterListItem(
+ icon: param['icon'] as IconData,
+ label: param['label'] as String,
+ unit: param['unit'] as String,
+ controller: param['controller'] as TextEditingController,
+ isOutOfBounds: _outOfBoundsKeys.contains(param['key']),
+ );
+ }).toList(),
+ ),
+ const SizedBox(height: 32),
+ // --- START MODIFICATION: Add countdown to Next button ---
+ ElevatedButton(
+ onPressed: _isLockedOut ? null : _validateAndProceed,
+ style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
+ child: Text(_isLockedOut ? 'Next ($_lockoutSecondsRemaining\s)' : 'Next'),
+ ),
+ // --- END MODIFICATION ---
+ ],
+ ),
),
);
+ // --- END MODIFICATION ---
}
Widget _buildComparisonView() {
@@ -773,12 +832,21 @@ class _InSituStep3DataCaptureState extends State with Wi
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
+ // --- START MODIFICATION: Add countdown to Stop Reading button ---
ElevatedButton.icon(
icon: Icon(_isAutoReading ? Icons.stop_circle_outlined : Icons.play_circle_outlined),
- label: Text(_isAutoReading ? 'Stop Reading' : 'Start Reading'),
- onPressed: () => _toggleAutoReading(type),
- style: ElevatedButton.styleFrom(backgroundColor: _isAutoReading ? Colors.orange : Colors.green, foregroundColor: Colors.white),
+ label: Text(_isAutoReading
+ ? (_isLockedOut ? 'Stop Reading ($_lockoutSecondsRemaining\s)' : 'Stop Reading')
+ : 'Start Reading'),
+ onPressed: (_isAutoReading && _isLockedOut) ? null : () => _toggleAutoReading(type),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: _isAutoReading
+ ? (_isLockedOut ? Colors.grey.shade600 : Colors.orange)
+ : Colors.green,
+ foregroundColor: Colors.white,
+ ),
),
+ // --- END MODIFICATION ---
TextButton.icon(
icon: const Icon(Icons.link_off),
label: const Text('Disconnect'),
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index 14189e6..17bb9ab 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -744,7 +744,7 @@ class _SettingsScreenState extends State {
ListTile(
leading: const Icon(Icons.info_outline),
title: const Text('App Version'),
- subtitle: const Text('MMS V4 1.2.07'),
+ subtitle: const Text('MMS V4 1.2.08'),
dense: true,
),
ListTile(
diff --git a/lib/serial/serial_manager.dart b/lib/serial/serial_manager.dart
index 568eaba..1d33499 100644
--- a/lib/serial/serial_manager.dart
+++ b/lib/serial/serial_manager.dart
@@ -164,7 +164,7 @@ class SerialManager {
}
/// Starts a periodic timer to automatically request data from the device.
- void startAutoReading({Duration interval = const Duration(seconds: 5)}) {
+ void startAutoReading({Duration interval = const Duration(seconds: 2)}) {
stopAutoReading(); // Stop any existing auto-reading timer first
if (connectionState.value == SerialConnectionState.connected) {
//startLiveReading(); // Initiate the first reading immediately
diff --git a/lib/services/marine_in_situ_sampling_service.dart b/lib/services/marine_in_situ_sampling_service.dart
index 5e8e5df..f0431f2 100644
--- a/lib/services/marine_in_situ_sampling_service.dart
+++ b/lib/services/marine_in_situ_sampling_service.dart
@@ -128,7 +128,7 @@ class MarineInSituSamplingService {
Future> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
Future connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
void disconnectFromBluetooth() => _bluetoothManager.disconnect();
- void startBluetoothAutoReading({Duration? interval}) => _bluetoothManager.startAutoReading(interval: interval ?? const Duration(seconds: 5));
+ void startBluetoothAutoReading({Duration? interval}) => _bluetoothManager.startAutoReading(interval: interval ?? const Duration(seconds: 2));
void stopBluetoothAutoReading() => _bluetoothManager.stopAutoReading();
// --- USB Serial Methods ---
@@ -153,7 +153,7 @@ class MarineInSituSamplingService {
}
void disconnectFromSerial() => _serialManager.disconnect();
- void startSerialAutoReading({Duration? interval}) => _serialManager.startAutoReading(interval: interval ?? const Duration(seconds: 5));
+ void startSerialAutoReading({Duration? interval}) => _serialManager.startAutoReading(interval: interval ?? const Duration(seconds: 2));
void stopSerialAutoReading() => _serialManager.stopAutoReading();
void dispose() {
diff --git a/lib/services/river_in_situ_sampling_service.dart b/lib/services/river_in_situ_sampling_service.dart
index 81bd692..7f346a9 100644
--- a/lib/services/river_in_situ_sampling_service.dart
+++ b/lib/services/river_in_situ_sampling_service.dart
@@ -129,7 +129,7 @@ class RiverInSituSamplingService {
Future> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
Future connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
void disconnectFromBluetooth() => _bluetoothManager.disconnect();
- void startBluetoothAutoReading({Duration? interval}) => _bluetoothManager.startAutoReading(interval: interval ?? const Duration(seconds: 5));
+ void startBluetoothAutoReading({Duration? interval}) => _bluetoothManager.startAutoReading(interval: interval ?? const Duration(seconds: 2));
void stopBluetoothAutoReading() => _bluetoothManager.stopAutoReading();
Future> getAvailableSerialDevices() => _serialManager.getAvailableDevices();
@@ -152,7 +152,7 @@ class RiverInSituSamplingService {
}
void disconnectFromSerial() => _serialManager.disconnect();
- void startSerialAutoReading({Duration? interval}) => _serialManager.startAutoReading(interval: interval ?? const Duration(seconds: 5));
+ void startSerialAutoReading({Duration? interval}) => _serialManager.startAutoReading(interval: interval ?? const Duration(seconds: 2));
void stopSerialAutoReading() => _serialManager.stopAutoReading();
void dispose() {
_bluetoothManager.dispose();