Commit 65c9e3b0 authored by akari's avatar akari
parents 45154e91 83539172
{
"APP_ENV": "dev",
"SERVICE_URL": "http://121.41.173.189/laki-merchant-service"
"SERVICE_URL": "http://192.168.112.129:8089"
}
......@@ -38,9 +38,34 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> {
) async {
emit(state.copyWith(isLoading: true, clearError: true));
try {
final info = await _storageService.getBluetoothReadInfo();
Log.warn('**************sn************: ${info.sn}');
emit(state.copyWith(info: info, isLoading: false, clearError: true));
BluetoothReadModel? info;
const maxRetries = 5;
const retryInterval = Duration(seconds: 10);
for (int i = 0; i < maxRetries; i++) {
try {
info = await _storageService.getBluetoothReadInfo();
Log.warn(
'**************sn 第${i + 1}次获取************: ${info.sn}');
if (info.hasSn) {
Log.warn('**************sn 获取成功,停止重试************');
break;
}
} catch (e) {
Log.warn('**************sn 第${i + 1}次获取失败: $e************');
}
// 如果还没获取到sn且不是最后一次,则等待后重试
if (i < maxRetries - 1 && (info == null || !info.hasSn)) {
await Future.delayed(retryInterval);
}
}
emit(state.copyWith(
info: info ?? BluetoothReadModel.empty,
isLoading: false,
clearError: true,
));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
......
/// 舱详情接口响应模型
///
/// 接口:GET /cabin/detail?carbinSn=xxx
/// 来源:从 laki_biz_app 拷贝并适配为本项目手写 fromJson 风格
class CabinDetailResponse {
final String? pageTitle;
final int? deviceId;
final String? deviceName;
final int? cabinId;
final String? cabinName;
final String? cabinType;
final String? carbinSn;
final String? cameraSn;
final String? wifiPwd;
final String? wifiName;
final PetInfoResponse? petInfo;
final CameraInfoResponse? cameraInfo;
final MonitoringDataResponse? monitoringData;
final CabinControlsResponse? cabinControls;
final int? currentPlanId;
final int? currentPetId;
final String? status; // 舱状态
final String? state; // 舱位物理状态
CabinDetailResponse({
this.pageTitle,
this.deviceId,
this.deviceName,
this.cabinId,
this.cabinName,
this.cabinType,
this.carbinSn,
this.cameraSn,
this.wifiPwd,
this.wifiName,
this.petInfo,
this.cameraInfo,
this.monitoringData,
this.cabinControls,
this.currentPlanId,
this.currentPetId,
this.status,
this.state,
});
factory CabinDetailResponse.fromJson(Map<String, dynamic> json) {
return CabinDetailResponse(
pageTitle: json['pageTitle'] as String?,
deviceId: json['deviceId'] as int?,
deviceName: json['deviceName'] as String?,
cabinId: json['cabinId'] as int?,
cabinName: json['cabinName'] as String?,
cabinType: json['cabinType'] as String?,
carbinSn: json['carbinSn'] as String?,
cameraSn: json['cameraSn'] as String?,
wifiPwd: json['wifiPwd'] as String?,
wifiName: json['wifiName'] as String?,
petInfo: json['petInfo'] != null
? PetInfoResponse.fromJson(json['petInfo'] as Map<String, dynamic>)
: null,
cameraInfo: json['cameraInfo'] != null
? CameraInfoResponse.fromJson(
json['cameraInfo'] as Map<String, dynamic>)
: null,
monitoringData: json['monitoringData'] != null
? MonitoringDataResponse.fromJson(
json['monitoringData'] as Map<String, dynamic>)
: null,
cabinControls: json['cabinControls'] != null
? CabinControlsResponse.fromJson(
json['cabinControls'] as Map<String, dynamic>)
: null,
currentPlanId: json['currentPlanId'] as int?,
currentPetId: json['currentPetId'] as int?,
status: json['status'] as String?,
state: json['state'] as String?,
);
}
CabinDetailResponse copyWith({
String? pageTitle,
int? deviceId,
String? deviceName,
int? cabinId,
String? cabinName,
String? cabinType,
String? carbinSn,
String? cameraSn,
String? wifiPwd,
String? wifiName,
PetInfoResponse? petInfo,
CameraInfoResponse? cameraInfo,
MonitoringDataResponse? monitoringData,
CabinControlsResponse? cabinControls,
int? currentPlanId,
int? currentPetId,
String? status,
String? state,
}) {
return CabinDetailResponse(
pageTitle: pageTitle ?? this.pageTitle,
deviceId: deviceId ?? this.deviceId,
deviceName: deviceName ?? this.deviceName,
cabinId: cabinId ?? this.cabinId,
cabinName: cabinName ?? this.cabinName,
cabinType: cabinType ?? this.cabinType,
carbinSn: carbinSn ?? this.carbinSn,
cameraSn: cameraSn ?? this.cameraSn,
wifiPwd: wifiPwd ?? this.wifiPwd,
wifiName: wifiName ?? this.wifiName,
petInfo: petInfo ?? this.petInfo,
cameraInfo: cameraInfo ?? this.cameraInfo,
monitoringData: monitoringData ?? this.monitoringData,
cabinControls: cabinControls ?? this.cabinControls,
currentPlanId: currentPlanId ?? this.currentPlanId,
currentPetId: currentPetId ?? this.currentPetId,
status: status ?? this.status,
state: state ?? this.state,
);
}
}
/// 宠物信息(舱详情接口子模型)
class PetInfoResponse {
final int? petId;
final String? name;
final String? avatar;
final String? type;
final String? breedTag;
final String? disease;
final String? ownerName;
final String? ownerPhone;
final String? basicInfo;
final String? petName;
final String? petType;
final String? monitoringStatus;
PetInfoResponse({
this.petId,
this.name,
this.avatar,
this.type,
this.breedTag,
this.disease,
this.ownerName,
this.ownerPhone,
this.basicInfo,
this.petName,
this.petType,
this.monitoringStatus,
});
factory PetInfoResponse.fromJson(Map<String, dynamic> json) {
return PetInfoResponse(
petId: json['petId'] as int?,
name: json['name'] as String?,
avatar: json['avatar'] as String?,
type: json['type'] as String?,
breedTag: json['breedTag'] as String?,
disease: json['disease'] as String?,
ownerName: json['ownerName'] as String?,
ownerPhone: json['ownerPhone'] as String?,
basicInfo: json['basicInfo'] as String?,
petName: json['petName'] as String?,
petType: json['petType'] as String?,
monitoringStatus: json['monitoringStatus'] as String?,
);
}
PetInfoResponse copyWith({
int? petId,
String? name,
String? avatar,
String? type,
String? breedTag,
String? disease,
String? ownerName,
String? ownerPhone,
String? basicInfo,
String? petName,
String? petType,
String? monitoringStatus,
}) {
return PetInfoResponse(
petId: petId ?? this.petId,
name: name ?? this.name,
avatar: avatar ?? this.avatar,
type: type ?? this.type,
breedTag: breedTag ?? this.breedTag,
disease: disease ?? this.disease,
ownerName: ownerName ?? this.ownerName,
ownerPhone: ownerPhone ?? this.ownerPhone,
basicInfo: basicInfo ?? this.basicInfo,
petName: petName ?? this.petName,
petType: petType ?? this.petType,
monitoringStatus: monitoringStatus ?? this.monitoringStatus,
);
}
}
/// 摄像头信息(舱详情接口子模型)
class CameraInfoResponse {
final int? cameraId;
final String? cameraName;
final String? sn;
final String? cameraPwd;
final String? status;
final String? streamUrl;
CameraInfoResponse({
this.cameraId,
this.cameraName,
this.sn,
this.cameraPwd,
this.status,
this.streamUrl,
});
factory CameraInfoResponse.fromJson(Map<String, dynamic> json) {
return CameraInfoResponse(
cameraId: json['cameraId'] as int?,
cameraName: json['cameraName'] as String?,
sn: json['sn'] as String?,
cameraPwd: json['cameraPwd'] as String?,
status: json['status'] as String?,
streamUrl: json['streamUrl'] as String?,
);
}
CameraInfoResponse copyWith({
int? cameraId,
String? cameraName,
String? sn,
String? cameraPwd,
String? status,
String? streamUrl,
}) {
return CameraInfoResponse(
cameraId: cameraId ?? this.cameraId,
cameraName: cameraName ?? this.cameraName,
sn: sn ?? this.sn,
cameraPwd: cameraPwd ?? this.cameraPwd,
status: status ?? this.status,
streamUrl: streamUrl ?? this.streamUrl,
);
}
}
/// 监控数据(舱详情接口子模型)
class MonitoringDataResponse {
final int? totalDuration;
final int? elapsedDuration;
final String? status;
final String? careLevel;
final int? planId;
final String? planName;
final String? planType;
final int? temperatureSetting;
final int? mainTemperature;
final int? humidity;
final int? humiditySetting;
final int? oxygenConcentration;
final int? oxygenSetting;
final int? openO2;
final int? noOpenO2SettingMax;
final int? openO2SettingMax;
final int? co2Measurement;
final int? co2Setting;
final int? co2FastClearSwitch;
final int? internalExternalCycleState;
final int? floodLight;
final int? inspectionLamp;
final int? blueLight;
final int? redLight;
final int? airConditioner;
final int? levelLight;
final int? windSet;
final int? rightAtomizerTreatmentTime;
final int? rightInfraredPhysiotherapyTime;
final int? wifible;
final int? totalTime;
final int? temperatureAlarm;
final int? humidityAlarm;
final int? oxygenAlarm;
final int? co2Alarm;
MonitoringDataResponse({
this.totalDuration,
this.elapsedDuration,
this.status,
this.careLevel,
this.planId,
this.planName,
this.planType,
this.temperatureSetting,
this.mainTemperature,
this.humidity,
this.humiditySetting,
this.oxygenConcentration,
this.oxygenSetting,
this.openO2,
this.noOpenO2SettingMax,
this.openO2SettingMax,
this.co2Measurement,
this.co2Setting,
this.co2FastClearSwitch,
this.internalExternalCycleState,
this.floodLight,
this.inspectionLamp,
this.blueLight,
this.redLight,
this.airConditioner,
this.levelLight,
this.windSet,
this.rightAtomizerTreatmentTime,
this.rightInfraredPhysiotherapyTime,
this.wifible,
this.totalTime,
this.temperatureAlarm,
this.humidityAlarm,
this.oxygenAlarm,
this.co2Alarm,
});
factory MonitoringDataResponse.fromJson(Map<String, dynamic> json) {
return MonitoringDataResponse(
totalDuration: json['totalDuration'] as int?,
elapsedDuration: json['elapsedDuration'] as int?,
status: json['status'] as String?,
careLevel: json['careLevel'] as String?,
planId: json['planId'] as int?,
planName: json['planName'] as String?,
planType: json['planType'] as String?,
temperatureSetting: json['temperatureSetting'] as int?,
mainTemperature: json['mainTemperature'] as int?,
humidity: json['humidity'] as int?,
humiditySetting: json['humiditySetting'] as int?,
oxygenConcentration: json['oxygenConcentration'] as int?,
oxygenSetting: json['oxygenSetting'] as int?,
openO2: json['openO2'] as int?,
noOpenO2SettingMax: json['noOpenO2SettingMax'] as int?,
openO2SettingMax: json['openO2SettingMax'] as int?,
co2Measurement: json['co2Measurement'] as int?,
co2Setting: json['co2Setting'] as int?,
co2FastClearSwitch: json['co2FastClearSwitch'] as int?,
internalExternalCycleState: json['internalExternalCycleState'] as int?,
floodLight: json['floodLight'] as int?,
inspectionLamp: json['inspectionLamp'] as int?,
blueLight: json['blueLight'] as int?,
redLight: json['redLight'] as int?,
airConditioner: json['airConditioner'] as int?,
levelLight: json['levelLight'] as int?,
windSet: json['windSet'] as int?,
rightAtomizerTreatmentTime: json['rightAtomizerTreatmentTime'] as int?,
rightInfraredPhysiotherapyTime: json['rightInfraredPhysiotherapyTime'] as int?,
wifible: json['wifible'] as int?,
totalTime: json['totalTime'] as int?,
temperatureAlarm: json['temperatureAlarm'] as int?,
humidityAlarm: json['humidityAlarm'] as int?,
oxygenAlarm: json['oxygenAlarm'] as int?,
co2Alarm: json['co2Alarm'] as int?,
);
}
MonitoringDataResponse copyWith({
int? totalDuration,
int? elapsedDuration,
String? status,
String? careLevel,
int? planId,
String? planName,
String? planType,
int? temperatureSetting,
int? mainTemperature,
int? humidity,
int? humiditySetting,
int? oxygenConcentration,
int? oxygenSetting,
int? openO2,
int? noOpenO2SettingMax,
int? openO2SettingMax,
int? co2Measurement,
int? co2Setting,
int? co2FastClearSwitch,
int? internalExternalCycleState,
int? floodLight,
int? inspectionLamp,
int? blueLight,
int? redLight,
int? airConditioner,
int? levelLight,
int? windSet,
int? rightAtomizerTreatmentTime,
int? rightInfraredPhysiotherapyTime,
int? wifible,
int? totalTime,
int? temperatureAlarm,
int? humidityAlarm,
int? oxygenAlarm,
int? co2Alarm,
}) {
return MonitoringDataResponse(
totalDuration: totalDuration ?? this.totalDuration,
elapsedDuration: elapsedDuration ?? this.elapsedDuration,
status: status ?? this.status,
careLevel: careLevel ?? this.careLevel,
planId: planId ?? this.planId,
planName: planName ?? this.planName,
planType: planType ?? this.planType,
temperatureSetting: temperatureSetting ?? this.temperatureSetting,
mainTemperature: mainTemperature ?? this.mainTemperature,
humidity: humidity ?? this.humidity,
humiditySetting: humiditySetting ?? this.humiditySetting,
oxygenConcentration: oxygenConcentration ?? this.oxygenConcentration,
oxygenSetting: oxygenSetting ?? this.oxygenSetting,
openO2: openO2 ?? this.openO2,
noOpenO2SettingMax: noOpenO2SettingMax ?? this.noOpenO2SettingMax,
openO2SettingMax: openO2SettingMax ?? this.openO2SettingMax,
co2Measurement: co2Measurement ?? this.co2Measurement,
co2Setting: co2Setting ?? this.co2Setting,
co2FastClearSwitch: co2FastClearSwitch ?? this.co2FastClearSwitch,
internalExternalCycleState: internalExternalCycleState ?? this.internalExternalCycleState,
floodLight: floodLight ?? this.floodLight,
inspectionLamp: inspectionLamp ?? this.inspectionLamp,
blueLight: blueLight ?? this.blueLight,
redLight: redLight ?? this.redLight,
airConditioner: airConditioner ?? this.airConditioner,
levelLight: levelLight ?? this.levelLight,
windSet: windSet ?? this.windSet,
rightAtomizerTreatmentTime: rightAtomizerTreatmentTime ?? this.rightAtomizerTreatmentTime,
rightInfraredPhysiotherapyTime: rightInfraredPhysiotherapyTime ?? this.rightInfraredPhysiotherapyTime,
wifible: wifible ?? this.wifible,
totalTime: totalTime ?? this.totalTime,
temperatureAlarm: temperatureAlarm ?? this.temperatureAlarm,
humidityAlarm: humidityAlarm ?? this.humidityAlarm,
oxygenAlarm: oxygenAlarm ?? this.oxygenAlarm,
co2Alarm: co2Alarm ?? this.co2Alarm,
);
}
}
/// 舱控制参数(舱详情接口子模型)
class CabinControlsResponse {
final double? temperature;
final double? oxygen;
final double? humidity;
final double? co2;
final int? nebulizer;
final int? sterilize;
final String? fanMode;
final String? airMode;
final bool? newFan;
final bool? openOxygen;
final bool? checkLight;
final bool? light;
final bool? blueLight;
final bool? redLight;
CabinControlsResponse({
this.temperature,
this.oxygen,
this.humidity,
this.co2,
this.nebulizer,
this.sterilize,
this.fanMode,
this.airMode,
this.newFan,
this.openOxygen,
this.checkLight,
this.light,
this.blueLight,
this.redLight,
});
factory CabinControlsResponse.fromJson(Map<String, dynamic> json) {
return CabinControlsResponse(
temperature: (json['temperature'] as num?)?.toDouble(),
oxygen: (json['oxygen'] as num?)?.toDouble(),
humidity: (json['humidity'] as num?)?.toDouble(),
co2: (json['co2'] as num?)?.toDouble(),
nebulizer: json['nebulizer'] as int?,
sterilize: json['sterilize'] as int?,
fanMode: json['fanMode'] as String?,
airMode: json['airMode'] as String?,
newFan: json['newFan'] as bool?,
openOxygen: json['openOxygen'] as bool?,
checkLight: json['checkLight'] as bool?,
light: json['light'] as bool?,
blueLight: json['blueLight'] as bool?,
redLight: json['redLight'] as bool?,
);
}
CabinControlsResponse copyWith({
double? temperature,
double? oxygen,
double? humidity,
double? co2,
int? nebulizer,
int? sterilize,
String? fanMode,
String? airMode,
bool? newFan,
bool? openOxygen,
bool? checkLight,
bool? light,
bool? blueLight,
bool? redLight,
}) {
return CabinControlsResponse(
temperature: temperature ?? this.temperature,
oxygen: oxygen ?? this.oxygen,
humidity: humidity ?? this.humidity,
co2: co2 ?? this.co2,
nebulizer: nebulizer ?? this.nebulizer,
sterilize: sterilize ?? this.sterilize,
fanMode: fanMode ?? this.fanMode,
airMode: airMode ?? this.airMode,
newFan: newFan ?? this.newFan,
openOxygen: openOxygen ?? this.openOxygen,
checkLight: checkLight ?? this.checkLight,
light: light ?? this.light,
blueLight: blueLight ?? this.blueLight,
redLight: redLight ?? this.redLight,
);
}
}
/// MQTT 摄像头告警消息数据模型
/// 对应 topic: camera/{cabinSn}/to 中 type="abnormal_behavior" 的消息
///
/// 与 Android 项目 qisuanfa_0621 MqttManager.processControlCommand() 保持一致:
/// - 只提取 behaviorCode 和 timestamp 两个字段用于标题映射
/// - 其余字段保留解析,方便后续扩展
class CameraAlarmBO {
/// 事件唯一标识(UUID 去横线),用于去重
final String eventId;
/// 仓 SN
final String deviceId;
/// 设备类型,固定 "camera"
final String deviceType;
/// 毫秒时间戳
final int timestamp;
/// 消息类型,如 "abnormal_behavior"
final String type;
/// 告警 ID
final int alarmId;
/// 告警类别
final String alarmType;
/// 行为编码
final int? behaviorCode;
/// 行为描述
final String? behaviorDesc;
/// 宠物名称
final String? petName;
const CameraAlarmBO({
required this.eventId,
required this.deviceId,
required this.deviceType,
required this.timestamp,
required this.type,
required this.alarmId,
required this.alarmType,
this.behaviorCode,
this.behaviorDesc,
this.petName,
});
factory CameraAlarmBO.fromJson(Map<String, dynamic> json) {
return CameraAlarmBO(
eventId: json['eventId'] as String? ?? '',
deviceId: json['deviceId'] as String? ?? '',
deviceType: json['deviceType'] as String? ?? '',
timestamp: json['timestamp'] as int? ?? 0,
type: json['type'] as String? ?? '',
alarmId: json['alarmId'] as int? ?? 0,
alarmType: json['alarmType'] as String? ?? '',
behaviorCode: json['behaviorCode'] as int?,
behaviorDesc: json['behaviorDesc'] as String?,
petName: json['petName'] as String?,
);
}
/// 与 Android 行为编码映射完全一致:
/// 2 → 异常液体
/// 3 → 检测到排泄物
/// 其他 → 未知异常
String get titleByBehaviorCode {
if (behaviorCode == 2) return '异常液体';
if (behaviorCode == 3) return '检测到排泄物';
return '未知异常';
}
}
import 'mock/cabin_mock_data.dart';
import 'package:laki_icu_app/models/bo/cabin_detail_response.dart';
import 'package:laki_icu_app/utils/constants.dart';
import 'package:laki_icu_app/utils/http/dio_request.dart';
import 'package:laki_icu_app/utils/http/response_model.dart';
class CabinRepository {
CabinRepository._();
static final CabinRepository instance = CabinRepository._();
/// 获取舱详情
///
/// [carbinSn] 舱序列号(设备 SN)
Future<ResponseModel<CabinDetailResponse>> getCabinDetail(
String carbinSn,
) {
return DioRequest.instance.get<CabinDetailResponse>(
'/icu/detail',
queryParameters: {'carbinSn': carbinSn},
fromJsonT: (data) =>
CabinDetailResponse.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel<CabinDetailResponse>> _mockCabinDetail() async {
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<CabinDetailResponse>(
code: 200,
msg: '获取舱详情成功(mock)',
data: CabinMockData.getMockCabinDetail(),
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
}
import '../../models/bo/cabin_detail_response.dart';
/// 舱详情模块本地 mock 数据。
///
/// 所有 mock 数据直接使用 models 层构造函数创建,
/// 确保数据结构与 models 定义完全一致。
/// 后续接入真实接口时,只需修改 Repository 层开关即可。
class CabinMockData {
CabinMockData._();
static CabinDetailResponse getMockCabinDetail() {
return CabinDetailResponse(
pageTitle: 'ICU 监护舱',
deviceId: 1001,
deviceName: 'TSAAS ICU 设备',
cabinId: 1,
cabinName: '1号舱',
cabinType: 'ICU',
carbinSn: 'VE10085871QOXG',
cameraSn: 'VE10085871QOXG',
wifiPwd: '143548',
wifiName: 'TSAAS-ICU-WiFi',
petInfo: PetInfoResponse(
petId: 1,
name: '妮蔻',
avatar: '',
type: '猫',
breedTag: '长毛三花',
disease: '胃炎',
ownerName: '张先生',
ownerPhone: '130-1111-3333',
basicInfo: '3岁 雌性 4.5kg',
petName: '妮蔻',
petType: '猫',
monitoringStatus: 'monitoring',
),
cameraInfo: CameraInfoResponse(
cameraId: 1,
cameraName: '主摄像头',
sn: 'VE10085871QOXG',
cameraPwd: '143548',
status: 'online',
streamUrl: '',
),
monitoringData: MonitoringDataResponse(
totalDuration: 7200,
elapsedDuration: 3600,
status: 'monitoring',
careLevel: '特级',
planId: 1,
planName: '标准监护方案',
planType: 'standard',
temperatureSetting: 25,
mainTemperature: 24,
humidity: 55,
humiditySetting: 50,
oxygenConcentration: 21,
oxygenSetting: 20,
co2Measurement: 400,
co2Setting: 500,
),
cabinControls: CabinControlsResponse(
temperature: 25.0,
oxygen: 21.0,
humidity: 55.0,
co2: 400.0,
nebulizer: 0,
sterilize: 0,
fanMode: 'auto',
airMode: 'cycle',
newFan: true,
openOxygen: false,
checkLight: false,
light: true,
blueLight: false,
redLight: false,
),
currentPlanId: 1,
currentPetId: 1,
status: 'occupied',
state: 'normal',
);
}
}
import 'package:laki_icu_app/models/bo/cabin_detail_response.dart';
import 'package:laki_icu_app/repositories/cabin_repository.dart';
class CabinService {
final CabinRepository _repository;
CabinService() : _repository = CabinRepository.instance;
/// 获取舱详情
///
/// [carbinSn] 舱序列号(设备 SN)
/// 返回 [CabinDetailResponse],包含 cameraSn、wifiPwd、petInfo 等
Future<CabinDetailResponse> getCabinDetail(String carbinSn) async {
final result = await _repository.getCabinDetail(carbinSn);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
......@@ -3,7 +3,10 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_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/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/models/bo/camera_alarm_bo.dart';
import 'package:laki_icu_app/services/cabin_service.dart';
import 'package:laki_icu_app/services/monitoring_service.dart';
import 'package:laki_icu_app/services/p2p_video_service.dart';
import 'package:laki_icu_app/services/webrtc_service.dart';
......@@ -16,11 +19,11 @@ import 'package:laki_icu_app/utils/storage/storage_service.dart';
import 'monitoring_index_state.dart';
/// 设备凭据 —— Mock 阶段使用占位值,后续接口接入后替换
/// TODO: 从舱详情接口获取真实的 cameraSn 和 wifiPwd
/// 设备凭据 —— 从舱详情接口获取 cameraSn 和 wifiPwd
class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final MonitoringService _monitoringService;
final CabinService _cabinService;
final WebrtcService _webrtcService;
final P2pVideoService _p2pVideoService;
final StorageService _storageService;
......@@ -48,12 +51,20 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
bool _isMqttConnecting = false;
String? _mqttDeviceSn;
/// 已收到的摄像头告警 eventId 集合,用于去重
final Set<String> _seenAlarmEventIds = {};
/// 记录已请求过舱详情的 SN,避免每个 BLE 数据帧都重复请求接口
String? _lastFetchedCabinSn;
bool _isFetchingCabinDetail = false;
/// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。
int _switchGen = 0;
MonitoringIndexCubit()
: _monitoringService = MonitoringService(),
_cabinService = CabinService(),
_webrtcService = WebrtcService(),
_p2pVideoService = P2pVideoService(),
_storageService = StorageService(),
......@@ -143,6 +154,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
if (bluetoothReadInfo.hasSn) {
await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!);
await _connectMqttForDeviceSn(bluetoothReadInfo.sn!);
await _fetchCabinDetailAndConnectVideo(bluetoothReadInfo.sn!);
}
eventBus.emit(
BluetoothReadInfoChangedEvent(bluetoothReadInfo),
......@@ -245,6 +257,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
);
}
_connectMqttForDeviceSn(sn);
_fetchCabinDetailAndConnectVideo(sn);
}
}
if (deviceId != null && deviceId.isNotEmpty) {
......@@ -298,10 +311,22 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
/// 初始化 WebRTC 视频连接
///
/// 使用从舱详情接口获取的 [cabinCameraSn] 和 [cabinWifiPwd],
/// 凭证未就绪时跳过连接。
Future<void> _initWebrtc() async {
final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty ||
wifiPwd == null || wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] WebRTC 凭证未就绪,跳过连接');
return;
}
await _webrtcService.connect(
deviceNo: 'VE10085871QOXG',
password: '143548',
deviceNo: cameraSn,
password: wifiPwd,
);
}
......@@ -311,11 +336,73 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
/// 初始化 P2P 视频连接
///
/// 使用从舱详情接口获取的 [cabinCameraSn] 和 [cabinWifiPwd],
/// 凭证未就绪时跳过连接。
Future<void> _initP2p() async {
final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty ||
wifiPwd == null || wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] P2P 凭证未就绪,跳过连接');
return;
}
await _p2pVideoService.connect(
deviceId: 'VE10085871QOXG',
deviceId: cameraSn,
username: 'admin',
password: '143548',
password: wifiPwd,
);
}
/// 根据舱 SN 获取舱详情,并将摄像头凭证和宠物信息同步到 State
///
/// 成功后自动触发视频连接(按当前 [VideoStreamMode] 方式)。
/// 同一 SN 只会请求一次,避免 BLE 数据帧高频回调导致重复调用接口。
Future<void> _fetchCabinDetailAndConnectVideo(String carbinSn) async {
// 相同 SN 已请求过则跳过,避免每个 BLE 数据帧都触发一次接口调用
if (_lastFetchedCabinSn == carbinSn) return;
// 上一次请求尚未完成则跳过
if (_isFetchingCabinDetail) return;
_isFetchingCabinDetail = true;
_lastFetchedCabinSn = carbinSn;
try {
final detail = await _cabinService.getCabinDetail(carbinSn);
if (isClosed) return;
emit(state.copyWith(
cabinCameraSn: detail.cameraSn,
cabinWifiPwd: detail.wifiPwd,
patientInfo: detail.petInfo != null
? _mapPetInfoToPatientInfo(detail.petInfo!)
: state.patientInfo,
));
// 凭证就绪后发起视频连接
_initVideoByMode();
} catch (e) {
if (isClosed) return;
debugPrint('[MonitoringIndexCubit] 获取舱详情失败: $e');
} finally {
_isFetchingCabinDetail = false;
}
}
/// 将 [PetInfoResponse] 映射为 [PatientInfoBO]
PatientInfoBO _mapPetInfoToPatientInfo(PetInfoResponse pet) {
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: '',
);
}
......@@ -470,6 +557,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
await _storageService.deleteBluetoothReadInfo();
_isMqttReconnectEnabled = false;
_mqttDeviceSn = null;
_lastFetchedCabinSn = null;
await _disposeMqttClient();
eventBus.emit(const BluetoothReadInfoClearedEvent());
_mcuReportDecoder.clear();
......@@ -482,7 +570,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
mqttMessage: 'MQTT 已断开',
bluetoothMessage: '蓝牙设备已解绑',
latestBluetoothRawHex: '',
cameraAlerts: const [],
));
_seenAlarmEventIds.clear();
_isManualUnbindingBluetooth = false;
}
......@@ -596,7 +686,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
await _mqttClient!.connect();
_mqttClient!
..subscribeIcuCommands(deviceSn)
..subscribePetBind(deviceSn);
..subscribePetBind(deviceSn)
..subscribe(LakiMqttTopics.cameraTo(deviceSn));
if (!isClosed) {
emit(state.copyWith(
......@@ -634,6 +725,12 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mqttMessageSub = client.messageStream.listen((message) {
if (isClosed) return;
final json = message.json;
if (json != null &&
message.topic == LakiMqttTopics.cameraTo(_mqttDeviceSn ?? '')) {
_handleCameraAlarmMessage(json);
return;
}
emit(state.copyWith(
mqttMessage: 'MQTT 收到 ${message.topic}: ${message.payload}',
));
......@@ -645,6 +742,44 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
});
}
/// 处理摄像头告警消息(与 Android qisuanfa_0621 逻辑保持一致)
///
/// 仅处理 type == "abnormal_behavior" 的消息,
/// 按 behaviorCode 映射标题:2→异常液体、3→检测到排泄物、其他→未知异常
void _handleCameraAlarmMessage(Map<String, dynamic> json) {
if (json['type'] != 'abnormal_behavior') return;
final alarm = CameraAlarmBO.fromJson(json);
// 按 eventId 去重
if (_seenAlarmEventIds.contains(alarm.eventId)) return;
_seenAlarmEventIds.add(alarm.eventId);
final timeStr = _formatTimestamp(alarm.timestamp);
final title = alarm.titleByBehaviorCode;
final alert = AlertInfoBO(
title: title,
description: title,
time: timeStr,
status: '待处理',
);
emit(state.copyWith(
cameraAlerts: [alert, ...state.cameraAlerts],
));
}
/// 毫秒时间戳 → HH:mm:ss 格式(与 Android 一致)
String _formatTimestamp(int milliseconds) {
final dateTime =
DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true);
final local = dateTime.toLocal();
return '${local.hour.toString().padLeft(2, '0')}:'
'${local.minute.toString().padLeft(2, '0')}:'
'${local.second.toString().padLeft(2, '0')}';
}
void _publishMqttMonitorReport(McuReport report) {
final client = _mqttClient;
if (client == null || !client.isConnected || report.sn.isEmpty) {
......@@ -885,6 +1020,52 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
/// 获取 P2P 视频播放控制器供 UI 层使用
P2pVideoService get p2pVideoService => _p2pVideoService;
/// 选中告警(用于打开详情弹窗)
void selectAlert(AlertInfoBO alert) {
if (isClosed) return;
emit(state.copyWith(selectedAlert: alert));
}
/// 清除选中的告警(用于关闭弹窗)
void clearSelectedAlert() {
if (isClosed) return;
emit(state.copyWith(selectedAlert: null));
}
/// 标记告警为已处理
void markAlertAsProcessed(AlertInfoBO alert) {
if (isClosed) return;
final updatedAlerts = state.alerts.map((item) {
if (item.title == alert.title && item.time == alert.time) {
return AlertInfoBO(
title: item.title,
description: item.description,
time: item.time,
status: '已处理',
);
}
return item;
}).toList();
emit(state.copyWith(alerts: updatedAlerts, selectedAlert: null));
}
/// 标记告警为误报
void markAlertAsFalse(AlertInfoBO alert) {
if (isClosed) return;
final updatedAlerts = state.alerts.map((item) {
if (item.title == alert.title && item.time == alert.time) {
return AlertInfoBO(
title: item.title,
description: item.description,
time: item.time,
status: '误报',
);
}
return item;
}).toList();
emit(state.copyWith(alerts: updatedAlerts, selectedAlert: null));
}
@override
Future<void> close() {
_cancelBluetoothReconnect();
......
......@@ -71,6 +71,18 @@ class MonitoringIndexState extends Equatable {
/// 当前 MQTT 连接使用的设备 SN
final String? mqttDeviceSn;
/// 设备摄像头序列号(从舱详情接口 /cabin/detail 获取)
final String? cabinCameraSn;
/// 设备 WiFi 密码(从舱详情接口 /cabin/detail 获取)
final String? cabinWifiPwd;
/// MQTT 实时推送的摄像头告警列表(来自 camera/{sn}/to)
final List<AlertInfoBO> cameraAlerts;
/// 当前选中的告警(用于弹窗显示)
final AlertInfoBO? selectedAlert;
const MonitoringIndexState({
this.status = MonitoringIndexStatus.initial,
this.isLoading = false,
......@@ -78,6 +90,7 @@ class MonitoringIndexState extends Equatable {
this.metrics = const [],
this.patientInfo,
this.alerts = const [],
this.cameraAlerts = const [],
this.videoConnectionState = WebrtcConnectionState.disconnected,
this.videoStreamMode = VideoStreamMode.p2p,
this.isSwitchingMode = false,
......@@ -95,6 +108,9 @@ class MonitoringIndexState extends Equatable {
this.mqttConnectionState = LakiMqttConnectionState.disconnected,
this.mqttMessage,
this.mqttDeviceSn,
this.cabinCameraSn,
this.cabinWifiPwd,
this.selectedAlert,
});
MonitoringIndexState copyWith({
......@@ -104,6 +120,7 @@ class MonitoringIndexState extends Equatable {
List<MonitoringMetricBO>? metrics,
PatientInfoBO? patientInfo,
List<AlertInfoBO>? alerts,
List<AlertInfoBO>? cameraAlerts,
WebrtcConnectionState? videoConnectionState,
VideoStreamMode? videoStreamMode,
bool? isSwitchingMode,
......@@ -121,6 +138,9 @@ class MonitoringIndexState extends Equatable {
LakiMqttConnectionState? mqttConnectionState,
String? mqttMessage,
String? mqttDeviceSn,
String? cabinCameraSn,
String? cabinWifiPwd,
AlertInfoBO? selectedAlert,
bool clearBoundBluetoothDevice = false,
bool clearLatestMcuReport = false,
bool clearPatientInfo = false,
......@@ -133,6 +153,7 @@ class MonitoringIndexState extends Equatable {
metrics: metrics ?? this.metrics,
patientInfo: clearPatientInfo ? null : patientInfo ?? this.patientInfo,
alerts: alerts ?? this.alerts,
cameraAlerts: cameraAlerts ?? this.cameraAlerts,
videoConnectionState: videoConnectionState ?? this.videoConnectionState,
videoStreamMode: videoStreamMode ?? this.videoStreamMode,
isSwitchingMode: isSwitchingMode ?? this.isSwitchingMode,
......@@ -159,6 +180,9 @@ class MonitoringIndexState extends Equatable {
mqttMessage: mqttMessage ?? this.mqttMessage,
mqttDeviceSn:
clearMqttDeviceSn ? null : mqttDeviceSn ?? this.mqttDeviceSn,
cabinCameraSn: cabinCameraSn ?? this.cabinCameraSn,
cabinWifiPwd: cabinWifiPwd ?? this.cabinWifiPwd,
selectedAlert: selectedAlert ?? this.selectedAlert,
);
}
......@@ -170,6 +194,7 @@ class MonitoringIndexState extends Equatable {
metrics,
patientInfo,
alerts,
cameraAlerts,
videoConnectionState,
videoStreamMode,
isSwitchingMode,
......@@ -187,5 +212,8 @@ class MonitoringIndexState extends Equatable {
mqttConnectionState,
mqttMessage,
mqttDeviceSn,
cabinCameraSn,
cabinWifiPwd,
selectedAlert,
];
}
......@@ -13,6 +13,7 @@ import 'cubit/monitoring_index_cubit.dart';
import 'cubit/monitoring_index_state.dart';
import 'widgets/bluetooth_bind_dialog.dart';
import 'widgets/monitoring_alert_card.dart';
import 'widgets/monitoring_alert_detail_dialog.dart';
import 'widgets/monitoring_metric_card.dart';
import 'widgets/monitoring_pet_info_card.dart';
import 'widgets/video_player_widget.dart';
......@@ -59,19 +60,26 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
@override
Widget build(BuildContext context) {
return BlocBuilder<MonitoringIndexCubit, MonitoringIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
return BlocListener<MonitoringIndexCubit, MonitoringIndexState>(
listener: (context, state) {
if (state.selectedAlert != null) {
_showAlertDetailDialog(state.selectedAlert!);
}
if (state.status == MonitoringIndexStatus.failure &&
state.metrics.isEmpty) {
return _buildError(state.error);
}
return _buildMainArea(state);
},
child: BlocBuilder<MonitoringIndexCubit, MonitoringIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (state.status == MonitoringIndexStatus.failure &&
state.metrics.isEmpty) {
return _buildError(state.error);
}
return _buildMainArea(state);
},
),
);
}
......@@ -121,7 +129,12 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
children: [
Expanded(
flex: 10,
child: MonitoringAlertCard(alerts: state.alerts),
child: MonitoringAlertCard(
alerts: [
...state.cameraAlerts,
...state.alerts,
],
),
),
SizedBox(width: 10.w),
Expanded(
......@@ -244,6 +257,18 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
void _showAlertDetailDialog(AlertInfoBO alert) {
showDialog<void>(
context: context,
builder: (_) => BlocProvider.value(
value: _monitoringIndexCubit,
child: MonitoringAlertDetailDialog(alert: alert),
),
).then((_) {
_monitoringIndexCubit.clearSelectedAlert();
});
}
List<MonitoringMetricBO> _environmentMetrics(
List<MonitoringMetricBO> metrics) {
final matched = metrics
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/views/monitoring/index/cubit/monitoring_index_cubit.dart';
class MonitoringAlertCard extends StatelessWidget {
final List<AlertInfoBO> alerts;
......@@ -73,53 +75,57 @@ class MonitoringAlertCard extends StatelessWidget {
itemBuilder: (context, index) {
final item = alerts[index];
final pending = item.status.contains('待处理');
return _buildAlertItem(item, pending);
return _buildAlertItem(context, item, pending);
},
);
}
Widget _buildAlertItem(AlertInfoBO item, bool pending) {
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIconBlock(pending),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
color: pending ? const Color(0xFFF26522) : const Color(0xFFFFC14A),
fontSize: 28.sp,
fontWeight: FontWeight.bold,
Widget _buildAlertItem(BuildContext context, AlertInfoBO item, bool pending) {
return InkWell(
onTap: () => context.read<MonitoringIndexCubit>().selectAlert(item),
borderRadius: BorderRadius.circular(16.r),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIconBlock(pending),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
color: pending ? const Color(0xFFF26522) : const Color(0xFFFFC14A),
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
),
SizedBox(height: 8.h),
Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
SizedBox(height: 8.h),
Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
),
),
),
// SizedBox(height: 10.h),
Text(
item.time,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
// SizedBox(height: 10.h),
Text(
item.time,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
),
),
),
],
],
),
),
),
],
],
),
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/views/monitoring/index/cubit/monitoring_index_cubit.dart';
class MonitoringAlertDetailDialog extends StatelessWidget {
final AlertInfoBO alert;
const MonitoringAlertDetailDialog({
super.key,
required this.alert,
});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: _buildDialogContent(context),
);
}
Widget _buildDialogContent(BuildContext context) {
return Container(
width: 1300.w,
height: 800.h,
margin: EdgeInsets.fromLTRB(100.w, 100.h, 100.w, 100.h),
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('lib/assets/monitoring/dialog_alert_bg.png'),
fit: BoxFit.fill,
),
borderRadius: BorderRadius.circular(20.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
SizedBox(height: 30.h),
_buildContent(context),
SizedBox(height: 40.h),
_buildBottomButtons(context),
],
),
);
}
Widget _buildHeader() {
return Container(
padding: EdgeInsets.fromLTRB(40.w, 24.h, 40.w, 24.h),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFF00D4FF),
width: 2,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'告警详情',
style: TextStyle(
color: const Color(0xFF00D4FF),
fontSize: 36.sp,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Widget _buildContent(BuildContext context) {
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImagePlaceholder(),
SizedBox(width: 30.w),
Expanded(
child: _buildAlertInfo(),
),
],
),
),
);
}
Widget _buildImagePlaceholder() {
return Container(
width: 280.w,
height: 200.h,
decoration: BoxDecoration(
color: const Color(0xFF1A2332),
borderRadius: BorderRadius.circular(12.r),
),
);
}
Widget _buildAlertInfo() {
final isPending = alert.status.contains('待处理');
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
alert.title,
style: TextStyle(
color: const Color(0xFFFFC14A),
fontSize: 34.sp,
fontWeight: FontWeight.bold,
),
),
Text(
alert.time,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
),
),
],
),
SizedBox(height: 20.h),
_buildInfoRow('处理状态', isPending ? '待处理' : '已处理', isPending),
_buildInfoRow('处理状态', isPending ? '待处理' : '已处理', isPending),
_buildInfoRow('处理时间', '', false),
SizedBox(height: 30.h),
Expanded(
child: SingleChildScrollView(
child: Text(
alert.description,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
height: 1.5,
),
),
),
),
],
);
}
Widget _buildInfoRow(String label, String value, bool isHighlight) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
children: [
SizedBox(
width: 140.w,
child: Text(
label,
style: TextStyle(
color: Colors.white.withValues(alpha: 153),
fontSize: 26.sp,
),
),
),
SizedBox(width: 20.w),
Text(
value,
style: TextStyle(
color: isHighlight ? const Color(0xFFFFC14A) : Colors.white,
fontSize: 26.sp,
fontWeight: isHighlight ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
Widget _buildBottomButtons(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(40.w, 20.h, 40.w, 30.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildButton(
'关闭窗口',
const Color(0xFF2A2E3B),
Colors.white,
const Color(0xFF4A4E5B),
() => Navigator.of(context).pop(),
),
SizedBox(width: 30.w),
_buildButton(
'标记误报',
const Color(0xFF2A2E3B),
Colors.white,
const Color(0xFF4A4E5B),
() => _handleMarkAsFalse(context),
),
SizedBox(width: 30.w),
_buildButton(
'已处理',
const Color(0xFF00D4FF),
Colors.black,
const Color(0xFF00D4FF),
() => _handleMarkAsProcessed(context),
isActive: true,
),
],
),
);
}
Widget _buildButton(
String text,
Color backgroundColor,
Color textColor,
Color borderColor,
VoidCallback onPressed, {
bool isActive = false,
}) {
return GestureDetector(
onTap: onPressed,
child: Container(
width: 220.w,
height: 72.h,
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(
color: borderColor,
width: 2.w,
),
borderRadius: BorderRadius.circular(8.r),
boxShadow: isActive
? [
BoxShadow(
color: borderColor.withValues(alpha: 128),
blurRadius: 10.r,
offset: const Offset(0, 0),
),
]
: null,
),
child: Center(
child: Text(
text,
style: TextStyle(
color: textColor,
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
void _handleMarkAsFalse(BuildContext context) {
context.read<MonitoringIndexCubit>().markAlertAsFalse(alert);
Navigator.of(context).pop();
}
void _handleMarkAsProcessed(BuildContext context) {
context.read<MonitoringIndexCubit>().markAlertAsProcessed(alert);
Navigator.of(context).pop();
}
}
\ No newline at end of file
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