fix issue on marine investigative and npe report
This commit is contained in:
parent
d0f9d72ebd
commit
6c4bc335b8
@ -67,7 +67,7 @@ class _MarineInvesManualStep2SiteInfoState extends State<MarineInvesManualStep2S
|
|||||||
setState(() => setImageCallback(file));
|
setState(() => setImageCallback(file));
|
||||||
} else if (mounted) {
|
} else if (mounted) {
|
||||||
// Corrected snackbar message
|
// Corrected snackbar message
|
||||||
_showSnackBar('Image selection failed. Please ensure all photos are taken in landscape (vertical) mode.', isError: true);
|
_showSnackBar('Image selection failed. Please ensure all photos are taken in landscape (horizontal) mode.', isError: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -150,7 +150,7 @@ class _MarineInvesManualStep2SiteInfoState extends State<MarineInvesManualStep2S
|
|||||||
Text("Required Photos *", style: Theme.of(context).textTheme.titleLarge),
|
Text("Required Photos *", style: Theme.of(context).textTheme.titleLarge),
|
||||||
// MODIFIED: Matched in-situ text
|
// MODIFIED: Matched in-situ text
|
||||||
const Text(
|
const Text(
|
||||||
"All photos must be in landscape (vertical) orientation. A watermark will be applied automatically.",
|
"All photos must be in landscape (horizontal) orientation. A watermark will be applied automatically.",
|
||||||
style: TextStyle(color: Colors.grey)
|
style: TextStyle(color: Colors.grey)
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|||||||
@ -28,12 +28,12 @@ class NPEReportFromInSitu extends StatefulWidget {
|
|||||||
State<NPEReportFromInSitu> createState() => _NPEReportFromInSituState();
|
State<NPEReportFromInSitu> createState() => _NPEReportFromInSituState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
// Modified: Added WidgetsBindingObserver to handle app lifecycle changes (USB permission dialog)
|
||||||
|
class _NPEReportFromInSituState extends State<NPEReportFromInSitu> with WidgetsBindingObserver {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _isPickingImage = false;
|
bool _isPickingImage = false;
|
||||||
|
|
||||||
// --- START: MODIFIED STATE VARIABLES ---
|
|
||||||
// Data handling
|
// Data handling
|
||||||
bool? _useRecentSample; // To track Yes/No selection
|
bool? _useRecentSample; // To track Yes/No selection
|
||||||
bool _isLoadingRecentSamples = false; // Now triggered on-demand
|
bool _isLoadingRecentSamples = false; // Now triggered on-demand
|
||||||
@ -48,7 +48,6 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
String? _selectedState;
|
String? _selectedState;
|
||||||
String? _selectedCategory;
|
String? _selectedCategory;
|
||||||
Map<String, dynamic>? _selectedManualStation;
|
Map<String, dynamic>? _selectedManualStation;
|
||||||
// --- END: MODIFIED STATE VARIABLES ---
|
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
final _stationIdController = TextEditingController();
|
final _stationIdController = TextEditingController();
|
||||||
@ -81,6 +80,8 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Added: Register observer
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
||||||
_loadAllStatesFromProvider(); // Load manual stations for "No" path
|
_loadAllStatesFromProvider(); // Load manual stations for "No" path
|
||||||
_setDefaultDateTime(); // Set default time for all paths
|
_setDefaultDateTime(); // Set default time for all paths
|
||||||
@ -88,6 +89,8 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// Added: Remove observer
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
_lockoutTimer?.cancel();
|
_lockoutTimer?.cancel();
|
||||||
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
||||||
@ -117,7 +120,23 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: ADDED HELPER METHODS ---
|
// Added: Handle App Lifecycle changes (specifically for USB permission dialog return)
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
if (mounted) {
|
||||||
|
final btConnecting = _samplingService.bluetoothConnectionState.value == BluetoothConnectionState.connecting;
|
||||||
|
final serialConnecting = _samplingService.serialConnectionState.value == SerialConnectionState.connecting;
|
||||||
|
|
||||||
|
// If the UI is still loading or the service thinks it's still connecting (stuck after permission dialog),
|
||||||
|
// force a disconnect/reset so the user can try again.
|
||||||
|
if (_isLoading || btConnecting || serialConnecting) {
|
||||||
|
_disconnectFromAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _setDefaultDateTime() {
|
void _setDefaultDateTime() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
||||||
@ -167,19 +186,16 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
_latController.clear();
|
_latController.clear();
|
||||||
_longController.clear();
|
_longController.clear();
|
||||||
|
|
||||||
// --- CHANGE: Clear measurement controllers so they are empty before reading ---
|
|
||||||
_doPercentController.clear();
|
_doPercentController.clear();
|
||||||
_doMgLController.clear();
|
_doMgLController.clear();
|
||||||
_phController.clear();
|
_phController.clear();
|
||||||
_condController.clear();
|
_condController.clear();
|
||||||
_turbController.clear();
|
_turbController.clear();
|
||||||
_tempController.clear();
|
_tempController.clear();
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
_setDefaultDateTime(); // Reset to 'now'
|
_setDefaultDateTime(); // Reset to 'now'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// --- END: ADDED HELPER METHODS ---
|
|
||||||
|
|
||||||
Future<void> _fetchRecentNearbySamples() async {
|
Future<void> _fetchRecentNearbySamples() async {
|
||||||
setState(() => _isLoadingRecentSamples = true);
|
setState(() => _isLoadingRecentSamples = true);
|
||||||
@ -269,10 +285,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
_npeData.latitude = _latController.text;
|
_npeData.latitude = _latController.text;
|
||||||
_npeData.longitude = _longController.text;
|
_npeData.longitude = _longController.text;
|
||||||
|
|
||||||
// selectedStation is already set by either _populateFormFromData or the "No" path dropdown
|
_npeData.locationDescription = _locationController.text;
|
||||||
// _npeData.selectedStation = _selectedRecentSample?.selectedStation;
|
|
||||||
|
|
||||||
_npeData.locationDescription = _locationController.text; // Used by both paths
|
|
||||||
_npeData.possibleSource = _possibleSourceController.text;
|
_npeData.possibleSource = _possibleSourceController.text;
|
||||||
_npeData.othersObservationRemark = _othersObservationController.text;
|
_npeData.othersObservationRemark = _othersObservationController.text;
|
||||||
_npeData.oxygenSaturation = double.tryParse(_doPercentController.text);
|
_npeData.oxygenSaturation = double.tryParse(_doPercentController.text);
|
||||||
@ -387,7 +400,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
if (mounted) setState(() => _isPickingImage = false);
|
if (mounted) setState(() => _isPickingImage = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: IN-SITU DEVICE METHODS (Unchanged) ---
|
// --- IN-SITU DEVICE METHODS ---
|
||||||
void _updateTextFields(Map<String, double> readings) {
|
void _updateTextFields(Map<String, double> readings) {
|
||||||
const defaultValue = -999.0;
|
const defaultValue = -999.0;
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -433,7 +446,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _connectToDevice(String type) async {
|
Future<bool> _connectToDevice(String type) async {
|
||||||
setState(() => _isLoading = true); // Use main loading indicator
|
setState(() => _isLoading = true);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
try {
|
try {
|
||||||
if (type == 'bluetooth') {
|
if (type == 'bluetooth') {
|
||||||
@ -463,7 +476,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
debugPrint("Connection failed: $e");
|
debugPrint("Connection failed: $e");
|
||||||
if (mounted) _showConnectionFailedDialog();
|
if (mounted) _showConnectionFailedDialog();
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isLoading = false); // Stop main loading indicator
|
if (mounted) setState(() => _isLoading = false);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@ -547,11 +560,10 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isAutoReading = false;
|
_isAutoReading = false;
|
||||||
_isLockedOut = false;
|
_isLockedOut = false;
|
||||||
|
_isLoading = false; // Modified: Reset isLoading to unlock buttons
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- END: IN-SITU DEVICE METHODS (Unchanged) ---
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -565,7 +577,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
children: [
|
children: [
|
||||||
// --- START: SECTION 1 (NEW) ---
|
// --- SECTION 1 ---
|
||||||
_buildSectionTitle("1. Use Recent Sample?"),
|
_buildSectionTitle("1. Use Recent Sample?"),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -577,9 +589,9 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_useRecentSample = value;
|
_useRecentSample = value;
|
||||||
_clearManualStationSelection(); // Clear "No" path data
|
_clearManualStationSelection();
|
||||||
if (value == true && _recentNearbySamples.isEmpty) {
|
if (value == true && _recentNearbySamples.isEmpty) {
|
||||||
_fetchRecentNearbySamples(); // Fetch samples on-demand
|
_fetchRecentNearbySamples();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -593,7 +605,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_useRecentSample = value;
|
_useRecentSample = value;
|
||||||
_clearRecentSampleSelection(); // Clear "Yes" path data
|
_clearRecentSampleSelection();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -601,10 +613,8 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// --- END: SECTION 1 (NEW) ---
|
|
||||||
|
|
||||||
// --- START: SECTION 2 (CONDITIONAL) ---
|
// --- SECTION 2 ---
|
||||||
// "YES" PATH: Select from recent samples
|
|
||||||
if (_useRecentSample == true) ...[
|
if (_useRecentSample == true) ...[
|
||||||
_buildSectionTitle("2. Select Recent Sample"),
|
_buildSectionTitle("2. Select Recent Sample"),
|
||||||
if (_isLoadingRecentSamples)
|
if (_isLoadingRecentSamples)
|
||||||
@ -622,7 +632,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
if (sample != null) {
|
if (sample != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedRecentSample = sample;
|
_selectedRecentSample = sample;
|
||||||
_npeData.selectedStation = sample.selectedStation; // CRITICAL: Set station for submission
|
_npeData.selectedStation = sample.selectedStation;
|
||||||
_populateFormFromData(sample);
|
_populateFormFromData(sample);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -631,7 +641,6 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
// "NO" PATH: Select from manual station list
|
|
||||||
if (_useRecentSample == false) ...[
|
if (_useRecentSample == false) ...[
|
||||||
_buildSectionTitle("2. Select Manual Station"),
|
_buildSectionTitle("2. Select Manual Station"),
|
||||||
DropdownSearch<String>(
|
DropdownSearch<String>(
|
||||||
@ -687,7 +696,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
onChanged: (station) {
|
onChanged: (station) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedManualStation = station;
|
_selectedManualStation = station;
|
||||||
_npeData.selectedStation = station; // CRITICAL: Set station for submission
|
_npeData.selectedStation = station;
|
||||||
_stationIdController.text = station?['man_station_code'] ?? '';
|
_stationIdController.text = station?['man_station_code'] ?? '';
|
||||||
_locationController.text = station?['man_station_name'] ?? '';
|
_locationController.text = station?['man_station_name'] ?? '';
|
||||||
_latController.text = station?['man_latitude']?.toString() ?? '';
|
_latController.text = station?['man_latitude']?.toString() ?? '';
|
||||||
@ -697,9 +706,8 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
validator: (val) => val == null && _stationsForCategory.isNotEmpty ? "Station is required" : null,
|
validator: (val) => val == null && _stationsForCategory.isNotEmpty ? "Station is required" : null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// --- END: SECTION 2 (CONDITIONAL) ---
|
|
||||||
|
|
||||||
// --- START: SHARED SECTIONS (NOW ALWAYS VISIBLE) ---
|
// --- SHARED SECTIONS ---
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_buildSectionTitle("Station Information"),
|
_buildSectionTitle("Station Information"),
|
||||||
_buildTextFormField(controller: _stationIdController, label: "Station ID", readOnly: true),
|
_buildTextFormField(controller: _stationIdController, label: "Station ID", readOnly: true),
|
||||||
@ -734,12 +742,10 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15)
|
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15)
|
||||||
),
|
),
|
||||||
// Disable button if "Yes/No" hasn't been selected
|
|
||||||
onPressed: _isLoading || _useRecentSample == null ? null : _submitNpeReport,
|
onPressed: _isLoading || _useRecentSample == null ? null : _submitNpeReport,
|
||||||
child: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Text("Submit Report"),
|
child: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Text("Submit Report"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// --- END: SHARED SECTIONS ---
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -890,13 +896,10 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: WIDGET BUILDERS FOR IN-SITU (Unchanged) ---
|
|
||||||
Widget _buildInSituSection() {
|
Widget _buildInSituSection() {
|
||||||
final activeConnection = _getActiveConnectionDetails();
|
final activeConnection = _getActiveConnectionDetails();
|
||||||
final String? activeType = activeConnection?['type'] as String?;
|
final String? activeType = activeConnection?['type'] as String?;
|
||||||
|
|
||||||
// For the "No" path, the in-situ fields must be editable.
|
|
||||||
// For the "Yes" path, they should be read-only as they come from the sample.
|
|
||||||
final bool areFieldsReadOnly = (_useRecentSample == true);
|
final bool areFieldsReadOnly = (_useRecentSample == true);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@ -987,7 +990,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
required String label,
|
required String label,
|
||||||
required String unit,
|
required String unit,
|
||||||
required TextEditingController controller,
|
required TextEditingController controller,
|
||||||
bool readOnly = false, // ADDED: readOnly parameter
|
bool readOnly = false,
|
||||||
}) {
|
}) {
|
||||||
final bool isMissing = controller.text.isEmpty || controller.text.contains('-999');
|
final bool isMissing = controller.text.isEmpty || controller.text.contains('-999');
|
||||||
final String displayValue = isMissing ? '-.--' : (double.tryParse(controller.text)?.toStringAsFixed(5) ?? '-.--');
|
final String displayValue = isMissing ? '-.--' : (double.tryParse(controller.text)?.toStringAsFixed(5) ?? '-.--');
|
||||||
@ -1001,14 +1004,12 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
trailing: SizedBox(
|
trailing: SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
// --- START: MODIFIED to handle readOnly vs. editable ---
|
|
||||||
controller: readOnly ? null : controller,
|
controller: readOnly ? null : controller,
|
||||||
initialValue: readOnly ? displayValue : null,
|
initialValue: readOnly ? displayValue : null,
|
||||||
key: readOnly ? ValueKey(displayValue) : null,
|
key: readOnly ? ValueKey(displayValue) : null,
|
||||||
// --- END: MODIFIED ---
|
|
||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
keyboardType: readOnly ? null : const TextInputType.numberWithOptions(decimal: true), // Allow editing only if NOT readOnly
|
keyboardType: readOnly ? null : const TextInputType.numberWithOptions(decimal: true),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: isMissing ? Colors.grey : Theme.of(context).colorScheme.primary,
|
color: isMissing ? Colors.grey : Theme.of(context).colorScheme.primary,
|
||||||
@ -1016,16 +1017,13 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
isDense: true, // Helps with alignment
|
isDense: true,
|
||||||
// --- CHANGE: Added hint text to display when controller is empty (before start reading) ---
|
|
||||||
hintText: '-.--',
|
hintText: '-.--',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
// -----------------------------------------------------------------------------------------
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: WIDGET BUILDERS FOR IN-SITU ---
|
|
||||||
}
|
}
|
||||||
@ -26,7 +26,8 @@ class NPEReportFromTarball extends StatefulWidget {
|
|||||||
State<NPEReportFromTarball> createState() => _NPEReportFromTarballState();
|
State<NPEReportFromTarball> createState() => _NPEReportFromTarballState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
// Modified: Added WidgetsBindingObserver to handle app lifecycle changes (USB permission dialog)
|
||||||
|
class _NPEReportFromTarballState extends State<NPEReportFromTarball> with WidgetsBindingObserver {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _isPickingImage = false;
|
bool _isPickingImage = false;
|
||||||
@ -70,6 +71,8 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Added: Register observer
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@ -83,6 +86,8 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// Added: Remove observer
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_stationIdController.dispose();
|
_stationIdController.dispose();
|
||||||
_locationController.dispose();
|
_locationController.dispose();
|
||||||
_eventDateTimeController.dispose();
|
_eventDateTimeController.dispose();
|
||||||
@ -106,6 +111,23 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added: Handle App Lifecycle changes (specifically for USB permission dialog return)
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
if (mounted) {
|
||||||
|
final btConnecting = _samplingService.bluetoothConnectionState.value == BluetoothConnectionState.connecting;
|
||||||
|
final serialConnecting = _samplingService.serialConnectionState.value == SerialConnectionState.connecting;
|
||||||
|
|
||||||
|
// If the UI is still loading or the service thinks it's still connecting (stuck after permission dialog),
|
||||||
|
// force a disconnect/reset so the user can try again.
|
||||||
|
if (_isLoading || btConnecting || serialConnecting) {
|
||||||
|
_disconnectFromAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _setDefaultDateTime() {
|
void _setDefaultDateTime() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
||||||
@ -409,6 +431,7 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isAutoReading = false;
|
_isAutoReading = false;
|
||||||
_isLockedOut = false;
|
_isLockedOut = false;
|
||||||
|
_isLoading = false; // Added: Reset isLoading to unlock buttons
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,8 @@ class NPEReportNewLocation extends StatefulWidget {
|
|||||||
State<NPEReportNewLocation> createState() => _NPEReportNewLocationState();
|
State<NPEReportNewLocation> createState() => _NPEReportNewLocationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
// Modified: Added WidgetsBindingObserver to handle app lifecycle changes (USB permission dialog)
|
||||||
|
class _NPEReportNewLocationState extends State<NPEReportNewLocation> with WidgetsBindingObserver {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _isPickingImage = false;
|
bool _isPickingImage = false;
|
||||||
@ -67,6 +68,8 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Added: Register observer
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
_samplingService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
||||||
_setDefaultDateTime();
|
_setDefaultDateTime();
|
||||||
_loadAllStatesFromProvider();
|
_loadAllStatesFromProvider();
|
||||||
@ -74,6 +77,8 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// Added: Remove observer
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_dataSubscription?.cancel();
|
_dataSubscription?.cancel();
|
||||||
_lockoutTimer?.cancel();
|
_lockoutTimer?.cancel();
|
||||||
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
if (_samplingService.bluetoothConnectionState.value != BluetoothConnectionState.disconnected) {
|
||||||
@ -102,6 +107,23 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added: Handle App Lifecycle changes (specifically for USB permission dialog return)
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
if (mounted) {
|
||||||
|
final btConnecting = _samplingService.bluetoothConnectionState.value == BluetoothConnectionState.connecting;
|
||||||
|
final serialConnecting = _samplingService.serialConnectionState.value == SerialConnectionState.connecting;
|
||||||
|
|
||||||
|
// If the UI is still loading or the service thinks it's still connecting (stuck after permission dialog),
|
||||||
|
// force a disconnect/reset so the user can try again.
|
||||||
|
if (_isLoading || btConnecting || serialConnecting) {
|
||||||
|
_disconnectFromAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _setDefaultDateTime() {
|
void _setDefaultDateTime() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
_eventDateTimeController.text = DateFormat('yyyy-MM-dd HH:mm').format(now);
|
||||||
@ -243,10 +265,62 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await _showImageErrorDialog(
|
||||||
|
"Image processing failed. Please ensure the photo is taken in landscape mode."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) setState(() => _isPickingImage = false);
|
if (mounted) setState(() => _isPickingImage = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showImageErrorDialog(String message) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.error_outline, color: Colors.red),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text('Image Error'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(message),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text(
|
||||||
|
"Please ensure your device is held horizontally:",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Icon(
|
||||||
|
Icons.stay_current_landscape,
|
||||||
|
size: 60,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('OK'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _updateTextFields(Map<String, double> readings) {
|
void _updateTextFields(Map<String, double> readings) {
|
||||||
const defaultValue = -999.0;
|
const defaultValue = -999.0;
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -406,6 +480,7 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isAutoReading = false;
|
_isAutoReading = false;
|
||||||
_isLockedOut = false;
|
_isLockedOut = false;
|
||||||
|
_isLoading = false; // Added: Reset isLoading to unlock buttons
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
// lib/services/marine_api_service.dart
|
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -151,8 +149,9 @@ class MarineApiService {
|
|||||||
// Pass the station type to the API so it knows which foreign key to check (station_id vs tbl_station_id)
|
// Pass the station type to the API so it knows which foreign key to check (station_id vs tbl_station_id)
|
||||||
final String stationTypeParam = Uri.encodeComponent(stationType);
|
final String stationTypeParam = Uri.encodeComponent(stationType);
|
||||||
|
|
||||||
|
// *** FIX: Use the correct top-level resource name 'marine-investigative' ***
|
||||||
final String endpoint =
|
final String endpoint =
|
||||||
'marine/investigative/records-by-station?station_id=$stationId&date=$dateStr&station_type=$stationTypeParam';
|
'marine-investigative/records-by-station?station_id=$stationId&date=$dateStr&station_type=$stationTypeParam';
|
||||||
|
|
||||||
debugPrint("MarineApiService: Calling API endpoint: $endpoint");
|
debugPrint("MarineApiService: Calling API endpoint: $endpoint");
|
||||||
final response = await _baseService.get(baseUrl, endpoint);
|
final response = await _baseService.get(baseUrl, endpoint);
|
||||||
@ -184,10 +183,10 @@ class MarineApiService {
|
|||||||
'samplingDate': samplingDate,
|
'samplingDate': samplingDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use a new endpoint dedicated to the investigative module
|
// *** FIX: Use the correct top-level resource name 'marine-investigative' ***
|
||||||
return _baseService.postMultipart(
|
return _baseService.postMultipart(
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
endpoint: 'marine/investigative/images/send-email',
|
endpoint: 'marine-investigative/images/send-email',
|
||||||
fields: fields,
|
fields: fields,
|
||||||
files: {},
|
files: {},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -732,7 +732,10 @@ class MarineInvestigativeSamplingService {
|
|||||||
final allLimits = await _dbHelper.loadMarineParameterLimits() ?? [];
|
final allLimits = await _dbHelper.loadMarineParameterLimits() ?? [];
|
||||||
if (allLimits.isEmpty) return "";
|
if (allLimits.isEmpty) return "";
|
||||||
|
|
||||||
final dynamic stationId = data.selectedStation?['man_station_id'];
|
// --- START FIX: Use correct key 'station_id' with fallback to 'man_station_id' ---
|
||||||
|
final dynamic stationId = data.selectedStation?['station_id'] ?? data.selectedStation?['man_station_id'];
|
||||||
|
// --- END FIX ---
|
||||||
|
|
||||||
if (stationId == null) return ""; // Cannot check limits
|
if (stationId == null) return ""; // Cannot check limits
|
||||||
|
|
||||||
final readings = {
|
final readings = {
|
||||||
@ -843,7 +846,7 @@ class MarineInvestigativeSamplingService {
|
|||||||
if (isHit) {
|
if (isHit) {
|
||||||
final valueStr = value.toStringAsFixed(5);
|
final valueStr = value.toStringAsFixed(5);
|
||||||
final lowerStr = lowerLimit?.toStringAsFixed(5) ?? 'N/A';
|
final lowerStr = lowerLimit?.toStringAsFixed(5) ?? 'N/A';
|
||||||
final upperStr = upperLimit?.toStringAsFixed(5) ?? 'N/á';
|
final upperStr = upperLimit?.toStringAsFixed(5) ?? 'N/A';
|
||||||
String limitStr;
|
String limitStr;
|
||||||
if (lowerStr != 'N/A' && upperStr != 'N/A') {
|
if (lowerStr != 'N/A' && upperStr != 'N/A') {
|
||||||
limitStr = '$lowerStr - $upperStr';
|
limitStr = '$lowerStr - $upperStr';
|
||||||
@ -864,7 +867,7 @@ class MarineInvestigativeSamplingService {
|
|||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln(' ')
|
..writeln(' ')
|
||||||
..writeln('🚨 *NPE Parameter Limit Detected:*')
|
..writeln('🚨 *Marine NPE Parameter Limit Detected:*')
|
||||||
..writeln('The following parameters triggered an NPE alert:');
|
..writeln('The following parameters triggered an NPE alert:');
|
||||||
buffer.writeAll(npeMessages, '\n');
|
buffer.writeAll(npeMessages, '\n');
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user