Commit 72cbd0e5 authored by 张宏's avatar 张宏

Merge branch 'master' of http://git.ruanyiit.com/zhanghong/laki_icu_app

# Conflicts: # lib/utils/event_bus.dart # lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
parents eec1450c 43d33868
......@@ -122,6 +122,9 @@ class DeviceControlBloc
final BleBluetoothManager _bluetoothManager;
final StorageService _storageService;
final McuReportFrameDecoder _mcuReportDecoder;
static const Duration _careLevelPendingTimeout = Duration(seconds: 5);
int? _pendingCareLevelProtocolValue;
DateTime? _pendingCareLevelSince;
late final StreamSubscription<McuReportChangedEvent> _mcuReportSub;
late final StreamSubscription<BluetoothDataPacket> _bluetoothDataSub;
......@@ -220,6 +223,7 @@ class DeviceControlBloc
'co2Set=${report.co2Setting} '
'sn=${report.sn.isEmpty ? '<EMPTY>' : report.sn}',
);
final reportCareLevel = _careLevelFromReportValue(report.levelLight);
final newData = state.data.copyWith(
sn: report.sn.isEmpty ? state.data.sn : report.sn,
cabinTemp: state.data.cabinTemp.copyWith(
......@@ -250,6 +254,11 @@ class DeviceControlBloc
state.data.co2Concentration.alarmThreshold,
),
),
careLevel: reportCareLevel == null
? state.data.careLevel
: state.data.careLevel.copyWith(
currentLevel: reportCareLevel,
),
);
emit(state.copyWith(data: newData, clearError: true));
......@@ -683,6 +692,8 @@ class DeviceControlBloc
DeviceControlCareLevelChanged event,
Emitter<DeviceControlState> emit,
) async {
final protocolValue = _careLevelProtocolValue(event.level);
_markCareLevelPending(protocolValue);
final newData = state.data.copyWith(
careLevel: state.data.careLevel.copyWith(
currentLevel: event.level,
......@@ -695,10 +706,11 @@ class DeviceControlBloc
));
await _writeControlValueToBluetooth(
identifier: 0xA8,
setValue: '${_careLevelProtocolValue(event.level)}',
setValue: '$protocolValue',
errorPrefix: '护理等级',
emit: emit,
);
eventBus.emit(CareLevelChangedEvent(protocolValue));
}
// ==================== 新风进化控制事件处理 ====================
......@@ -1110,6 +1122,52 @@ class DeviceControlBloc
}
}
CareLevel? _careLevelFromProtocolValue(int value) {
switch (value) {
case 0:
return CareLevel.off;
case 1:
return CareLevel.level3;
case 2:
return CareLevel.level2;
case 3:
return CareLevel.level1;
case 4:
return CareLevel.special;
}
return null;
}
CareLevel? _careLevelFromReportValue(int value) {
final pendingValue = _pendingCareLevelProtocolValue;
final pendingSince = _pendingCareLevelSince;
if (pendingValue != null && pendingSince != null) {
final isPendingFresh =
DateTime.now().difference(pendingSince) < _careLevelPendingTimeout;
if (isPendingFresh && value != pendingValue) {
Log.warn(
'🔥🔥🔥 BLE_READ_DEBUG care_level_report_ignored '
'pending=$pendingValue report=$value',
);
return null;
}
if (value == pendingValue || !isPendingFresh) {
_clearPendingCareLevel();
}
}
return _careLevelFromProtocolValue(value);
}
void _markCareLevelPending(int protocolValue) {
_pendingCareLevelProtocolValue = protocolValue;
_pendingCareLevelSince = DateTime.now();
}
void _clearPendingCareLevel() {
_pendingCareLevelProtocolValue = null;
_pendingCareLevelSince = null;
}
List<int>? _buildControlValueCommand({
required int identifier,
required String setValue,
......
......@@ -102,9 +102,42 @@ class PatientInfoBO extends Equatable {
);
}
PatientInfoBO copyWith({
String? name,
String? type,
String? phone,
String? breed,
String? doctor,
String? disease,
String? checkInDate,
String? careLevel,
String? avatarUrl,
}) {
return PatientInfoBO(
name: name ?? this.name,
type: type ?? this.type,
phone: phone ?? this.phone,
breed: breed ?? this.breed,
doctor: doctor ?? this.doctor,
disease: disease ?? this.disease,
checkInDate: checkInDate ?? this.checkInDate,
careLevel: careLevel ?? this.careLevel,
avatarUrl: avatarUrl ?? this.avatarUrl,
);
}
@override
List<Object?> get props =>
[name, type, phone, breed, doctor, disease, checkInDate, careLevel, avatarUrl];
List<Object?> get props => [
name,
type,
phone,
breed,
doctor,
disease,
checkInDate,
careLevel,
avatarUrl
];
}
/// 用途:首页监护舱告警信息展示数据
......
......@@ -58,3 +58,10 @@ class DeviceSensorAlarmEvent {
const DeviceSensorAlarmEvent({required this.sn, this.alarmInfo});
}
// 护理等级更新事件
class CareLevelChangedEvent {
final int protocolValue;
const CareLevelChangedEvent(this.protocolValue);
}
......@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import 'package:laki_icu_app/models/bo/cabin_detail_response.dart';
import 'package:laki_icu_app/utils/date_utils.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
......@@ -38,7 +39,11 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub;
StreamSubscription<BluetoothReadInfoChangedEvent>? _bluetoothReadInfoSub;
StreamSubscription<McuReportChangedEvent>? _mcuReportChangedSub;
<<<<<<< HEAD
StreamSubscription<DeviceSensorAlarmEvent>? _deviceAlarmSub;
=======
StreamSubscription<CareLevelChangedEvent>? _careLevelChangedSub;
>>>>>>> 43d33868106a647f3523e8dbd11674431c5cdc53
StreamSubscription<LakiMqttConnectionState>? _mqttStateSub;
StreamSubscription<LakiMqttMessage>? _mqttMessageSub;
StreamSubscription<String>? _mqttErrorSub;
......@@ -62,6 +67,9 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
/// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。
int _switchGen = 0;
static const Duration _careLevelPendingTimeout = Duration(seconds: 5);
int? _pendingCareLevelProtocolValue;
DateTime? _pendingCareLevelSince;
MonitoringIndexCubit()
: _cabinService = CabinService(),
......@@ -76,7 +84,11 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
_listenBluetoothState();
_listenBluetoothReadInfo();
_listenMcuReportChanged();
<<<<<<< HEAD
_listenDeviceSensorAlarm();
=======
_listenCareLevelChanged();
>>>>>>> 43d33868106a647f3523e8dbd11674431c5cdc53
_loadBoundBluetoothDevice();
// // todo 测试使用 待删除
......@@ -157,7 +169,8 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
).copyWith(sn: reportSn.isEmpty ? null : reportSn);
if (bluetoothReadInfo.hasSn) {
await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!);
await _connectMqttForDeviceSn(bluetoothReadInfo.sn!); // stephen 需要保留,选择蓝牙之后获取到sn就需要连接上mqtt
await _connectMqttForDeviceSn(
bluetoothReadInfo.sn!); // stephen 需要保留,选择蓝牙之后获取到sn就需要连接上mqtt
await _fetchCabinDetailAndConnectVideo(bluetoothReadInfo.sn!);
}
eventBus.emit(
......@@ -166,9 +179,14 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
_publishMqttMonitorReport(latestReport);
if (isClosed) return;
}
final careLevel = latestReport == null
? null
: _careLevelTextFromReportValue(latestReport.levelLight);
emit(state.copyWith(
latestBluetoothRawHex: packet.rawHex,
latestMcuReport: latestReport,
patientInfo: _patientInfoWithCareLevelText(careLevel),
currentCareLevel: careLevel,
metrics: latestReport == null
? state.metrics
: _metricsFromBluetoothReport(latestReport),
......@@ -193,9 +211,28 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
'🔥🔥🔥 BLE_READ_DEBUG index_metrics_from_report '
'${_metricsLogText(metrics)}',
);
final careLevel = _careLevelTextFromReportValue(event.report.levelLight);
emit(state.copyWith(
latestMcuReport: event.report,
metrics: metrics,
patientInfo: _patientInfoWithCareLevelText(careLevel),
currentCareLevel: careLevel,
));
});
}
void _listenCareLevelChanged() {
_careLevelChangedSub = eventBus.on<CareLevelChangedEvent>().listen((event) {
if (isClosed) return;
_markCareLevelPending(event.protocolValue);
final careLevel = _careLevelTextFromProtocolValue(event.protocolValue);
final patientInfo = _patientInfoWithCareLevelValue(
event.protocolValue,
fromReport: false,
);
emit(state.copyWith(
patientInfo: patientInfo,
currentCareLevel: careLevel,
));
});
}
......@@ -383,11 +420,18 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
try {
final detail = await _cabinService.getCabinDetail(carbinSn);
if (isClosed) return;
final careLevel = _careLevelFromCabinDetail(detail);
// 仅从舱详情接口获取 cameraSn / wifiPwd,宠物信息改由 MQTT pet/bind 推送
emit(state.copyWith(
cabinCameraSn: detail.cameraSn,
cabinWifiPwd: detail.wifiPwd,
currentCareLevel: careLevel,
patientInfo: detail.petInfo != null
? _mapPetInfoToPatientInfo(
detail.petInfo!,
careLevel: careLevel,
)
: state.patientInfo,
));
// 凭证就绪后发起视频连接
......@@ -400,6 +444,111 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
}
}
/// 将 [PetInfoResponse] 映射为 [PatientInfoBO]
PatientInfoBO _mapPetInfoToPatientInfo(
PetInfoResponse pet, {
String? careLevel,
}) {
return PatientInfoBO(
name: (pet.petName ?? pet.name) ?? '',
type: (pet.petType ?? pet.type) ?? '',
phone: pet.ownerPhone ?? '',
breed: pet.breedTag ?? '',
doctor: '缺失字段',
avatarUrl: pet.avatar ?? '',
disease: pet.disease ?? '',
checkInDate: '缺失字段',
careLevel: careLevel ?? '缺失字段',
);
}
String? _careLevelFromCabinDetail(CabinDetailResponse detail) {
final careLevel = detail.monitoringData?.careLevel?.trim();
if (careLevel != null && careLevel.isNotEmpty) {
return careLevel;
}
return _careLevelTextFromProtocolValue(detail.monitoringData?.levelLight);
}
PatientInfoBO? _patientInfoWithCareLevelValue(
int value, {
bool fromReport = true,
}) {
final current = state.patientInfo;
if (current == null) return null;
final resolvedValue =
fromReport ? _resolveCareLevelReportValue(value) : value;
if (resolvedValue == null) return current;
final careLevel = _careLevelTextFromProtocolValue(resolvedValue);
if (careLevel == null || careLevel == current.careLevel) {
return current;
}
return current.copyWith(careLevel: careLevel);
}
PatientInfoBO? _patientInfoWithCareLevelText(String? careLevel) {
final current = state.patientInfo;
if (current == null) return null;
if (careLevel == null || careLevel == current.careLevel) {
return current;
}
return current.copyWith(careLevel: careLevel);
}
String? _careLevelTextFromReportValue(int value) {
final resolvedValue = _resolveCareLevelReportValue(value);
if (resolvedValue == null) return null;
return _careLevelTextFromProtocolValue(resolvedValue);
}
int? _resolveCareLevelReportValue(int value) {
final pendingValue = _pendingCareLevelProtocolValue;
final pendingSince = _pendingCareLevelSince;
if (pendingValue != null && pendingSince != null) {
final isPendingFresh =
DateTime.now().difference(pendingSince) < _careLevelPendingTimeout;
if (isPendingFresh && value != pendingValue) {
Log.warn(
'🔥🔥🔥 BLE_READ_DEBUG index_care_level_report_ignored '
'pending=$pendingValue report=$value',
);
return null;
}
if (value == pendingValue || !isPendingFresh) {
_clearPendingCareLevel();
}
}
return value;
}
void _markCareLevelPending(int protocolValue) {
_pendingCareLevelProtocolValue = protocolValue;
_pendingCareLevelSince = DateTime.now();
}
void _clearPendingCareLevel() {
_pendingCareLevelProtocolValue = null;
_pendingCareLevelSince = null;
}
String? _careLevelTextFromProtocolValue(int? value) {
switch (value) {
case 0:
return '关闭';
case 1:
return '三级';
case 2:
return '二级';
case 3:
return '一级';
case 4:
return '特级';
}
return null;
}
// ==================== 模式切换 ====================
/// 切换视频传输模式
......@@ -818,7 +967,8 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
doctor: '缺失字段',
avatarUrl: (data['avatar'] as String?) ?? '',
disease: (data['disease'] as String?) ?? '',
checkInDate: formatIsoTime(data['createTime'] as String?, fallback: '缺失字段'),
checkInDate:
formatIsoTime(data['createTime'] as String?, fallback: '缺失字段'),
careLevel: '缺失字段',
);
......@@ -1056,16 +1206,21 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
final deviceSn = state.mqttDeviceSn;
final client = _mqttClient;
debugPrint('[出舱] deviceSn=$deviceSn mqttClient=${client != null} isConnected=${client?.isConnected ?? false} patientInfo=${state.patientInfo?.name}');
debugPrint(
'[出舱] deviceSn=$deviceSn mqttClient=${client != null} isConnected=${client?.isConnected ?? false} patientInfo=${state.patientInfo?.name}');
if (deviceSn != null && deviceSn.isNotEmpty && client != null && client.isConnected) {
if (deviceSn != null &&
deviceSn.isNotEmpty &&
client != null &&
client.isConnected) {
try {
final msgId = client.publishPetUnBind(
deviceSn: deviceSn,
type: 'unbindPet', // TODO: 出舱 type 后续等服务端确定后再调整
petData: _patientInfoToMap(),
);
debugPrint('[出舱] MQTT publishPetUnBind 成功 msgId=$msgId topic=icu/$deviceSn/pet/bind');
debugPrint(
'[出舱] MQTT publishPetUnBind 成功 msgId=$msgId topic=icu/$deviceSn/pet/bind');
} catch (e) {
debugPrint('[出舱] MQTT publishPetUnBind 失败: $e');
if (!isClosed) {
......@@ -1142,6 +1297,7 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
/// 摄像头告警:通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。
void markAlertAsProcessed(AlertInfoBO alert) {
if (isClosed) return;
<<<<<<< HEAD
if (alert.isDeviceAlarm) {
// 设备告警:不上行 MQTT,仅移除
final removedAlerts =
......@@ -1163,6 +1319,17 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
clearSelectedAlert: true,
));
}
=======
_publishCameraAlarmResult(alert, 'handled');
final removedCameraAlerts =
state.cameraAlerts.where((item) => item != alert).toList();
final removedAlerts = state.alerts.where((item) => item != alert).toList();
emit(state.copyWith(
cameraAlerts: removedCameraAlerts,
alerts: removedAlerts,
selectedAlert: null,
));
>>>>>>> 43d33868106a647f3523e8dbd11674431c5cdc53
}
/// 标记告警为误报
......@@ -1171,6 +1338,7 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
/// 摄像头告警:通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。
void markAlertAsFalse(AlertInfoBO alert) {
if (isClosed) return;
<<<<<<< HEAD
if (alert.isDeviceAlarm) {
// 设备告警:不上行 MQTT,仅移除
final removedAlerts =
......@@ -1192,13 +1360,27 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
clearSelectedAlert: true,
));
}
=======
_publishCameraAlarmResult(alert, 'false_alarm');
final removedCameraAlerts =
state.cameraAlerts.where((item) => item != alert).toList();
final removedAlerts = state.alerts.where((item) => item != alert).toList();
emit(state.copyWith(
cameraAlerts: removedCameraAlerts,
alerts: removedAlerts,
selectedAlert: null,
));
>>>>>>> 43d33868106a647f3523e8dbd11674431c5cdc53
}
/// 上行 MQTT camera/{SN}/up 告警处理结果
void _publishCameraAlarmResult(AlertInfoBO alert, String status) {
final client = _mqttClient;
final deviceSn = _mqttDeviceSn;
if (client == null || !client.isConnected || deviceSn == null || deviceSn.isEmpty) return;
if (client == null ||
!client.isConnected ||
deviceSn == null ||
deviceSn.isEmpty) return;
if (alert.alarmId == null) return;
try {
......@@ -1226,7 +1408,11 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
_bluetoothDataSub?.cancel();
_bluetoothReadInfoSub?.cancel();
_mcuReportChangedSub?.cancel();
<<<<<<< HEAD
_deviceAlarmSub?.cancel();
=======
_careLevelChangedSub?.cancel();
>>>>>>> 43d33868106a647f3523e8dbd11674431c5cdc53
_webrtcService.dispose();
_p2pVideoService.dispose();
return _disposeMqttClient().then((_) => super.close());
......
......@@ -18,6 +18,7 @@ class MonitoringIndexState extends Equatable {
final String? error;
final List<MonitoringMetricBO> metrics;
final PatientInfoBO? patientInfo;
final String? currentCareLevel;
final List<AlertInfoBO> alerts;
/// WebRTC 视频连接状态
......@@ -89,6 +90,7 @@ class MonitoringIndexState extends Equatable {
this.error,
this.metrics = const [],
this.patientInfo,
this.currentCareLevel,
this.alerts = const [],
this.cameraAlerts = const [],
this.videoConnectionState = WebrtcConnectionState.disconnected,
......@@ -119,6 +121,7 @@ class MonitoringIndexState extends Equatable {
String? error,
List<MonitoringMetricBO>? metrics,
PatientInfoBO? patientInfo,
String? currentCareLevel,
List<AlertInfoBO>? alerts,
List<AlertInfoBO>? cameraAlerts,
WebrtcConnectionState? videoConnectionState,
......@@ -144,6 +147,7 @@ class MonitoringIndexState extends Equatable {
bool clearBoundBluetoothDevice = false,
bool clearLatestMcuReport = false,
bool clearPatientInfo = false,
bool clearCurrentCareLevel = false,
bool clearMqttDeviceSn = false,
bool clearSelectedAlert = false,
}) {
......@@ -153,6 +157,9 @@ class MonitoringIndexState extends Equatable {
error: error ?? this.error,
metrics: metrics ?? this.metrics,
patientInfo: clearPatientInfo ? null : patientInfo ?? this.patientInfo,
currentCareLevel: clearCurrentCareLevel
? null
: currentCareLevel ?? this.currentCareLevel,
alerts: alerts ?? this.alerts,
cameraAlerts: cameraAlerts ?? this.cameraAlerts,
videoConnectionState: videoConnectionState ?? this.videoConnectionState,
......@@ -183,9 +190,8 @@ class MonitoringIndexState extends Equatable {
clearMqttDeviceSn ? null : mqttDeviceSn ?? this.mqttDeviceSn,
cabinCameraSn: cabinCameraSn ?? this.cabinCameraSn,
cabinWifiPwd: cabinWifiPwd ?? this.cabinWifiPwd,
selectedAlert: clearSelectedAlert
? null
: selectedAlert ?? this.selectedAlert,
selectedAlert:
clearSelectedAlert ? null : selectedAlert ?? this.selectedAlert,
);
}
......@@ -218,6 +224,7 @@ class MonitoringIndexState extends Equatable {
error,
metrics,
patientInfo,
currentCareLevel,
alerts,
cameraAlerts,
videoConnectionState,
......
......@@ -8,7 +8,6 @@ import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/event_bus.dart';
import 'cubit/monitoring_index_cubit.dart';
import 'cubit/monitoring_index_state.dart';
import 'widgets/bluetooth_bind_dialog.dart';
......@@ -41,6 +40,7 @@ class MonitoringIndexContent extends StatefulWidget {
class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
late final MonitoringIndexCubit _monitoringIndexCubit;
StreamSubscription<OpenBluetoothScanEvent>? _bluetoothScanEventSub;
/// 正在弹出框展示的告警,为空表示当前没有弹窗
AlertInfoBO? _currentDialogAlert;
......@@ -107,6 +107,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
flex: 26,
child: MonitoringPetInfoCard(
patientInfo: state.patientInfo,
currentCareLevel: state.currentCareLevel,
onExit: () => _monitoringIndexCubit.clearPatientInfo(),
onBindPet: () => _monitoringIndexCubit.setTestPatientInfo(),
),
......@@ -187,9 +188,10 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
child: Container(
width: 388.w,
height: 62.h,
decoration: const BoxDecoration(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/title_bg_spacewalk.png'),
image: AssetImage(
'lib/assets/monitoring/title_bg_spacewalk.png'),
fit: BoxFit.cover,
),
),
......
......@@ -2,16 +2,17 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class MonitoringPetInfoCard extends StatelessWidget {
final PatientInfoBO? patientInfo;
final String? currentCareLevel;
final VoidCallback? onExit;
final VoidCallback? onBindPet;
const MonitoringPetInfoCard({
super.key,
required this.patientInfo,
this.currentCareLevel,
this.onExit,
this.onBindPet,
});
......@@ -20,15 +21,21 @@ class MonitoringPetInfoCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('lib/assets/monitoring/box_bg_460x905.png'),fit: BoxFit.fill),
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/box_bg_460x905.png'),
fit: BoxFit.fill),
// border: Border.all(color: Colors.red,width: 1.w)
),
padding: EdgeInsets.only(left: 40.w,right: 40.w, top: 32.h),
child: patientInfo == null ? _buildEmpty() : _buildContent(patientInfo!),
padding: EdgeInsets.only(left: 40.w, right: 40.w, top: 32.h),
child: patientInfo == null
? _buildEmpty(currentCareLevel)
: _buildContent(patientInfo!),
);
}
Widget _buildEmpty() {
Widget _buildEmpty(String? careLevel) {
final displayCareLevel =
careLevel == null || careLevel.isEmpty ? '暂无' : careLevel;
return Column(
children: [
SizedBox(height: 10.h),
......@@ -101,7 +108,7 @@ class MonitoringPetInfoCard extends StatelessWidget {
),
SizedBox(height: 41.h),
Text(
'暂无',
displayCareLevel,
style: TextStyle(
color: Colors.white,
fontSize: 64.sp,
......@@ -110,111 +117,43 @@ class MonitoringPetInfoCard extends StatelessWidget {
),
),
SizedBox(height: 55.h),
// 进度条
Container(
padding: EdgeInsets.symmetric(horizontal: 4.w),
decoration: BoxDecoration(
color: const Color(0xFF0A1A2E),
borderRadius: BorderRadius.circular(6.r),
_buildLevelBar(displayCareLevel),
],
),
SizedBox(height: 26.h),
// 绑定宠物按钮
GestureDetector(
onTap: onBindPet,
child: Container(
width: double.infinity,
height: 140.h,
decoration: const BoxDecoration(
image: DecorationImage(
image:
AssetImage('lib/assets/monitoring/title_bg_spacewalk.png'),
fit: BoxFit.fill,
),
),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(2.r),
boxShadow: const [
BoxShadow(
color: Color(0x99FFFFFF),
blurRadius: 6,
),
],
),
),
),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: const Color(0xFF25E5B0).withOpacity(0.3),
borderRadius: BorderRadius.circular(2.r),
),
),
),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: const Color(0xFFFFB347).withOpacity(0.3),
borderRadius: BorderRadius.circular(2.r),
),
),
),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: const Color(0xFFFF7A4D).withOpacity(0.3),
borderRadius: BorderRadius.circular(2.r),
),
),
),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: const Color(0xFFFF4E4E).withOpacity(0.3),
borderRadius: BorderRadius.circular(2.r),
),
SizedBox(width: 24.w),
Text(
'绑定宠物',
style: TextStyle(
color: Colors.white,
fontSize: 32.sp,
fontWeight: FontWeight.w700,
letterSpacing: 4.w,
),
),
SizedBox(width: 24.w),
],
),
),
],
),
),
SizedBox(height: 26.h),
// 绑定宠物按钮
// GestureDetector(
// onTap: onBindPet,
// child: Container(
// width: double.infinity,
// height: 140.h,
// decoration: const BoxDecoration(
// image: DecorationImage(
// image: AssetImage('lib/assets/monitoring/title_bg_spacewalk.png'),
// fit: BoxFit.fill,
// ),
// ),
// child: Center(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// SizedBox(width: 24.w),
// Text(
// '绑定宠物',
// style: TextStyle(
// color: Colors.white,
// fontSize: 32.sp,
// fontWeight: FontWeight.w700,
// letterSpacing: 4.w,
// ),
// ),
// SizedBox(width: 24.w),
// ],
// ),
// ),
// ),
// ),
],
);
}
......@@ -234,7 +173,6 @@ class MonitoringPetInfoCard extends StatelessWidget {
SizedBox(height: 30.h),
_buildCareLevelBlock(info),
SizedBox(height: 25.h),
_buildExitButton(),
],
);
......@@ -246,7 +184,7 @@ class MonitoringPetInfoCard extends StatelessWidget {
children: [
Container(
width: 150.w,
height: 160.w,
height: 160.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: const Color(0xFF1F4E79),
......@@ -358,7 +296,8 @@ class MonitoringPetInfoCard extends StatelessWidget {
Widget _buildInfoBlock(PatientInfoBO info) {
return Column(
children: [
_buildInfoItem(Icons.description_outlined, '宠物病因', _truncate(info.disease)),
_buildInfoItem(
Icons.description_outlined, '宠物病因', _truncate(info.disease)),
SizedBox(height: 20.h),
_buildInfoItem(Icons.home_outlined, '入住时间', info.checkInDate),
SizedBox(height: 20.h),
......@@ -433,12 +372,12 @@ class MonitoringPetInfoCard extends StatelessWidget {
],
),
SizedBox(height: 49.h),
_buildLevelBar(),
_buildLevelBar(info.careLevel),
],
);
}
Widget _buildLevelBar() {
Widget _buildLevelBar(String careLevel) {
const segments = [
Color(0xFFFFFFFF),
Color(0xFF25E5B0),
......@@ -453,6 +392,7 @@ class MonitoringPetInfoCard extends StatelessWidget {
Color(0x99FF7A4D),
Color(0x99FF4E4E),
];
final activeIndex = _careLevelIndex(careLevel);
return Container(
padding: EdgeInsets.symmetric(horizontal: 4.w),
decoration: BoxDecoration(
......@@ -468,14 +408,18 @@ class MonitoringPetInfoCard extends StatelessWidget {
margin: EdgeInsets.symmetric(horizontal: 2.w),
height: 10.h,
decoration: BoxDecoration(
color: entry.value,
color: entry.key <= activeIndex
? entry.value
: const Color(0xFF6A7481),
borderRadius: BorderRadius.circular(2.r),
boxShadow: [
BoxShadow(
color: shadowColors[entry.key],
blurRadius: 6,
),
],
boxShadow: entry.key <= activeIndex
? [
BoxShadow(
color: shadowColors[entry.key],
blurRadius: 6,
),
]
: null,
),
),
))
......@@ -484,6 +428,22 @@ class MonitoringPetInfoCard extends StatelessWidget {
);
}
int _careLevelIndex(String careLevel) {
switch (careLevel) {
case '关闭':
return 0;
case '三级':
return 1;
case '二级':
return 2;
case '一级':
return 3;
case '特级':
return 4;
}
return 0;
}
Widget _buildExitButton() {
return GestureDetector(
onTap: onExit,
......
......@@ -15,14 +15,14 @@ class SettingsAboutPanel extends StatelessWidget {
Widget build(BuildContext context) {
return SettingsPanelFrame(
backgroundAsset: settingsAssetPanelMain,
padding: EdgeInsets.fromLTRB(42.w, 54.h, 42.w, 48.h),
padding: EdgeInsets.fromLTRB(100.w, 80.h, 100.w, 100.h),
child: BlocBuilder<SettingsIndexCubit, SettingsIndexState>(
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _LogoPlaceholder(),
SizedBox(height: 62.h),
SizedBox(height: 12.h),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
......@@ -31,9 +31,9 @@ class SettingsAboutPanel extends StatelessWidget {
const _BarcodeCard(),
],
),
SizedBox(height: 36.h),
SizedBox(height: 20.h),
Divider(color: const Color(0xFF4CA7D2).withValues(alpha: 0.36)),
SizedBox(height: 18.h),
SizedBox(height: 4.h),
Expanded(
child: Row(
children: [
......@@ -71,8 +71,8 @@ class _LogoPlaceholder extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Container(
width: 855.w,
height: 184.h,
width: 863.w,
height: 150.h,
color: const Color(0xFFD7D7D7),
alignment: Alignment.center,
child: Text(
......@@ -155,8 +155,7 @@ class _DeviceInfoColumn extends StatelessWidget {
return BlocBuilder<BluetoothReadBloc, BluetoothReadState>(
buildWhen: (previous, current) => previous.info.sn != current.info.sn,
builder: (context, state) {
final sn =
state.info.sn?.isNotEmpty == true ? state.info.sn! : 'CNA07212L';
final sn = state.info.sn?.isNotEmpty == true ? state.info.sn! : '--';
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
......@@ -239,36 +238,42 @@ class _InfoRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
width: 210.w,
child: Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 27.sp,
fontWeight: FontWeight.w700,
return SizedBox(
height: 96.h,
child: Row(
children: [
SizedBox(
width: 210.w,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 27.sp,
fontWeight: FontWeight.w700,
),
),
),
),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: valueWidget ??
Text(
value ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 27.sp,
fontWeight: FontWeight.w700,
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: valueWidget ??
Text(
value ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 27.sp,
fontWeight: FontWeight.w700,
),
),
),
),
),
),
],
],
),
);
}
}
......@@ -282,8 +287,8 @@ class _SelectButton extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
width: 350.w,
height: 52.h,
decoration: settingsButtonDecoration(),
height: 96.h,
decoration: settingsButtonDecoration(asset: settingsAssetDDL),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
......@@ -296,12 +301,7 @@ class _SelectButton extends StatelessWidget {
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 28.w),
Icon(
Icons.keyboard_arrow_down,
color: const Color(0xFF7CCDF0).withValues(alpha: 0.74),
size: 30.w,
),
SizedBox(width: 100.w)
],
),
);
......
......@@ -4,9 +4,10 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
const String settingsAssetPanelLeft =
'lib/assets/monitoring/box_bg_460x905.png';
const String settingsAssetPanelMain =
'lib/assets/monitoring/box_bg_674x509.png';
const String settingsAssetButton =
'lib/assets/monitoring/frame_button_324x164.png';
'lib/assets/settings/frame_panel_3900x2000.png';
const String settingsAssetDDL = 'lib/assets/settings/ddl.png';
const String settingsAssetButton = 'lib/assets/settings/frame_333x150.png';
class SettingsPanelFrame extends StatelessWidget {
const SettingsPanelFrame({
......@@ -37,10 +38,13 @@ class SettingsPanelFrame extends StatelessWidget {
}
}
BoxDecoration settingsButtonDecoration({bool active = false}) {
BoxDecoration settingsButtonDecoration({
bool active = false,
String asset = settingsAssetButton,
}) {
return BoxDecoration(
image: const DecorationImage(
image: AssetImage(settingsAssetButton),
image: DecorationImage(
image: AssetImage(asset),
fit: BoxFit.fill,
),
boxShadow: active
......
......@@ -3,6 +3,10 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'settings_panel_frame.dart';
const String _settingsMenuButtonAsset = 'lib/assets/settings/btn_setmenu.png';
const String _settingsMenuButtonActiveAsset =
'lib/assets/settings/btn_setmenu_active.png';
class SettingsSideMenu extends StatelessWidget {
const SettingsSideMenu({
super.key,
......@@ -25,7 +29,7 @@ class SettingsSideMenu extends StatelessWidget {
Widget build(BuildContext context) {
return SettingsPanelFrame(
backgroundAsset: settingsAssetPanelLeft,
padding: EdgeInsets.fromLTRB(42.w, 48.h, 42.w, 48.h),
padding: EdgeInsets.fromLTRB(36.w, 48.h, 36.w, 48.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -37,7 +41,7 @@ class SettingsSideMenu extends StatelessWidget {
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 34.h),
SizedBox(height: 30.h),
Divider(color: const Color(0xFF4CA7D2).withValues(alpha: 0.42)),
SizedBox(height: 30.h),
for (final menu in menus) ...[
......@@ -46,7 +50,7 @@ class SettingsSideMenu extends StatelessWidget {
active: menu == selectedMenu,
onTap: () => onSelected(menu),
),
SizedBox(height: 22.h),
// SizedBox(height: 6.h),
],
],
),
......@@ -71,9 +75,10 @@ class _SettingsMenuButton extends StatelessWidget {
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 64.h,
padding: EdgeInsets.symmetric(horizontal: 28.w),
decoration: settingsButtonDecoration(active: active),
width: 388,
height: 62,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
decoration: _settingsMenuButtonDecoration(active: active),
child: Row(
children: [
Expanded(
......@@ -82,23 +87,35 @@ class _SettingsMenuButton extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
color: active ? const Color(0xFFC9F8FF) : Colors.white,
fontSize: 30.sp,
fontSize: 20,
// height: 0.9,
fontWeight: FontWeight.w700,
),
),
),
Text(
'》》',
style: TextStyle(
color: const Color(0xFF67C9F5)
.withValues(alpha: active ? 0.72 : 0.36),
fontSize: 28.sp,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
}
BoxDecoration _settingsMenuButtonDecoration({required bool active}) {
return BoxDecoration(
image: DecorationImage(
image: AssetImage(
active ? _settingsMenuButtonActiveAsset : _settingsMenuButtonAsset,
),
fit: BoxFit.fill,
),
boxShadow: active
? [
BoxShadow(
color: const Color(0xFF45CFFF).withValues(alpha: 0.10),
blurRadius: 18.r,
spreadRadius: 10.r,
),
]
: null,
);
}
......@@ -102,6 +102,7 @@ flutter:
- lib/assets/tab/
- lib/assets/icon/
- lib/assets/monitoring/
- lib/assets/settings/
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
......
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