diff --git a/lib/screens/air/manual/widgets/air_manual_collection.dart b/lib/screens/air/manual/widgets/air_manual_collection.dart index b1da607..957e56d 100644 --- a/lib/screens/air/manual/widgets/air_manual_collection.dart +++ b/lib/screens/air/manual/widgets/air_manual_collection.dart @@ -49,6 +49,8 @@ class _AirManualCollectionWidgetState extends State { final _pm10TimeResultController = TextEditingController(); final _pm10PressureController = TextEditingController(); final _pm10PressureResultController = TextEditingController(); + final _pm10TavgController = TextEditingController(); + final _pm10QstdController = TextEditingController(); final _pm10VstdController = TextEditingController(); // PM2.5 Controllers @@ -58,6 +60,8 @@ class _AirManualCollectionWidgetState extends State { final _pm25TimeResultController = TextEditingController(); final _pm25PressureController = TextEditingController(); final _pm25PressureResultController = TextEditingController(); + final _pm25TavgController = TextEditingController(); + final _pm25QstdController = TextEditingController(); final _pm25VstdController = TextEditingController(); @override @@ -88,6 +92,8 @@ class _AirManualCollectionWidgetState extends State { _pm10TimeResultController.dispose(); _pm10PressureController.dispose(); _pm10PressureResultController.dispose(); + _pm10TavgController.dispose(); + _pm10QstdController.dispose(); _pm10VstdController.dispose(); _pm25FlowRateController.dispose(); _pm25FlowRateResultController.dispose(); @@ -95,6 +101,8 @@ class _AirManualCollectionWidgetState extends State { _pm25TimeResultController.dispose(); _pm25PressureController.dispose(); _pm25PressureResultController.dispose(); + _pm25TavgController.dispose(); + _pm25QstdController.dispose(); _pm25VstdController.dispose(); super.dispose(); } @@ -153,35 +161,58 @@ class _AirManualCollectionWidgetState extends State { void _calculateVstd(int type) { setState(() { try { + // Get the correct controllers for either PM10 or PM2.5 + final flowRateCtrl = type == 1 ? _pm10FlowRateController : _pm25FlowRateController; final pressureCtrl = type == 1 ? _pm10PressureController : _pm25PressureController; - final flowResultCtrl = type == 1 ? _pm10FlowRateResultController : _pm25FlowRateResultController; final timeResultCtrl = type == 1 ? _pm10TimeResultController : _pm25TimeResultController; - final vstdCtrl = type == 1 ? _pm10VstdController : _pm25VstdController; final pressureResultCtrl = type == 1 ? _pm10PressureResultController : _pm25PressureResultController; + final tAvgCtrl = type == 1 ? _pm10TavgController : _pm25TavgController; + final qStdCtrl = type == 1 ? _pm10QstdController : _pm25QstdController; + final vstdCtrl = type == 1 ? _pm10VstdController : _pm25VstdController; - if (pressureCtrl.text.isEmpty || _finalTempController.text.isEmpty || flowResultCtrl.text.isEmpty || timeResultCtrl.text.isEmpty) { + if (flowRateCtrl.text.isEmpty || + pressureCtrl.text.isEmpty || + _finalTempController.text.isEmpty || + timeResultCtrl.text.isEmpty) { + tAvgCtrl.text = ""; + qStdCtrl.text = ""; vstdCtrl.text = "0.000"; return; } + const double hpaToinHg = 0.0295; + const double inHgToMmHg = 25.4; + + // 1. Get time and user-input flowrate (cfm) + double totalTime_min = double.parse(timeResultCtrl.text); + // **CRITICAL FIX**: Use the actual flowrate keyed in by the user (in cfm) + double qa_cfm_actual = double.parse(flowRateCtrl.text); + + // 2. Calculate actual pressure double pressureHpa = double.parse(pressureCtrl.text); - double pressureInHg = pressureHpa * 0.02953; + double pressureInHg = pressureHpa * hpaToinHg; + double p_actual_mmHg = pressureInHg * inHgToMmHg; // result pressure pressureResultCtrl.text = pressureInHg.toStringAsFixed(2); - double qa_m3min = double.parse(flowResultCtrl.text); - double totalTime_min = double.parse(timeResultCtrl.text); - double pa_mmHg = pressureInHg * 25.4; - double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0; + // 3. Calculate average temperature in Celsius + double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0; // ambient temperature + tAvgCtrl.text = t_avg_celsius.toStringAsFixed(2); - double numerator = qa_m3min * pa_mmHg * 298.15; - double denominator = 760 * (273.15 + t_avg_celsius); - double q_std = numerator / denominator; + // 4. Calculate Standard Flowrate (QSTD) using your exact formula + // Formula: (flowrate * result pressure * 298) / (760 * (273 * ambient temperature)) + double q_std_numerator = qa_cfm_actual * pressureInHg * 298; + double q_std_denominator = (760 * (273 + t_avg_celsius)); + double q_std = (q_std_denominator == 0) ? 0 : q_std_numerator / q_std_denominator; + qStdCtrl.text = q_std.toStringAsFixed(5); + + // 5. Final VSTD + // Formula: QSTD * Total time double v_std = q_std * totalTime_min; - if (type == 1) { + if (type == 1) { // PM10 _pm10VstdController.text = v_std.toStringAsFixed(3); widget.data.pm10Vstd = v_std; - } else { + } else { // PM2.5 _pm25VstdController.text = v_std.toStringAsFixed(3); widget.data.pm25Vstd = v_std; } @@ -192,13 +223,10 @@ class _AirManualCollectionWidgetState extends State { }); } - // **CRITICAL FIX IS HERE** void _onSubmitPressed() { if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); // Saves data from fields with an onSaved function + _formKey.currentState!.save(); - // **THE FIX**: Manually save the values from the read-only result controllers - // into the data model before submitting, as they don't have onSaved functions. widget.data.pm10FlowrateResult = _pm10FlowRateResultController.text; widget.data.pm10TotalTimeResult = _pm10TimeResultController.text; widget.data.pm10PressureResult = _pm10PressureResultController.text; @@ -206,13 +234,11 @@ class _AirManualCollectionWidgetState extends State { widget.data.pm25TotalTimeResult = _pm25TimeResultController.text; widget.data.pm25PressureResult = _pm25PressureResultController.text; - // Save optional remarks widget.data.optionalRemark1 = _optionalRemark1Controller.text; widget.data.optionalRemark2 = _optionalRemark2Controller.text; widget.data.optionalRemark3 = _optionalRemark3Controller.text; widget.data.optionalRemark4 = _optionalRemark4Controller.text; - // Now, call the submission function with the fully populated data model widget.onSubmit(); } } @@ -261,17 +287,62 @@ class _AirManualCollectionWidgetState extends State { validator: (v) => v == null ? 'Required' : null, onSaved: (v) => widget.data.powerFailure = v, ), - const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM10 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), _buildResultRow(_pm10FlowRateController, "Flow Rate (cfm)", _pm10FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm10FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm10Flowrate = double.tryParse(v!)), _buildResultRow(_pm10TimeController, "Total Time (hours)", _pm10TimeResultController, "Result (mins)", (v) => setState(() => _pm10TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm10TotalTime = v), _buildResultRow(_pm10PressureController, "Pressure (hPa)", _pm10PressureResultController, "Result (inHg)", (v) => _calculateVstd(1), (v) => widget.data.pm10Pressure = double.tryParse(v!)), + + const SizedBox(height: 8), + TextFormField( + controller: _pm10TavgController, + readOnly: true, + decoration: const InputDecoration( + labelText: 'Ambient Temperature [Tavg=(T1+T2)/2] (°C)', + border: OutlineInputBorder(), + filled: true, + fillColor: Colors.black12), + ), + const SizedBox(height: 8), + TextFormField( + controller: _pm10QstdController, + readOnly: true, + decoration: const InputDecoration( + labelText: 'Flowrate (Standard Air), QSTD (m³/min)', + border: OutlineInputBorder(), + filled: true, + fillColor: Colors.black12), + ), + const SizedBox(height: 8), + TextFormField(controller: _pm10VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM10 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)), const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM2.5 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), _buildResultRow(_pm25FlowRateController, "Flow Rate (cfm)", _pm25FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm25FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm25Flowrate = double.tryParse(v!)), _buildResultRow(_pm25TimeController, "Total Time (hours)", _pm25TimeResultController, "Result (mins)", (v) => setState(() => _pm25TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm25TotalTime = v), _buildResultRow(_pm25PressureController, "Pressure (hPa)", _pm25PressureResultController, "Result (inHg)", (v) => _calculateVstd(2), (v) => widget.data.pm25Pressure = double.tryParse(v!)), + + const SizedBox(height: 8), + TextFormField( + controller: _pm25TavgController, + readOnly: true, + decoration: const InputDecoration( + labelText: 'Ambient Temperature [Tavg=(T1+T2)/2] (°C)', + border: OutlineInputBorder(), + filled: true, + fillColor: Colors.black12), + ), + const SizedBox(height: 8), + TextFormField( + controller: _pm25QstdController, + readOnly: true, + decoration: const InputDecoration( + labelText: 'Flowrate (Standard Air), QSTD (m³/min)', + border: OutlineInputBorder(), + filled: true, + fillColor: Colors.black12), + ), + const SizedBox(height: 8), + TextFormField(controller: _pm25VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM2.5 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)), const SizedBox(height: 16),