Commit d6fc2a2b authored by 张宏's avatar 张宏
parents d2db426e 2160220c
...@@ -5,6 +5,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; ...@@ -5,6 +5,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'device_control_event.dart'; import 'device_control_event.dart';
import 'device_control_state.dart'; import 'device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart'; import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart'; import 'package:laki_icu_app/utils/bluetooth/index.dart';
import 'package:laki_icu_app/utils/event_bus.dart'; import 'package:laki_icu_app/utils/event_bus.dart';
import 'package:laki_icu_app/utils/logger.dart'; import 'package:laki_icu_app/utils/logger.dart';
...@@ -181,6 +182,11 @@ class DeviceControlBloc ...@@ -181,6 +182,11 @@ class DeviceControlBloc
); );
if (reports.isEmpty || isClosed) return; if (reports.isEmpty || isClosed) return;
final latestReport = reports.last; final latestReport = reports.last;
eventBus.emit(
BluetoothReadInfoChangedEvent(
BluetoothReadModel.fromMcuReport(latestReport),
),
);
eventBus.emit(McuReportChangedEvent(latestReport)); eventBus.emit(McuReportChangedEvent(latestReport));
} catch (e) { } catch (e) {
Log.warn('🔥🔥🔥 BLE_READ_DEBUG device_control_parse_error error=$e'); Log.warn('🔥🔥🔥 BLE_READ_DEBUG device_control_parse_error error=$e');
...@@ -210,19 +216,31 @@ class DeviceControlBloc ...@@ -210,19 +216,31 @@ class DeviceControlBloc
sn: report.sn.isEmpty ? state.data.sn : report.sn, sn: report.sn.isEmpty ? state.data.sn : report.sn,
cabinTemp: state.data.cabinTemp.copyWith( cabinTemp: state.data.cabinTemp.copyWith(
currentValue: _formatTemperature(report.mainTemperature), currentValue: _formatTemperature(report.mainTemperature),
setValue: _formatTemperature(report.temperatureSetting), setValue: _formatTemperatureOrKeep(
report.temperatureSetting,
state.data.cabinTemp.setValue,
),
), ),
cabinHumidity: state.data.cabinHumidity.copyWith( cabinHumidity: state.data.cabinHumidity.copyWith(
currentValue: _formatInt(report.humidity), currentValue: _formatInt(report.humidity),
setValue: _formatInt(report.humiditySetting), setValue: _formatIntOrKeep(
report.humiditySetting,
state.data.cabinHumidity.setValue,
),
), ),
oxygenConcentration: state.data.oxygenConcentration.copyWith( oxygenConcentration: state.data.oxygenConcentration.copyWith(
currentValue: _formatInt(report.oxygenConcentration), currentValue: _formatInt(report.oxygenConcentration),
setValue: _formatInt(report.oxygenSetting), setValue: _formatIntOrKeep(
report.oxygenSetting,
state.data.oxygenConcentration.setValue,
),
), ),
co2Concentration: state.data.co2Concentration.copyWith( co2Concentration: state.data.co2Concentration.copyWith(
currentValue: _formatInt(report.co2Measurement), currentValue: _formatInt(report.co2Measurement),
alarmThreshold: _formatInt(report.co2Setting), alarmThreshold: _formatIntOrKeep(
report.co2Setting,
state.data.co2Concentration.alarmThreshold,
),
), ),
); );
emit(state.copyWith(data: newData, clearError: true)); emit(state.copyWith(data: newData, clearError: true));
...@@ -287,6 +305,10 @@ class DeviceControlBloc ...@@ -287,6 +305,10 @@ class DeviceControlBloc
) async { ) async {
final currentValue = int.tryParse(state.data.cabinTemp.setValue) ?? 0; final currentValue = int.tryParse(state.data.cabinTemp.setValue) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 99); final nextValue = (currentValue + event.step).clamp(0, 99);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG cabin_temp_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
);
await _setCabinTempSetValue('$nextValue', emit); await _setCabinTempSetValue('$nextValue', emit);
} }
...@@ -304,6 +326,9 @@ class DeviceControlBloc ...@@ -304,6 +326,9 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(cabinTempSetValue: setValue), userSettings: state.userSettings.copyWith(cabinTempSetValue: setValue),
clearError: true, clearError: true,
)); ));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG cabin_temp_set_value value=$setValue',
);
await _writeControlValueToBluetooth( await _writeControlValueToBluetooth(
identifier: 0x02, identifier: 0x02,
setValue: setValue, setValue: setValue,
...@@ -361,6 +386,10 @@ class DeviceControlBloc ...@@ -361,6 +386,10 @@ class DeviceControlBloc
) async { ) async {
final currentValue = int.tryParse(state.data.cabinHumidity.setValue) ?? 0; final currentValue = int.tryParse(state.data.cabinHumidity.setValue) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 100); final nextValue = (currentValue + event.step).clamp(0, 100);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG cabin_humidity_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
);
await _setCabinHumiditySetValue('$nextValue', emit); await _setCabinHumiditySetValue('$nextValue', emit);
} }
...@@ -379,6 +408,9 @@ class DeviceControlBloc ...@@ -379,6 +408,9 @@ class DeviceControlBloc
state.userSettings.copyWith(cabinHumiditySetValue: setValue), state.userSettings.copyWith(cabinHumiditySetValue: setValue),
clearError: true, clearError: true,
)); ));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG cabin_humidity_set_value value=$setValue',
);
await _writeControlValueToBluetooth( await _writeControlValueToBluetooth(
identifier: 0x04, identifier: 0x04,
setValue: setValue, setValue: setValue,
...@@ -436,6 +468,10 @@ class DeviceControlBloc ...@@ -436,6 +468,10 @@ class DeviceControlBloc
final currentValue = final currentValue =
int.tryParse(state.data.oxygenConcentration.setValue) ?? 0; int.tryParse(state.data.oxygenConcentration.setValue) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 100); final nextValue = (currentValue + event.step).clamp(0, 100);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG oxygen_concentration_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
);
await _setOxygenConcentrationSetValue('$nextValue', emit); await _setOxygenConcentrationSetValue('$nextValue', emit);
} }
...@@ -454,6 +490,9 @@ class DeviceControlBloc ...@@ -454,6 +490,9 @@ class DeviceControlBloc
state.userSettings.copyWith(oxygenConcentrationSetValue: setValue), state.userSettings.copyWith(oxygenConcentrationSetValue: setValue),
clearError: true, clearError: true,
)); ));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG oxygen_concentration_set_value value=$setValue',
);
await _writeControlValueToBluetooth( await _writeControlValueToBluetooth(
identifier: 0x05, identifier: 0x05,
setValue: setValue, setValue: setValue,
...@@ -512,6 +551,10 @@ class DeviceControlBloc ...@@ -512,6 +551,10 @@ class DeviceControlBloc
final currentValue = final currentValue =
int.tryParse(state.data.co2Concentration.alarmThreshold) ?? 0; int.tryParse(state.data.co2Concentration.alarmThreshold) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 9999); final nextValue = (currentValue + event.step).clamp(0, 9999);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG co2_alarm_threshold_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
);
await _setCO2ConcentrationAlarmThreshold('$nextValue', emit); await _setCO2ConcentrationAlarmThreshold('$nextValue', emit);
} }
...@@ -530,6 +573,10 @@ class DeviceControlBloc ...@@ -530,6 +573,10 @@ class DeviceControlBloc
state.userSettings.copyWith(co2AlarmThreshold: alarmThreshold), state.userSettings.copyWith(co2AlarmThreshold: alarmThreshold),
clearError: true, clearError: true,
)); ));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG co2_alarm_threshold_set_value '
'value=$alarmThreshold',
);
await _writeControlValueToBluetooth( await _writeControlValueToBluetooth(
identifier: 0x31, identifier: 0x31,
setValue: alarmThreshold, setValue: alarmThreshold,
...@@ -581,10 +628,10 @@ class DeviceControlBloc ...@@ -581,10 +628,10 @@ class DeviceControlBloc
// ==================== 风速调节控制事件处理 ==================== // ==================== 风速调节控制事件处理 ====================
/// [用户操作] 设置风速模式(发送给MCU)—— 持久化 /// [用户操作] 设置风速模式(发送给MCU)—— 持久化
void _onWindSpeedModeChanged( Future<void> _onWindSpeedModeChanged(
DeviceControlWindSpeedModeChanged event, DeviceControlWindSpeedModeChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
windSpeed: state.data.windSpeed.copyWith( windSpeed: state.data.windSpeed.copyWith(
currentMode: event.mode, currentMode: event.mode,
...@@ -595,7 +642,12 @@ class DeviceControlBloc ...@@ -595,7 +642,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(windSpeedModeValue: event.mode), userSettings: state.userSettings.copyWith(windSpeedModeValue: event.mode),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeControlValueToBluetooth(
identifier: 0xB3,
setValue: '${_windSpeedProtocolValue(event.mode)}',
errorPrefix: '风速调节',
emit: emit,
);
} }
// ==================== 雾化时间设置事件处理 ==================== // ==================== 雾化时间设置事件处理 ====================
...@@ -643,10 +695,10 @@ class DeviceControlBloc ...@@ -643,10 +695,10 @@ class DeviceControlBloc
// ==================== 开放供氧控制事件处理 ==================== // ==================== 开放供氧控制事件处理 ====================
/// [用户操作] 设置开放供氧开关(发送给MCU)—— 持久化 /// [用户操作] 设置开放供氧开关(发送给MCU)—— 持久化
void _onOpenOxygenSwitchChanged( Future<void> _onOpenOxygenSwitchChanged(
DeviceControlOpenOxygenSwitchChanged event, DeviceControlOpenOxygenSwitchChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
openOxygen: state.data.openOxygen.copyWith( openOxygen: state.data.openOxygen.copyWith(
isOn: event.isOn, isOn: event.isOn,
...@@ -657,7 +709,12 @@ class DeviceControlBloc ...@@ -657,7 +709,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(openOxygenIsOn: event.isOn), userSettings: state.userSettings.copyWith(openOxygenIsOn: event.isOn),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x26,
isOn: event.isOn,
errorPrefix: '开放供氧',
emit: emit,
);
} }
// ==================== 供氧计时事件处理 ==================== // ==================== 供氧计时事件处理 ====================
...@@ -696,10 +753,10 @@ class DeviceControlBloc ...@@ -696,10 +753,10 @@ class DeviceControlBloc
// ==================== 循环模式控制事件处理 ==================== // ==================== 循环模式控制事件处理 ====================
/// [用户操作] 设置循环模式(发送给MCU)—— 持久化 /// [用户操作] 设置循环模式(发送给MCU)—— 持久化
void _onCirculationModeChanged( Future<void> _onCirculationModeChanged(
DeviceControlCirculationModeChanged event, DeviceControlCirculationModeChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
circulationMode: state.data.circulationMode.copyWith( circulationMode: state.data.circulationMode.copyWith(
currentMode: event.mode, currentMode: event.mode,
...@@ -711,16 +768,21 @@ class DeviceControlBloc ...@@ -711,16 +768,21 @@ class DeviceControlBloc
state.userSettings.copyWith(circulationModeValue: event.mode), state.userSettings.copyWith(circulationModeValue: event.mode),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x06,
isOn: event.mode == CirculationMode.external,
errorPrefix: event.mode == CirculationMode.external ? '外循环' : '内循环',
emit: emit,
);
} }
// ==================== 灯光控制事件处理 ==================== // ==================== 灯光控制事件处理 ====================
/// [用户操作] 设置检查灯开关(发送给MCU)—— 持久化 /// [用户操作] 设置检查灯开关(发送给MCU)—— 持久化
void _onCheckLightSwitchChanged( Future<void> _onCheckLightSwitchChanged(
DeviceControlCheckLightSwitchChanged event, DeviceControlCheckLightSwitchChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith( lightControl: state.data.lightControl.copyWith(
checkLightOn: event.isOn, checkLightOn: event.isOn,
...@@ -731,14 +793,19 @@ class DeviceControlBloc ...@@ -731,14 +793,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(checkLightOn: event.isOn), userSettings: state.userSettings.copyWith(checkLightOn: event.isOn),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x0A,
isOn: event.isOn,
errorPrefix: '检查灯',
emit: emit,
);
} }
/// [用户操作] 设置照明灯开关(发送给MCU)—— 持久化 /// [用户操作] 设置照明灯开关(发送给MCU)—— 持久化
void _onIlluminationLightSwitchChanged( Future<void> _onIlluminationLightSwitchChanged(
DeviceControlIlluminationLightSwitchChanged event, DeviceControlIlluminationLightSwitchChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith( lightControl: state.data.lightControl.copyWith(
illuminationOn: event.isOn, illuminationOn: event.isOn,
...@@ -749,14 +816,19 @@ class DeviceControlBloc ...@@ -749,14 +816,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(illuminationOn: event.isOn), userSettings: state.userSettings.copyWith(illuminationOn: event.isOn),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x09,
isOn: event.isOn,
errorPrefix: '照明灯',
emit: emit,
);
} }
/// [用户操作] 设置蓝光灯开关(发送给MCU)—— 持久化 /// [用户操作] 设置蓝光灯开关(发送给MCU)—— 持久化
void _onBlueLightSwitchChanged( Future<void> _onBlueLightSwitchChanged(
DeviceControlBlueLightSwitchChanged event, DeviceControlBlueLightSwitchChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith( lightControl: state.data.lightControl.copyWith(
blueLightOn: event.isOn, blueLightOn: event.isOn,
...@@ -767,14 +839,19 @@ class DeviceControlBloc ...@@ -767,14 +839,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(blueLightOn: event.isOn), userSettings: state.userSettings.copyWith(blueLightOn: event.isOn),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x1E,
isOn: event.isOn,
errorPrefix: '蓝光',
emit: emit,
);
} }
/// [用户操作] 设置红光灯开关(发送给MCU)—— 持久化 /// [用户操作] 设置红光灯开关(发送给MCU)—— 持久化
void _onRedLightSwitchChanged( Future<void> _onRedLightSwitchChanged(
DeviceControlRedLightSwitchChanged event, DeviceControlRedLightSwitchChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith( lightControl: state.data.lightControl.copyWith(
redLightOn: event.isOn, redLightOn: event.isOn,
...@@ -785,7 +862,12 @@ class DeviceControlBloc ...@@ -785,7 +862,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(redLightOn: event.isOn), userSettings: state.userSettings.copyWith(redLightOn: event.isOn),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeSwitchValueToBluetooth(
identifier: 0x1F,
isOn: event.isOn,
errorPrefix: '红光',
emit: emit,
);
} }
// ==================== 全局统计数据事件处理 ==================== // ==================== 全局统计数据事件处理 ====================
...@@ -860,12 +942,48 @@ class DeviceControlBloc ...@@ -860,12 +942,48 @@ class DeviceControlBloc
await _bluetoothManager.connectToDeviceId(deviceId); await _bluetoothManager.connectToDeviceId(deviceId);
} }
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG send_control '
'identifier=0x${identifier.toRadixString(16).padLeft(2, '0').toUpperCase()} '
'value=$setValue scale=$scale '
'hex=${BleBluetoothManager.bytesToHex(command)} '
'isConnected=${_bluetoothManager.isConnected}',
);
await _bluetoothManager.writeData(command); await _bluetoothManager.writeData(command);
} catch (e) { } catch (e) {
emit(state.copyWith(error: '$errorPrefix发送失败: $e')); emit(state.copyWith(error: '$errorPrefix发送失败: $e'));
} }
} }
Future<void> _writeSwitchValueToBluetooth({
required int identifier,
required bool isOn,
required String errorPrefix,
required Emitter<DeviceControlState> emit,
}) async {
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG switch_set_value '
'name=$errorPrefix isOn=$isOn',
);
await _writeControlValueToBluetooth(
identifier: identifier,
setValue: isOn ? '1' : '0',
errorPrefix: errorPrefix,
emit: emit,
);
}
int _windSpeedProtocolValue(WindSpeedMode mode) {
switch (mode) {
case WindSpeedMode.comfort:
return 0;
case WindSpeedMode.strong:
return 1;
case WindSpeedMode.sleep:
return 2;
}
}
List<int>? _buildControlValueCommand({ List<int>? _buildControlValueCommand({
required int identifier, required int identifier,
required String setValue, required String setValue,
...@@ -893,10 +1011,18 @@ class DeviceControlBloc ...@@ -893,10 +1011,18 @@ class DeviceControlBloc
return fixed.endsWith('.0') ? fixed.substring(0, fixed.length - 2) : fixed; return fixed.endsWith('.0') ? fixed.substring(0, fixed.length - 2) : fixed;
} }
String _formatTemperatureOrKeep(double value, String fallback) {
return value.isNaN ? fallback : _formatTemperature(value);
}
String _formatInt(int value) { String _formatInt(int value) {
return value < 0 ? '--' : '$value'; return value < 0 ? '--' : '$value';
} }
String _formatIntOrKeep(int value, String fallback) {
return value < 0 ? fallback : '$value';
}
@override @override
Future<void> close() async { Future<void> close() async {
await _mcuReportSub.cancel(); await _mcuReportSub.cancel();
......
...@@ -376,6 +376,15 @@ class BleBluetoothManager<T> { ...@@ -376,6 +376,15 @@ class BleBluetoothManager<T> {
return; return;
} }
final hex = bytesToHex(data);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG write_data '
'deviceId=${characteristic.deviceId} '
'service=${characteristic.serviceId} '
'characteristic=${characteristic.characteristicId} '
'withoutResponse=$withoutResponse '
'hex=$hex',
);
try { try {
if (withoutResponse) { if (withoutResponse) {
await _ble.writeCharacteristicWithoutResponse( await _ble.writeCharacteristicWithoutResponse(
...@@ -385,9 +394,11 @@ class BleBluetoothManager<T> { ...@@ -385,9 +394,11 @@ class BleBluetoothManager<T> {
} else { } else {
await _ble.writeCharacteristicWithResponse(characteristic, value: data); await _ble.writeCharacteristicWithResponse(characteristic, value: data);
} }
_notifyMessage('蓝牙数据发送成功: ${bytesToHex(data)}'); Log.warn('🔥🔥🔥 BLE_WRITE_DEBUG write_success hex=$hex');
_notifyMessage('蓝牙数据发送成功: $hex');
_sendResultController.add(true); _sendResultController.add(true);
} catch (error) { } catch (error) {
Log.warn('🔥🔥🔥 BLE_WRITE_DEBUG write_error hex=$hex error=$error');
_notifyMessage('蓝牙数据发送失败: $error'); _notifyMessage('蓝牙数据发送失败: $error');
_sendResultController.add(false); _sendResultController.add(false);
} }
......
import 'package:flutter/material.dart';
/// 自定义数字键盘
///
/// 使用示例:
/// ```dart
/// final result = await NumericKeyboard.show(
/// context,
/// initialValue: '25',
/// decimalEnabled: true, // 温度带小数点,其他设置为 false
/// );
/// if (result != null) {
/// // 使用 result 更新设定值
/// }
/// ```
class NumericKeyboard extends StatefulWidget {
const NumericKeyboard({
super.key,
required this.initialValue,
this.decimalEnabled = false,
this.onChanged,
this.onConfirm,
});
/// 初始显示值
final String initialValue;
/// 是否允许输入小数点(温度模式为 true)
final bool decimalEnabled;
/// 输入变化回调(可选)
final ValueChanged<String>? onChanged;
/// 确认按钮回调(可选,配合页面内嵌使用)
final ValueChanged<String>? onConfirm;
/// 弹出数字键盘并返回用户确认的值
///
/// [context] 构建上下文
/// [initialValue] 初始值,默认为空字符串
/// [decimalEnabled] 是否启用小数点输入,默认为 false
///
/// 返回用户点击确认时的字符串,或 null(如退回/关闭)
static Future<String?> show(
BuildContext context, {
String initialValue = '',
bool decimalEnabled = false,
}) {
return showModalBottomSheet<String>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
barrierColor: Colors.black54,
builder: (_) => NumericKeyboard(
initialValue: initialValue,
decimalEnabled: decimalEnabled,
),
);
}
@override
State<NumericKeyboard> createState() => _NumericKeyboardState();
}
class _NumericKeyboardState extends State<NumericKeyboard> {
late String _text;
bool get _hasDecimal => _text.contains('.');
@override
void initState() {
super.initState();
_text = widget.initialValue;
}
void _onKeyTap(String key) {
setState(() {
switch (key) {
case '⌫':
if (_text.isNotEmpty) {
_text = _text.substring(0, _text.length - 1);
}
break;
case 'C':
_text = '';
break;
case 'OK':
// 确认时调用回调或关闭弹窗
if (widget.onConfirm != null) {
widget.onConfirm!(_text);
} else {
Navigator.of(context).pop(_text);
}
return; // 不触发 onChanged 和后续 setState 中的 pop
case '.':
if (!widget.decimalEnabled || _hasDecimal) return;
if (_text.isEmpty) {
_text = '0.';
} else {
_text = '$_text.';
}
break;
default:
// 数字 0-9
// 避免前导零(除非是 "0." 开头)
if (_text == '0' && key != '.') {
_text = key;
} else {
_text = '$_text$key';
}
break;
}
});
if (key != 'OK') {
widget.onChanged?.call(_text);
}
}
@override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
return Container(
padding: EdgeInsets.fromLTRB(18, 18, 18, bottomPadding + 14),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF0D3558),
Color(0xFF06192F),
],
),
border: Border(
top: BorderSide(color: Color(0xFF7BD5FF), width: 1.5),
),
boxShadow: [
BoxShadow(
color: Color(0x8000A8FF),
blurRadius: 22,
offset: Offset(0, -6),
),
],
),
child: SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHandle(),
const SizedBox(height: 14),
_buildDisplayArea(),
const SizedBox(height: 10),
_buildKeypad(),
],
),
),
);
}
Widget _buildHandle() {
return Container(
width: 78,
height: 4,
decoration: BoxDecoration(
color: const Color(0xFF79D7FF).withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(2),
),
);
}
Widget _buildDisplayArea() {
return Row(
children: [
Expanded(
child: Container(
height: 58,
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 18),
decoration: BoxDecoration(
color: const Color(0xFF041126),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFF58BCEB), width: 1.2),
boxShadow: const [
BoxShadow(
color: Color(0x4000A8FF),
blurRadius: 14,
offset: Offset(0, 0),
),
],
),
child: Text(
_text.isEmpty ? '0' : _text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 30,
height: 1,
fontWeight: FontWeight.w700,
color: Color(0xFFEAF9FF),
),
),
),
),
const SizedBox(width: 12),
SizedBox(
width: 104,
height: 58,
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => _onKeyTap('OK'),
child: Ink(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0xFF25D8FF),
Color(0xFF1577D8),
],
),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFB6F4FF)),
boxShadow: const [
BoxShadow(
color: Color(0x6625D8FF),
blurRadius: 16,
),
],
),
child: const Center(
child: Text(
'确定',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
),
),
),
],
);
}
Widget _buildKeypad() {
// 按键定义:行列表,每行包含(标签, 宽度比例, 颜色类型)
final keys = <List<_KeyDef>>[
[
const _KeyDef('1', 1),
const _KeyDef('2', 1),
const _KeyDef('3', 1),
const _KeyDef('⌫', 1, isAction: true),
],
[
const _KeyDef('4', 1),
const _KeyDef('5', 1),
const _KeyDef('6', 1),
const _KeyDef('C', 1, isAction: true),
],
[
const _KeyDef('7', 1),
const _KeyDef('8', 1),
const _KeyDef('9', 1),
const _KeyDef('', 1),
],
[
if (widget.decimalEnabled) const _KeyDef('.', 1),
_KeyDef('0', widget.decimalEnabled ? 2 : 3),
const _KeyDef('', 1),
],
];
return Padding(
padding: const EdgeInsets.only(top: 2),
child: Column(
children: keys.map((row) {
return Row(
children: row.map((keyDef) {
if (keyDef.label.isEmpty) {
return const Spacer(flex: 1);
}
return Expanded(
flex: keyDef.flex,
child: Padding(
padding: const EdgeInsets.all(5),
child: _buildKeyButton(keyDef),
),
);
}).toList(),
);
}).toList(),
),
);
}
Widget _buildKeyButton(_KeyDef keyDef) {
final isAction = keyDef.isAction;
return SizedBox(
height: 60,
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => _onKeyTap(keyDef.label),
child: Ink(
decoration: BoxDecoration(
color: isAction
? const Color(0xFF10385D)
: const Color(0xFF092849),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isAction
? const Color(0xFF5D9EC5)
: const Color(0xFF3F9ED2),
width: 1,
),
boxShadow: const [
BoxShadow(
color: Color(0x2600A8FF),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: Center(
child: Text(
keyDef.label,
style: TextStyle(
fontSize: isAction ? 20 : 25,
fontWeight: isAction ? FontWeight.w600 : FontWeight.w700,
color: isAction
? const Color(0xFFB8E7FF)
: const Color(0xFFEAF9FF),
),
),
),
),
),
),
);
}
}
/// 键盘按键定义
class _KeyDef {
final String label;
final int flex;
final bool isAction;
const _KeyDef(this.label, this.flex, {this.isAction = false});
}
...@@ -2,6 +2,9 @@ import 'package:auto_route/auto_route.dart'; ...@@ -2,6 +2,9 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart'; import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'cubit/device_control_index_cubit.dart'; import 'cubit/device_control_index_cubit.dart';
...@@ -130,23 +133,32 @@ class _DeviceControlLayout extends StatelessWidget { ...@@ -130,23 +133,32 @@ class _DeviceControlLayout extends StatelessWidget {
SizedBox(height: 24.h), SizedBox(height: 24.h),
Expanded( Expanded(
flex: 58, flex: 58,
child: Row( child: LayoutBuilder(
children: [ builder: (context, constraints) {
const Expanded( final gap = 24.w;
flex: 48, final metricCardWidth =
child: DeviceControlModePanel(), (constraints.maxWidth - gap * 3) / 4;
), final modePanelWidth = metricCardWidth * 2 + gap;
SizedBox(width: 24.w),
const Expanded( return Row(
flex: 23, children: [
child: DeviceControlWindPanel(), SizedBox(
), width: modePanelWidth,
SizedBox(width: 24.w), child: const DeviceControlModePanel(),
const Expanded( ),
flex: 29, SizedBox(width: gap),
child: DeviceControlTimePanel(), SizedBox(
), width: metricCardWidth,
], child: const DeviceControlWindPanel(),
),
SizedBox(width: gap),
SizedBox(
width: metricCardWidth,
child: const DeviceControlTimePanel(),
),
],
);
},
), ),
), ),
], ],
...@@ -169,14 +181,27 @@ class _DeviceControlLayout extends StatelessWidget { ...@@ -169,14 +181,27 @@ class _DeviceControlLayout extends StatelessWidget {
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
const Expanded( Expanded(
flex: 20, flex: 20,
child: DeviceControlInfoPanel( child:
title: '开放供氧', BlocBuilder<DeviceControlBloc, DeviceControlState>(
actionText: 'Off', buildWhen: (previous, current) =>
icon: Icons.air, previous.data.openOxygen !=
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。', current.data.openOxygen,
warning: '*保持设备周围空气相对禁止,严禁烟火', builder: (context, state) {
final isOn = state.data.openOxygen.isOn;
return DeviceControlInfoPanel(
title: '开放供氧',
actionText: isOn ? 'ON' : 'Off',
active: isOn,
icon: Icons.air,
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。',
warning: '*保持设备周围空气相对禁止,严禁烟火',
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlOpenOxygenSwitchChanged(!isOn),
),
);
},
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart'; import 'device_control_common.dart';
...@@ -9,43 +14,174 @@ class DeviceControlCarePanel extends StatelessWidget { ...@@ -9,43 +14,174 @@ class DeviceControlCarePanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DeviceControlPanelFrame( return BlocBuilder<DeviceControlBloc, DeviceControlState>(
backgroundAsset: assetPanelWide, buildWhen: (previous, current) =>
padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 24.h), previous.data.careLevel != current.data.careLevel,
child: Column( builder: (context, state) {
crossAxisAlignment: CrossAxisAlignment.start, final currentLevel = state.data.careLevel.currentLevel;
children: [
const DeviceControlPanelTitle('护理等级'), return DeviceControlPanelFrame(
const Spacer(), backgroundAsset: assetPanelWide,
Container( padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 24.h),
height: 20.h, child: Column(
decoration: BoxDecoration( crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: BorderRadius.circular(10.r), children: [
gradient: const LinearGradient( const DeviceControlPanelTitle('护理等级'),
colors: [ const Spacer(),
Color(0xFFDCEFFC), _CareLevelBar(currentLevel: currentLevel),
Color(0xFF71FFCF), SizedBox(height: 24.h),
Color(0xFFE4CF1D), Row(
Color(0xFFFFA11B), children: [
Color(0xFFFF5945), for (final item in _careLevelItems)
Color(0xFFFFFFFF), Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(item.level),
),
child: Text(
item.label,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
),
),
),
),
], ],
), ),
), ],
), ),
SizedBox(height: 24.h), );
Row( },
mainAxisAlignment: MainAxisAlignment.spaceBetween, );
}
}
class _CareLevelBar extends StatelessWidget {
const _CareLevelBar({required this.currentLevel});
final CareLevel currentLevel;
@override
Widget build(BuildContext context) {
final activeIndex = _careLevelItems.indexWhere(
(item) => item.level == currentLevel,
);
return LayoutBuilder(
builder: (context, constraints) {
final segmentGap = 4.w;
final knobSize = 54.w;
final segmentWidth =
(constraints.maxWidth - segmentGap * (_careLevelItems.length - 1)) /
_careLevelItems.length;
final knobLeft = (segmentWidth + segmentGap) * activeIndex +
segmentWidth -
knobSize / 2;
return SizedBox(
height: knobSize,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerLeft,
children: [ children: [
for (final item in const ['关闭', '三级', '二级', '一级', '特级']) Positioned(
Text( left: 0,
item, right: 0,
style: TextStyle(color: Colors.white, fontSize: 24.sp), top: (knobSize - 20.h) / 2,
child: Row(
children: [
for (var index = 0;
index < _careLevelItems.length;
index++) ...[
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () =>
context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(
_careLevelItems[index].level,
),
),
child: Container(
height: 20.h,
decoration: BoxDecoration(
color: index <= activeIndex
? _careLevelItems[index].color
: const Color(0xFF6A7481),
borderRadius: _segmentRadius(index),
boxShadow: index <= activeIndex
? [
BoxShadow(
color: _careLevelItems[index]
.color
.withValues(alpha: 0.35),
blurRadius: 8.r,
),
]
: null,
),
),
),
),
if (index < _careLevelItems.length - 1)
SizedBox(width: segmentGap),
],
],
),
),
Positioned(
left: knobLeft.clamp(0, constraints.maxWidth - knobSize),
child: Container(
width: knobSize,
height: knobSize,
decoration: BoxDecoration(
color: const Color(0xFFF7F7F7),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.white.withValues(alpha: 0.72),
blurRadius: 10.r,
),
],
),
), ),
),
], ],
), ),
], );
), },
); );
} }
BorderRadius _segmentRadius(int index) {
final radius = Radius.circular(12.r);
if (index == 0) {
return BorderRadius.horizontal(left: radius);
}
if (index == _careLevelItems.length - 1) {
return BorderRadius.horizontal(right: radius);
}
return BorderRadius.zero;
}
}
const _careLevelItems = [
_CareLevelItem('关闭', CareLevel.off, Color(0xFFE5F7FF)),
_CareLevelItem('三级', CareLevel.level3, Color(0xFF60F2B6)),
_CareLevelItem('二级', CareLevel.level2, Color(0xFFFFCE1B)),
_CareLevelItem('一级', CareLevel.level1, Color(0xFFFF8D2E)),
_CareLevelItem('特级', CareLevel.special, Color(0xFFFF554C)),
];
class _CareLevelItem {
const _CareLevelItem(this.label, this.level, this.color);
final String label;
final CareLevel level;
final Color color;
} }
...@@ -12,6 +12,8 @@ class DeviceControlInfoPanel extends StatelessWidget { ...@@ -12,6 +12,8 @@ class DeviceControlInfoPanel extends StatelessWidget {
required this.icon, required this.icon,
required this.body, required this.body,
this.warning, this.warning,
this.active = false,
this.onTap,
}); });
final String title; final String title;
...@@ -19,49 +21,61 @@ class DeviceControlInfoPanel extends StatelessWidget { ...@@ -19,49 +21,61 @@ class DeviceControlInfoPanel extends StatelessWidget {
final IconData icon; final IconData icon;
final String body; final String body;
final String? warning; final String? warning;
final bool active;
final VoidCallback? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DeviceControlPanelFrame( return GestureDetector(
backgroundAsset: assetPanelWide, behavior: HitTestBehavior.opaque,
padding: EdgeInsets.fromLTRB(30.w, 22.h, 30.w, 24.h), onTap: onTap,
child: Column( child: DeviceControlPanelFrame(
crossAxisAlignment: CrossAxisAlignment.start, backgroundAsset: assetPanelWide,
children: [ padding: EdgeInsets.fromLTRB(30.w, 22.h, 30.w, 24.h),
Row( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Expanded(child: DeviceControlPanelTitle(title)), children: [
DeviceControlPill(text: actionText), Row(
], children: [
), Expanded(child: DeviceControlPanelTitle(title)),
const Spacer(), DeviceControlPill(text: actionText, active: active),
Row( ],
children: [ ),
Icon(icon, color: const Color(0xFF6AC6F2), size: 54.w), const Spacer(),
SizedBox(width: 28.w), Row(
Expanded( children: [
child: Text( Icon(
body, icon,
style: TextStyle( color: active
color: const Color(0xFFC9F8FF), ? const Color(0xFF45F36F)
fontSize: 20.sp, : const Color(0xFF6AC6F2),
height: 1.35, size: 54.w,
),
SizedBox(width: 28.w),
Expanded(
child: Text(
body,
style: TextStyle(
color: const Color(0xFFC9F8FF),
fontSize: 20.sp,
height: 1.35,
),
), ),
), ),
],
),
if (warning != null) ...[
SizedBox(height: 8.h),
Text(
warning!,
style: TextStyle(
color: const Color(0xFFFFC400),
fontSize: 16.sp,
),
), ),
], ],
),
if (warning != null) ...[
SizedBox(height: 8.h),
Text(
warning!,
style: TextStyle(
color: const Color(0xFFFFC400),
fontSize: 16.sp,
),
),
], ],
], ),
), ),
); );
} }
......
...@@ -5,6 +5,7 @@ import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart'; ...@@ -5,6 +5,7 @@ import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart'; import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart'; import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart'; import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/numeric_keyboard.dart';
import 'device_control_common.dart'; import 'device_control_common.dart';
...@@ -64,6 +65,12 @@ class DeviceControlMetricCard extends StatelessWidget { ...@@ -64,6 +65,12 @@ class DeviceControlMetricCard extends StatelessWidget {
isOxygen: isOxygen, isOxygen: isOxygen,
isCo2: isCo2, isCo2: isCo2,
); );
final setValueEventBuilder = _setValueEventBuilder(
isTemperature: isTemperature,
isHumidity: isHumidity,
isOxygen: isOxygen,
isCo2: isCo2,
);
return DeviceControlPanelFrame( return DeviceControlPanelFrame(
active: isOxygen, active: isOxygen,
...@@ -147,15 +154,30 @@ class DeviceControlMetricCard extends StatelessWidget { ...@@ -147,15 +154,30 @@ class DeviceControlMetricCard extends StatelessWidget {
), ),
), ),
const Spacer(), const Spacer(),
Text( GestureDetector(
setValue, behavior: HitTestBehavior.opaque,
style: TextStyle( onTap: setValueEventBuilder == null
color: isCo2 ? null
? const Color(0xFFFFC400) : () => _showSetValueKeyboard(
: const Color(0xFF00B7F4), context,
fontSize: 64.sp, initialValue: setValue,
height: 1, decimalEnabled: isTemperature,
fontWeight: FontWeight.bold, eventBuilder: setValueEventBuilder,
),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 18.w, vertical: 8.h),
child: Text(
setValue,
style: TextStyle(
color: isCo2
? const Color(0xFFFFC400)
: const Color(0xFF00B7F4),
fontSize: 64.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
), ),
), ),
const Spacer(), const Spacer(),
...@@ -206,6 +228,46 @@ class DeviceControlMetricCard extends StatelessWidget { ...@@ -206,6 +228,46 @@ class DeviceControlMetricCard extends StatelessWidget {
} }
return null; return null;
} }
static DeviceControlEvent Function(String value)? _setValueEventBuilder({
required bool isTemperature,
required bool isHumidity,
required bool isOxygen,
required bool isCo2,
}) {
if (isTemperature) {
return (value) => DeviceControlCabinTempSetValueChanged(value);
}
if (isHumidity) {
return (value) => DeviceControlCabinHumiditySetValueChanged(value);
}
if (isOxygen) {
return (value) => DeviceControlOxygenConcentrationSetValueChanged(value);
}
if (isCo2) {
return (value) =>
DeviceControlCO2ConcentrationAlarmThresholdChanged(value);
}
return null;
}
static Future<void> _showSetValueKeyboard(
BuildContext context, {
required String initialValue,
required bool decimalEnabled,
required DeviceControlEvent Function(String value) eventBuilder,
}) async {
final result = await NumericKeyboard.show(
context,
initialValue: initialValue == '--' ? '' : initialValue,
decimalEnabled: decimalEnabled,
);
if (!context.mounted || result == null) return;
final value = result.trim();
if (value.isEmpty || value == '.') return;
context.read<DeviceControlBloc>().add(eventBuilder(value));
}
} }
class _MetricAdjustButton extends StatelessWidget { class _MetricAdjustButton extends StatelessWidget {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart'; import 'device_control_common.dart';
...@@ -9,33 +14,97 @@ class DeviceControlModePanel extends StatelessWidget { ...@@ -9,33 +14,97 @@ class DeviceControlModePanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const items = [ return BlocBuilder<DeviceControlBloc, DeviceControlState>(
_ModeItem(label: '外循环', icon: Icons.sync, active: true), buildWhen: (previous, current) =>
_ModeItem(label: '检查灯', icon: Icons.light), previous.data.circulationMode != current.data.circulationMode ||
_ModeItem(label: '照明灯', icon: Icons.lightbulb), previous.data.lightControl != current.data.lightControl,
_ModeItem(label: '内循环', icon: Icons.autorenew), builder: (context, state) {
_ModeItem(label: '蓝光', icon: Icons.wb_sunny_outlined), final circulationMode = state.data.circulationMode.currentMode;
_ModeItem(label: '红光', icon: Icons.wb_sunny), final lightControl = state.data.lightControl;
]; final items = [
_ModeItem(
return DeviceControlPanelFrame( label: '外循环',
backgroundAsset: assetPanelLarge, icon: Icons.sync,
padding: EdgeInsets.all(28.w), active: circulationMode == CirculationMode.external,
child: GridView.count( event: const DeviceControlCirculationModeChanged(
crossAxisCount: 3, CirculationMode.external,
crossAxisSpacing: 24.w,
mainAxisSpacing: 24.h,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1.1,
children: [
for (final item in items)
_ModeButton(
label: item.label,
icon: item.icon,
active: item.active,
), ),
], ),
), _ModeItem(
label: '检查灯',
icon: Icons.light,
active: lightControl.checkLightOn,
event: DeviceControlCheckLightSwitchChanged(
!lightControl.checkLightOn,
),
),
_ModeItem(
label: '照明灯',
icon: Icons.lightbulb,
active: lightControl.illuminationOn,
event: DeviceControlIlluminationLightSwitchChanged(
!lightControl.illuminationOn,
),
),
_ModeItem(
label: '内循环',
icon: Icons.autorenew,
active: circulationMode == CirculationMode.internal,
event: const DeviceControlCirculationModeChanged(
CirculationMode.internal,
),
),
_ModeItem(
label: '蓝光',
icon: Icons.wb_sunny_outlined,
active: lightControl.blueLightOn,
event: DeviceControlBlueLightSwitchChanged(
!lightControl.blueLightOn,
),
),
_ModeItem(
label: '红光',
icon: Icons.wb_sunny,
active: lightControl.redLightOn,
event: DeviceControlRedLightSwitchChanged(
!lightControl.redLightOn,
),
),
];
return DeviceControlPanelFrame(
backgroundAsset: assetPanelLarge,
padding: EdgeInsets.fromLTRB(30.w, 30.h, 30.w, 30.h),
child: LayoutBuilder(
builder: (context, constraints) {
final horizontalGap = 22.w;
final verticalGap = 22.h;
final itemWidth = (constraints.maxWidth - horizontalGap * 2) / 3;
final itemHeight = (constraints.maxHeight - verticalGap) / 2;
return Wrap(
spacing: horizontalGap,
runSpacing: verticalGap,
children: [
for (final item in items)
SizedBox(
width: itemWidth,
height: itemHeight,
child: _ModeButton(
label: item.label,
icon: item.icon,
active: item.active,
onTap: () => context.read<DeviceControlBloc>().add(
item.event,
),
),
),
],
);
},
),
);
},
); );
} }
} }
...@@ -45,37 +114,51 @@ class _ModeButton extends StatelessWidget { ...@@ -45,37 +114,51 @@ class _ModeButton extends StatelessWidget {
required this.label, required this.label,
required this.icon, required this.icon,
this.active = false, this.active = false,
this.onTap,
}); });
final String label; final String label;
final IconData icon; final IconData icon;
final bool active; final bool active;
final VoidCallback? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA); final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA);
return Container( return GestureDetector(
decoration: deviceControlButtonDecoration( behavior: HitTestBehavior.opaque,
active: active, onTap: onTap,
backgroundAsset: assetArrowButton, child: Container(
), decoration: deviceControlButtonDecoration(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 18.h), active: active,
child: Column( backgroundAsset: assetArrowButton,
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ padding: EdgeInsets.fromLTRB(12.w, 6.h, 12.w, 16.h),
Icon(icon, color: color, size: 56.w), child: Stack(
SizedBox(height: 20.h), alignment: Alignment.center,
Text( children: [
label, Positioned(
maxLines: 1, top: 28.h,
overflow: TextOverflow.ellipsis, child: Icon(icon, color: color, size: 100.w),
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w700,
), ),
), Positioned(
], left: 0,
right: 0,
bottom: 0,
child: Text(
label,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w700,
),
),
),
],
),
), ),
); );
} }
...@@ -85,10 +168,12 @@ class _ModeItem { ...@@ -85,10 +168,12 @@ class _ModeItem {
final String label; final String label;
final IconData icon; final IconData icon;
final bool active; final bool active;
final DeviceControlEvent event;
const _ModeItem({ const _ModeItem({
required this.label, required this.label,
required this.icon, required this.icon,
required this.event,
this.active = false, this.active = false,
}); });
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/utils/numeric_keyboard.dart';
import 'device_control_common.dart'; import 'device_control_common.dart';
...@@ -9,28 +14,97 @@ class DeviceControlTimePanel extends StatelessWidget { ...@@ -9,28 +14,97 @@ class DeviceControlTimePanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DeviceControlPanelFrame( return BlocBuilder<DeviceControlBloc, DeviceControlState>(
backgroundAsset: assetPanelTall, buildWhen: (previous, current) =>
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h), previous.data.nebulizationTime != current.data.nebulizationTime ||
child: Column( previous.data.disinfectionTime != current.data.disinfectionTime,
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, state) {
children: [ final nebulizationMinutes = state.data.nebulizationTime.setMinutes;
const DeviceControlPanelTitle('设置雾化时间'), final disinfectionMinutes = state.data.disinfectionTime.setMinutes;
SizedBox(height: 18.h),
const Expanded( return DeviceControlPanelFrame(
child: DeviceControlDigitalValueBox(value: '00', unit: '分钟'), backgroundAsset: assetPanelTall,
), padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
Divider(color: Colors.white.withValues(alpha: 0.16), height: 36.h), child: Column(
const DeviceControlPanelTitle('设置消毒时间'), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(height: 18.h), children: [
const Expanded( const DeviceControlPanelTitle('设置雾化时间'),
child: DeviceControlDigitalValueBox( SizedBox(height: 18.h),
value: '14', Expanded(
unit: '分钟', child: _TimeValueButton(
active: true, value: nebulizationMinutes,
), active: false,
onTap: () => _showTimeKeyboard(
context,
initialValue: nebulizationMinutes,
eventBuilder: (value) =>
DeviceControlNebulizationTimeChanged(value),
),
),
),
Divider(
color: Colors.white.withValues(alpha: 0.16),
height: 36.h,
),
const DeviceControlPanelTitle('设置消毒时间'),
SizedBox(height: 18.h),
Expanded(
child: _TimeValueButton(
value: disinfectionMinutes,
active: true,
onTap: () => _showTimeKeyboard(
context,
initialValue: disinfectionMinutes,
eventBuilder: (value) =>
DeviceControlDisinfectionTimeChanged(value),
),
),
),
],
), ),
], );
},
);
}
static Future<void> _showTimeKeyboard(
BuildContext context, {
required String initialValue,
required DeviceControlEvent Function(String value) eventBuilder,
}) async {
final result = await NumericKeyboard.show(
context,
initialValue: initialValue == '--' ? '' : initialValue,
decimalEnabled: false,
);
if (!context.mounted || result == null) return;
final value = result.trim();
if (value.isEmpty) return;
context.read<DeviceControlBloc>().add(eventBuilder(value));
}
}
class _TimeValueButton extends StatelessWidget {
const _TimeValueButton({
required this.value,
required this.active,
required this.onTap,
});
final String value;
final bool active;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: DeviceControlDigitalValueBox(
value: value,
unit: '分钟',
active: active,
), ),
); );
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart'; import 'device_control_common.dart';
...@@ -9,33 +14,62 @@ class DeviceControlWindPanel extends StatelessWidget { ...@@ -9,33 +14,62 @@ class DeviceControlWindPanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DeviceControlPanelFrame( return BlocBuilder<DeviceControlBloc, DeviceControlState>(
backgroundAsset: assetPanelTall, buildWhen: (previous, current) =>
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h), previous.data.windSpeed != current.data.windSpeed,
child: Column( builder: (context, state) {
crossAxisAlignment: CrossAxisAlignment.start, final currentMode = state.data.windSpeed.currentMode;
children: [
const DeviceControlPanelTitle('风速调节'), return DeviceControlPanelFrame(
SizedBox(height: 24.h), backgroundAsset: assetPanelTall,
const Expanded( padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: _WideModeButton( child: Column(
label: '睡眠模式', crossAxisAlignment: CrossAxisAlignment.start,
icon: Icons.nightlight_round, children: [
), const DeviceControlPanelTitle('风速调节'),
), SizedBox(height: 24.h),
SizedBox(height: 18.h), Expanded(
const Expanded( child: _WideModeButton(
child: _WideModeButton( label: '睡眠模式',
label: '舒适模式', icon: Icons.nightlight_round,
icon: Icons.favorite_border, active: currentMode == WindSpeedMode.sleep,
), onTap: () => context.read<DeviceControlBloc>().add(
), const DeviceControlWindSpeedModeChanged(
SizedBox(height: 18.h), WindSpeedMode.sleep,
const Expanded( ),
child: _WideModeButton(label: '强劲模式', icon: Icons.air), ),
),
),
SizedBox(height: 18.h),
Expanded(
child: _WideModeButton(
label: '舒适模式',
icon: Icons.favorite_border,
active: currentMode == WindSpeedMode.comfort,
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.comfort,
),
),
),
),
SizedBox(height: 18.h),
Expanded(
child: _WideModeButton(
label: '强劲模式',
icon: Icons.air,
active: currentMode == WindSpeedMode.strong,
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.strong,
),
),
),
),
],
), ),
], );
), },
); );
} }
} }
...@@ -44,34 +78,46 @@ class _WideModeButton extends StatelessWidget { ...@@ -44,34 +78,46 @@ class _WideModeButton extends StatelessWidget {
const _WideModeButton({ const _WideModeButton({
required this.label, required this.label,
required this.icon, required this.icon,
this.active = false,
this.onTap,
}); });
final String label; final String label;
final IconData icon; final IconData icon;
final bool active;
final VoidCallback? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA);
width: double.infinity, return GestureDetector(
decoration: deviceControlButtonDecoration(backgroundAsset: assetButton), behavior: HitTestBehavior.opaque,
padding: EdgeInsets.symmetric(horizontal: 28.w), onTap: onTap,
child: Row( child: Container(
children: [ width: double.infinity,
Icon(icon, color: const Color(0xFF7DC4EA), size: 52.w), decoration: deviceControlButtonDecoration(
SizedBox(width: 34.w), active: active,
Expanded( backgroundAsset: assetButton,
child: Text( ),
label, padding: EdgeInsets.symmetric(horizontal: 28.w),
maxLines: 1, child: Row(
overflow: TextOverflow.ellipsis, children: [
style: TextStyle( Icon(icon, color: color, size: 52.w),
color: Colors.white, SizedBox(width: 34.w),
fontSize: 26.sp, Expanded(
fontWeight: FontWeight.w600, child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w600,
),
), ),
), ),
), ],
], ),
), ),
); );
} }
......
...@@ -36,6 +36,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -36,6 +36,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
StreamSubscription<BluetoothConnectionStatus>? _bluetoothStateSub; StreamSubscription<BluetoothConnectionStatus>? _bluetoothStateSub;
StreamSubscription<String>? _bluetoothMessageSub; StreamSubscription<String>? _bluetoothMessageSub;
StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub; StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub;
StreamSubscription<BluetoothReadInfoChangedEvent>? _bluetoothReadInfoSub;
StreamSubscription<McuReportChangedEvent>? _mcuReportChangedSub; StreamSubscription<McuReportChangedEvent>? _mcuReportChangedSub;
StreamSubscription<LakiMqttConnectionState>? _mqttStateSub; StreamSubscription<LakiMqttConnectionState>? _mqttStateSub;
StreamSubscription<LakiMqttMessage>? _mqttMessageSub; StreamSubscription<LakiMqttMessage>? _mqttMessageSub;
...@@ -69,6 +70,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -69,6 +70,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_listenWebrtcState(); _listenWebrtcState();
_listenP2pState(); _listenP2pState();
_listenBluetoothState(); _listenBluetoothState();
_listenBluetoothReadInfo();
_listenMcuReportChanged(); _listenMcuReportChanged();
_loadBoundBluetoothDevice(); _loadBoundBluetoothDevice();
loadData(); loadData();
...@@ -178,13 +180,40 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -178,13 +180,40 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
void _listenMcuReportChanged() { void _listenMcuReportChanged() {
_mcuReportChangedSub = eventBus.on<McuReportChangedEvent>().listen((event) { _mcuReportChangedSub = eventBus.on<McuReportChangedEvent>().listen((event) {
if (isClosed) return; if (isClosed) return;
final metrics = _metricsFromBluetoothReport(event.report);
Log.warn(
'🔥🔥🔥 BLE_READ_DEBUG index_metrics_from_report '
'${_metricsLogText(metrics)}',
);
emit(state.copyWith( emit(state.copyWith(
latestMcuReport: event.report, latestMcuReport: event.report,
metrics: _metricsFromBluetoothReport(event.report), metrics: metrics,
)); ));
}); });
} }
void _listenBluetoothReadInfo() {
_bluetoothReadInfoSub =
eventBus.on<BluetoothReadInfoChangedEvent>().listen((event) {
if (isClosed) return;
final metrics = _metricsFromBluetoothReadInfo(event.info);
if (metrics == null) {
Log.warn(
'🔥🔥🔥 BLE_READ_DEBUG index_read_info_no_metrics '
'sn=${event.info.sn} temp=${event.info.mainTemperature} '
'humidity=${event.info.humidity} o2=${event.info.oxygenConcentration} '
'co2=${event.info.co2Measurement}',
);
return;
}
Log.warn(
'🔥🔥🔥 BLE_READ_DEBUG index_metrics_from_read_info '
'${_metricsLogText(metrics)}',
);
emit(state.copyWith(metrics: metrics));
});
}
String _appendBluetoothSideToSn(String sn) { String _appendBluetoothSideToSn(String sn) {
final cleanSn = sn.trim(); final cleanSn = sn.trim();
if (cleanSn.isEmpty) return ''; if (cleanSn.isEmpty) return '';
...@@ -241,8 +270,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -241,8 +270,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
)); ));
try { try {
final metrics = await _monitoringService.getMetrics(); // final metrics = await _monitoringService.getMetrics();
if (isClosed) return; // if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo(); final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return; if (isClosed) return;
final alerts = await _monitoringService.getAlerts(); final alerts = await _monitoringService.getAlerts();
...@@ -251,7 +280,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -251,7 +280,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
emit(state.copyWith( emit(state.copyWith(
status: MonitoringIndexStatus.success, status: MonitoringIndexStatus.success,
isLoading: false, isLoading: false,
metrics: metrics,
patientInfo: patientInfo, patientInfo: patientInfo,
alerts: alerts, alerts: alerts,
error: null, error: null,
...@@ -430,8 +458,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -430,8 +458,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
Future<void> refreshData() async { Future<void> refreshData() async {
try { try {
final metrics = await _monitoringService.getMetrics(); // final metrics = await _monitoringService.getMetrics();
if (isClosed) return; // if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo(); final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return; if (isClosed) return;
final alerts = await _monitoringService.getAlerts(); final alerts = await _monitoringService.getAlerts();
...@@ -439,7 +467,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -439,7 +467,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
emit(state.copyWith( emit(state.copyWith(
status: MonitoringIndexStatus.success, status: MonitoringIndexStatus.success,
metrics: metrics,
patientInfo: patientInfo, patientInfo: patientInfo,
alerts: alerts, alerts: alerts,
error: null, error: null,
...@@ -750,36 +777,98 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -750,36 +777,98 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mqttClient = null; _mqttClient = null;
} }
List<MonitoringMetricBO> _metricsFromBluetoothReport(McuReport report) { List<MonitoringMetricBO> _metricsFromBluetoothReport(
McuReport report, {
List<MonitoringMetricBO>? baseMetrics,
}) {
final metrics = baseMetrics ?? state.metrics;
final replacements = <String, MonitoringMetricBO>{
'舱内温度': _replaceMetricValue(
'舱内温度',
_formatReportTemperatureOrKeep(report.mainTemperature, '舱内温度'),
fallbackUnit: '℃',
baseMetrics: metrics,
),
'舱内湿度': _replaceMetricValue(
'舱内湿度',
_formatReportIntOrKeep(report.humidity, '舱内湿度'),
fallbackUnit: 'RH',
baseMetrics: metrics,
),
'氧气浓度': _replaceMetricValue(
'氧气浓度',
_formatReportIntOrKeep(report.oxygenConcentration, '氧气浓度'),
fallbackUnit: '%',
baseMetrics: metrics,
),
'二氧化碳': _replaceMetricValue(
'二氧化碳',
_formatReportIntOrKeep(report.co2Measurement, '二氧化碳'),
fallbackUnit: 'ppm',
baseMetrics: metrics,
),
};
if (metrics.isEmpty) {
return replacements.values.toList();
}
return [
for (final metric in metrics) replacements[metric.label] ?? metric,
];
}
List<MonitoringMetricBO>? _metricsFromBluetoothReadInfo(
BluetoothReadModel info,
) {
final hasMetric = info.mainTemperature != null ||
info.humidity != null ||
info.oxygenConcentration != null ||
info.co2Measurement != null;
if (!hasMetric) return null;
final metrics = state.metrics;
final replacements = <String, MonitoringMetricBO>{ final replacements = <String, MonitoringMetricBO>{
'舱内温度': _replaceMetricValue( '舱内温度': _replaceMetricValue(
'舱内温度', '舱内温度',
_formatReportTemperature(report.mainTemperature), info.mainTemperature == null
? _existingMetricValue('舱内温度') ?? '--'
: _formatReportTemperature(info.mainTemperature!),
fallbackUnit: '℃', fallbackUnit: '℃',
baseMetrics: metrics,
), ),
'舱内湿度': _replaceMetricValue( '舱内湿度': _replaceMetricValue(
'舱内湿度', '舱内湿度',
_formatReportInt(report.humidity), info.humidity == null
? _existingMetricValue('舱内湿度') ?? '--'
: '${info.humidity}',
fallbackUnit: 'RH', fallbackUnit: 'RH',
baseMetrics: metrics,
), ),
'氧气浓度': _replaceMetricValue( '氧气浓度': _replaceMetricValue(
'氧气浓度', '氧气浓度',
_formatReportInt(report.oxygenConcentration), info.oxygenConcentration == null
? _existingMetricValue('氧气浓度') ?? '--'
: '${info.oxygenConcentration}',
fallbackUnit: '%', fallbackUnit: '%',
baseMetrics: metrics,
), ),
'二氧化碳': _replaceMetricValue( '二氧化碳': _replaceMetricValue(
'二氧化碳', '二氧化碳',
_formatReportInt(report.co2Measurement), info.co2Measurement == null
? _existingMetricValue('二氧化碳') ?? '--'
: '${info.co2Measurement}',
fallbackUnit: 'ppm', fallbackUnit: 'ppm',
baseMetrics: metrics,
), ),
}; };
if (state.metrics.isEmpty) { if (metrics.isEmpty) {
return replacements.values.toList(); return replacements.values.toList();
} }
return [ return [
for (final metric in state.metrics) replacements[metric.label] ?? metric, for (final metric in metrics) replacements[metric.label] ?? metric,
]; ];
} }
...@@ -787,9 +876,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -787,9 +876,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
String label, String label,
String value, { String value, {
required String fallbackUnit, required String fallbackUnit,
List<MonitoringMetricBO>? baseMetrics,
}) { }) {
final metrics = baseMetrics ?? state.metrics;
MonitoringMetricBO? existing; MonitoringMetricBO? existing;
for (final metric in state.metrics) { for (final metric in metrics) {
if (metric.label == label) { if (metric.label == label) {
existing = metric; existing = metric;
break; break;
...@@ -817,8 +908,29 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -817,8 +908,29 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
return fixed.endsWith('.0') ? fixed.substring(0, fixed.length - 2) : fixed; return fixed.endsWith('.0') ? fixed.substring(0, fixed.length - 2) : fixed;
} }
String _formatReportInt(int value) { String _formatReportTemperatureOrKeep(double value, String label) {
return value < 0 ? '--' : '$value'; if (!value.isNaN) return _formatReportTemperature(value);
return _existingMetricValue(label) ?? '--';
}
String _formatReportIntOrKeep(int value, String label) {
if (value >= 0) return '$value';
return _existingMetricValue(label) ?? '--';
}
String? _existingMetricValue(String label) {
for (final metric in state.metrics) {
if (metric.label == label && metric.value != '--') {
return metric.value;
}
}
return null;
}
String _metricsLogText(List<MonitoringMetricBO> metrics) {
return metrics
.map((item) => '${item.label}=${item.value}${item.unit}')
.join(' ');
} }
/// 清空宠物信息(出舱) /// 清空宠物信息(出舱)
...@@ -907,6 +1019,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -907,6 +1019,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_bluetoothStateSub?.cancel(); _bluetoothStateSub?.cancel();
_bluetoothMessageSub?.cancel(); _bluetoothMessageSub?.cancel();
_bluetoothDataSub?.cancel(); _bluetoothDataSub?.cancel();
_bluetoothReadInfoSub?.cancel();
_mcuReportChangedSub?.cancel(); _mcuReportChangedSub?.cancel();
_webrtcService.dispose(); _webrtcService.dispose();
_p2pVideoService.dispose(); _p2pVideoService.dispose();
......
...@@ -120,7 +120,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> { ...@@ -120,7 +120,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
children: [ children: [
Expanded( Expanded(
flex: 32, flex: 32,
child: state.metrics.isEmpty ? _buildMetricsEmpty() : _buildMetrics(state.metrics), child: _buildMetrics(state.metrics),
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
Expanded( Expanded(
...@@ -157,6 +157,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> { ...@@ -157,6 +157,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
); );
} }
// ignore: unused_element
Widget _buildMetricsEmpty() { Widget _buildMetricsEmpty() {
return Container( return Container(
width: double.infinity, width: double.infinity,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment