add 30 sec timeout for sonde reading stabilization
This commit is contained in:
parent
fc14740b01
commit
8931ed9297
@ -27,9 +27,9 @@
|
|||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<!-- END: STORAGE PERMISSIONS -->
|
<!-- END: STORAGE PERMISSIONS -->
|
||||||
|
|
||||||
<!-- MMS V4 1.2.06 -->
|
<!-- MMS V4 1.2.08 -->
|
||||||
<application
|
<application
|
||||||
android:label="MMS V4 1.2.07"
|
android:label="MMS V4 1.2.08"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
|
|||||||
@ -80,7 +80,7 @@ class BluetoothManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a periodic timer that requests data automatically.
|
/// 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.
|
// Cancel any existing timer to prevent duplicates.
|
||||||
stopAutoReading();
|
stopAutoReading();
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,12 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
bool _isAutoReading = false;
|
bool _isAutoReading = false;
|
||||||
StreamSubscription? _dataSubscription;
|
StreamSubscription? _dataSubscription;
|
||||||
|
|
||||||
|
// --- START MODIFICATION: Countdown Timer State ---
|
||||||
|
Timer? _lockoutTimer;
|
||||||
|
int _lockoutSecondsRemaining = 30;
|
||||||
|
bool _isLockedOut = false;
|
||||||
|
// --- END MODIFICATION ---
|
||||||
|
|
||||||
// --- START FIX: Declare service variable ---
|
// --- START FIX: Declare service variable ---
|
||||||
late final MarineInSituSamplingService _samplingService;
|
late final MarineInSituSamplingService _samplingService;
|
||||||
// --- END FIX ---
|
// --- END FIX ---
|
||||||
@ -85,6 +91,7 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
|
_lockoutTimer?.cancel(); // --- MODIFICATION: Cancel timer on dispose ---
|
||||||
|
|
||||||
// --- START FIX: Use the pre-fetched service instance ---
|
// --- START FIX: Use the pre-fetched service instance ---
|
||||||
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
||||||
@ -251,12 +258,40 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> 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) {
|
void _toggleAutoReading(String activeType) {
|
||||||
final service = context.read<MarineInSituSamplingService>();
|
final service = context.read<MarineInSituSamplingService>();
|
||||||
setState(() {
|
setState(() {
|
||||||
_isAutoReading = !_isAutoReading;
|
_isAutoReading = !_isAutoReading;
|
||||||
if (_isAutoReading) {
|
if (_isAutoReading) {
|
||||||
if (activeType == 'bluetooth') service.startBluetoothAutoReading(); else service.startSerialAutoReading();
|
if (activeType == 'bluetooth') service.startBluetoothAutoReading(); else service.startSerialAutoReading();
|
||||||
|
_startLockoutTimer(); // --- MODIFICATION: Start countdown
|
||||||
} else {
|
} else {
|
||||||
if (activeType == 'bluetooth') service.stopBluetoothAutoReading(); else service.stopSerialAutoReading();
|
if (activeType == 'bluetooth') service.stopBluetoothAutoReading(); else service.stopSerialAutoReading();
|
||||||
}
|
}
|
||||||
@ -272,8 +307,12 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
}
|
}
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
_dataSubscription = null;
|
_dataSubscription = null;
|
||||||
|
_lockoutTimer?.cancel(); // --- MODIFICATION: Cancel timer on disconnect ---
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isAutoReading = false);
|
setState(() {
|
||||||
|
_isAutoReading = false;
|
||||||
|
_isLockedOut = false; // --- MODIFICATION: Reset lockout state ---
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +343,13 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _validateAndProceed() {
|
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) {
|
if (_isAutoReading) {
|
||||||
_showStopReadingDialog();
|
_showStopReadingDialog();
|
||||||
return;
|
return;
|
||||||
@ -480,87 +526,100 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
final activeConnection = _getActiveConnectionDetails();
|
final activeConnection = _getActiveConnectionDetails();
|
||||||
final String? activeType = activeConnection?['type'] as String?;
|
final String? activeType = activeConnection?['type'] as String?;
|
||||||
|
|
||||||
return Form(
|
// --- START MODIFICATION: Add WillPopScope to block back navigation ---
|
||||||
key: _formKey,
|
return WillPopScope(
|
||||||
child: ListView(
|
onWillPop: () async {
|
||||||
padding: const EdgeInsets.all(24.0),
|
if (_isLockedOut) {
|
||||||
children: [
|
_showSnackBar("Please wait for the initial reading period to complete.", isError: true);
|
||||||
Text("Data Capture", style: Theme.of(context).textTheme.headlineSmall),
|
return false; // Prevent back navigation
|
||||||
const SizedBox(height: 16),
|
}
|
||||||
Row(
|
return true; // Allow back navigation
|
||||||
children: [
|
},
|
||||||
Expanded(
|
child: Form(
|
||||||
child: activeType == 'bluetooth'
|
key: _formKey,
|
||||||
? FilledButton.icon(icon: const Icon(Icons.bluetooth_connected), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth'))
|
child: ListView(
|
||||||
: OutlinedButton.icon(icon: const Icon(Icons.bluetooth), label: const Text("Bluetooth"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('bluetooth')),
|
padding: const EdgeInsets.all(24.0),
|
||||||
),
|
children: [
|
||||||
const SizedBox(width: 16),
|
Text("Data Capture", style: Theme.of(context).textTheme.headlineSmall),
|
||||||
Expanded(
|
const SizedBox(height: 16),
|
||||||
child: activeType == 'serial'
|
Row(
|
||||||
? FilledButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial'))
|
children: [
|
||||||
: OutlinedButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial')),
|
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(height: 16),
|
),
|
||||||
if (activeConnection != null)
|
const SizedBox(width: 16),
|
||||||
_buildConnectionCard(type: activeConnection['type'], connectionState: activeConnection['state'], deviceName: activeConnection['name']),
|
Expanded(
|
||||||
const SizedBox(height: 24),
|
child: activeType == 'serial'
|
||||||
// START FIX: Restored ValueListenableBuilder to listen for Sonde ID updates.
|
? FilledButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial'))
|
||||||
ValueListenableBuilder<String?>(
|
: OutlinedButton.icon(icon: const Icon(Icons.usb), label: const Text("USB Serial"), onPressed: _isLoading ? null : () => _handleConnectionAttempt('serial')),
|
||||||
valueListenable: service.sondeId,
|
),
|
||||||
builder: (context, sondeId, child) {
|
],
|
||||||
final newSondeId = sondeId ?? '';
|
),
|
||||||
// Use a post-frame callback to safely update the controller after the build.
|
const SizedBox(height: 16),
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
if (activeConnection != null)
|
||||||
if (mounted && _sondeIdController.text != newSondeId) {
|
_buildConnectionCard(type: activeConnection['type'], connectionState: activeConnection['state'], deviceName: activeConnection['name']),
|
||||||
_sondeIdController.text = newSondeId;
|
const SizedBox(height: 24),
|
||||||
widget.data.sondeId = newSondeId;
|
// START FIX: Restored ValueListenableBuilder to listen for Sonde ID updates.
|
||||||
}
|
ValueListenableBuilder<String?>(
|
||||||
});
|
valueListenable: service.sondeId,
|
||||||
return TextFormField(
|
builder: (context, sondeId, child) {
|
||||||
controller: _sondeIdController,
|
final newSondeId = sondeId ?? '';
|
||||||
decoration: const InputDecoration(labelText: 'Sonde ID *', hintText: 'Connect device or enter manually'),
|
// Use a post-frame callback to safely update the controller after the build.
|
||||||
validator: (v) => v == null || v.isEmpty ? 'Sonde ID is required' : null,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
onChanged: (value) => widget.data.sondeId = value,
|
if (mounted && _sondeIdController.text != newSondeId) {
|
||||||
onSaved: (v) => widget.data.sondeId = v,
|
_sondeIdController.text = newSondeId;
|
||||||
);
|
widget.data.sondeId = newSondeId;
|
||||||
},
|
}
|
||||||
),
|
});
|
||||||
// END FIX
|
return TextFormField(
|
||||||
const SizedBox(height: 16),
|
controller: _sondeIdController,
|
||||||
Row(
|
decoration: const InputDecoration(labelText: 'Sonde ID *', hintText: 'Connect device or enter manually'),
|
||||||
children: [
|
validator: (v) => v == null || v.isEmpty ? 'Sonde ID is required' : null,
|
||||||
Expanded(child: TextFormField(controller: _dateController, readOnly: true, decoration: const InputDecoration(labelText: 'Date'))),
|
onChanged: (value) => widget.data.sondeId = value,
|
||||||
const SizedBox(width: 16),
|
onSaved: (v) => widget.data.sondeId = v,
|
||||||
Expanded(child: TextFormField(controller: _timeController, readOnly: true, decoration: const InputDecoration(labelText: 'Time'))),
|
);
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
|
// 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)
|
if (_previousReadingsForComparison != null)
|
||||||
_buildComparisonView(),
|
_buildComparisonView(),
|
||||||
|
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
Column(
|
Column(
|
||||||
children: _parameters.map((param) {
|
children: _parameters.map((param) {
|
||||||
return _buildParameterListItem(
|
return _buildParameterListItem(
|
||||||
icon: param['icon'] as IconData,
|
icon: param['icon'] as IconData,
|
||||||
label: param['label'] as String,
|
label: param['label'] as String,
|
||||||
unit: param['unit'] as String,
|
unit: param['unit'] as String,
|
||||||
controller: param['controller'] as TextEditingController,
|
controller: param['controller'] as TextEditingController,
|
||||||
isOutOfBounds: _outOfBoundsKeys.contains(param['key']),
|
isOutOfBounds: _outOfBoundsKeys.contains(param['key']),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
ElevatedButton(
|
// --- START MODIFICATION: Add countdown to Next button ---
|
||||||
onPressed: _validateAndProceed,
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
|
onPressed: _isLockedOut ? null : _validateAndProceed,
|
||||||
child: const Text('Next'),
|
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
|
||||||
),
|
child: Text(_isLockedOut ? 'Next ($_lockoutSecondsRemaining\s)' : 'Next'),
|
||||||
],
|
),
|
||||||
|
// --- END MODIFICATION ---
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// --- END MODIFICATION ---
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildComparisonView() {
|
Widget _buildComparisonView() {
|
||||||
@ -773,12 +832,21 @@ class _InSituStep3DataCaptureState extends State<InSituStep3DataCapture> with Wi
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
|
// --- START MODIFICATION: Add countdown to Stop Reading button ---
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: Icon(_isAutoReading ? Icons.stop_circle_outlined : Icons.play_circle_outlined),
|
icon: Icon(_isAutoReading ? Icons.stop_circle_outlined : Icons.play_circle_outlined),
|
||||||
label: Text(_isAutoReading ? 'Stop Reading' : 'Start Reading'),
|
label: Text(_isAutoReading
|
||||||
onPressed: () => _toggleAutoReading(type),
|
? (_isLockedOut ? 'Stop Reading ($_lockoutSecondsRemaining\s)' : 'Stop Reading')
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: _isAutoReading ? Colors.orange : Colors.green, foregroundColor: Colors.white),
|
: '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(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.link_off),
|
icon: const Icon(Icons.link_off),
|
||||||
label: const Text('Disconnect'),
|
label: const Text('Disconnect'),
|
||||||
|
|||||||
@ -744,7 +744,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info_outline),
|
leading: const Icon(Icons.info_outline),
|
||||||
title: const Text('App Version'),
|
title: const Text('App Version'),
|
||||||
subtitle: const Text('MMS V4 1.2.07'),
|
subtitle: const Text('MMS V4 1.2.08'),
|
||||||
dense: true,
|
dense: true,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
@ -164,7 +164,7 @@ class SerialManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a periodic timer to automatically request data from the device.
|
/// 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
|
stopAutoReading(); // Stop any existing auto-reading timer first
|
||||||
if (connectionState.value == SerialConnectionState.connected) {
|
if (connectionState.value == SerialConnectionState.connected) {
|
||||||
//startLiveReading(); // Initiate the first reading immediately
|
//startLiveReading(); // Initiate the first reading immediately
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class MarineInSituSamplingService {
|
|||||||
Future<List<BluetoothDevice>> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
|
Future<List<BluetoothDevice>> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
|
||||||
Future<void> connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
|
Future<void> connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
|
||||||
void disconnectFromBluetooth() => _bluetoothManager.disconnect();
|
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();
|
void stopBluetoothAutoReading() => _bluetoothManager.stopAutoReading();
|
||||||
|
|
||||||
// --- USB Serial Methods ---
|
// --- USB Serial Methods ---
|
||||||
@ -153,7 +153,7 @@ class MarineInSituSamplingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disconnectFromSerial() => _serialManager.disconnect();
|
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 stopSerialAutoReading() => _serialManager.stopAutoReading();
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|||||||
@ -129,7 +129,7 @@ class RiverInSituSamplingService {
|
|||||||
Future<List<BluetoothDevice>> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
|
Future<List<BluetoothDevice>> getPairedBluetoothDevices() => _bluetoothManager.getPairedDevices();
|
||||||
Future<void> connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
|
Future<void> connectToBluetoothDevice(BluetoothDevice device) => _bluetoothManager.connect(device);
|
||||||
void disconnectFromBluetooth() => _bluetoothManager.disconnect();
|
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();
|
void stopBluetoothAutoReading() => _bluetoothManager.stopAutoReading();
|
||||||
Future<List<UsbDevice>> getAvailableSerialDevices() => _serialManager.getAvailableDevices();
|
Future<List<UsbDevice>> getAvailableSerialDevices() => _serialManager.getAvailableDevices();
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ class RiverInSituSamplingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disconnectFromSerial() => _serialManager.disconnect();
|
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 stopSerialAutoReading() => _serialManager.stopAutoReading();
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_bluetoothManager.dispose();
|
_bluetoothManager.dispose();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user