Commit 14b0b287 authored by akari's avatar akari

feat: 添加中控页

parent 5c739763
import 'package:flutter_bloc/flutter_bloc.dart';
import 'controller_index_state.dart';
class ControllerIndexCubit extends Cubit<ControllerIndexState> {
ControllerIndexCubit({
required String selectedMetricLabel,
}) : super(ControllerIndexState(selectedMetricLabel: selectedMetricLabel));
void selectMetric(String label) {
emit(state.copyWith(selectedMetricLabel: label));
}
}
import 'package:equatable/equatable.dart';
class ControllerIndexState extends Equatable {
final String selectedMetricLabel;
const ControllerIndexState({
required this.selectedMetricLabel,
});
ControllerIndexState copyWith({
String? selectedMetricLabel,
}) {
return ControllerIndexState(
selectedMetricLabel: selectedMetricLabel ?? this.selectedMetricLabel,
);
}
@override
List<Object?> get props => [selectedMetricLabel];
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'cubit/controller_index_cubit.dart';
import 'cubit/controller_index_state.dart';
import 'widgets/monitoring_environment_detail_view.dart';
class MonitoringControllerView extends StatelessWidget {
const MonitoringControllerView({
super.key,
required this.metrics,
required this.selectedMetricLabel,
required this.onBack,
});
final List<MonitoringMetricBO> metrics;
final String selectedMetricLabel;
final VoidCallback onBack;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ControllerIndexCubit(
selectedMetricLabel: selectedMetricLabel,
),
child: ControllerIndexContent(
metrics: metrics,
onBack: onBack,
),
);
}
}
class ControllerIndexContent extends StatelessWidget {
const ControllerIndexContent({
super.key,
required this.metrics,
required this.onBack,
});
final List<MonitoringMetricBO> metrics;
final VoidCallback onBack;
@override
Widget build(BuildContext context) {
return BlocBuilder<ControllerIndexCubit, ControllerIndexState>(
builder: (context, state) {
return MonitoringEnvironmentDetailView(
metrics: metrics,
selectedLabel: state.selectedMetricLabel,
onBack: onBack,
onMetricSelected: context.read<ControllerIndexCubit>().selectMetric,
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
const _assetMetricBlue = 'lib/assets/monitoring/box_bg_325x372.png';
const _assetMetricGreen = 'lib/assets/monitoring/frame_card_green_325x372.png';
const _assetPanelWide = 'lib/assets/monitoring/frame_button_324x164.png';
const _assetPanelLarge = 'lib/assets/monitoring/frame_panel_674x509.png';
const _assetPanelTall = 'lib/assets/monitoring/box_bg_460x905.png';
const _assetButton = 'lib/assets/monitoring/frame_button_324x164.png';
const _assetArrowButton =
'lib/assets/monitoring/frame_button_arrows_561x637.png';
class MonitoringEnvironmentDetailView extends StatelessWidget {
const MonitoringEnvironmentDetailView({
super.key,
required this.metrics,
required this.selectedLabel,
required this.onBack,
required this.onMetricSelected,
});
final List<MonitoringMetricBO> metrics;
final String selectedLabel;
final VoidCallback onBack;
final ValueChanged<String> onMetricSelected;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 74,
child: Column(
children: [
Expanded(
flex: 42,
child: Row(
children: [
for (int index = 0; index < metrics.length; index++) ...[
Expanded(
child: _MetricControlCard(metric: metrics[index])),
if (index < metrics.length - 1) SizedBox(width: 24.w),
],
],
),
),
SizedBox(height: 24.h),
Expanded(
flex: 58,
child: Row(
children: [
Expanded(flex: 48, child: _buildModePanel()),
SizedBox(width: 24.w),
Expanded(flex: 23, child: _buildWindPanel()),
SizedBox(width: 24.w),
Expanded(flex: 29, child: _buildTimePanel()),
],
),
),
],
),
),
SizedBox(width: 24.w),
Expanded(
flex: 26,
child: Column(
children: [
Expanded(flex: 21, child: _buildCarePanel()),
SizedBox(height: 24.h),
const Expanded(
flex: 20,
child: _InfoPanel(
title: '新风进化',
actionText: 'Auto',
icon: Icons.cyclone,
body: 'CO₂超过设定值时自动启动外循环\n净化,当降至2000ppm后,本\n功能自动关闭',
),
),
SizedBox(height: 24.h),
const Expanded(
flex: 20,
child: _InfoPanel(
title: '开放供氧',
actionText: 'Off',
icon: Icons.air,
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。',
warning: '*保持设备周围空气相对禁止,严禁烟火',
),
),
SizedBox(height: 24.h),
Expanded(flex: 26, child: _buildOxygenTimerPanel()),
],
),
),
],
);
}
Widget _buildModePanel() {
const items = [
_ModeItem(label: '外循环', icon: Icons.sync, active: true),
_ModeItem(label: '检查灯', icon: Icons.light),
_ModeItem(label: '照明灯', icon: Icons.lightbulb),
_ModeItem(label: '内循环', icon: Icons.autorenew),
_ModeItem(label: '蓝光', icon: Icons.wb_sunny_outlined),
_ModeItem(label: '红光', icon: Icons.wb_sunny),
];
return _PanelFrame(
backgroundAsset: _assetPanelLarge,
padding: EdgeInsets.all(28.w),
child: GridView.count(
crossAxisCount: 3,
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,
),
],
),
);
}
Widget _buildWindPanel() {
return _PanelFrame(
backgroundAsset: _assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _PanelTitle('风速调节'),
SizedBox(height: 24.h),
const Expanded(
child: _WideModeButton(label: '睡眠模式', icon: Icons.nightlight_round),
),
SizedBox(height: 18.h),
const Expanded(
child: _WideModeButton(label: '舒适模式', icon: Icons.favorite_border),
),
SizedBox(height: 18.h),
const Expanded(
child: _WideModeButton(label: '强劲模式', icon: Icons.air)),
],
),
);
}
Widget _buildTimePanel() {
return _PanelFrame(
backgroundAsset: _assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _PanelTitle('设置雾化时间'),
SizedBox(height: 18.h),
const Expanded(child: _DigitalValueBox(value: '00', unit: '分钟')),
Divider(color: Colors.white.withValues(alpha: 0.16), height: 36.h),
const _PanelTitle('设置消毒时间'),
SizedBox(height: 18.h),
const Expanded(
child: _DigitalValueBox(
value: '14',
unit: '分钟',
active: true,
),
),
],
),
);
}
Widget _buildCarePanel() {
return _PanelFrame(
backgroundAsset: _assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _PanelTitle('护理等级'),
const Spacer(),
Container(
height: 20.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.r),
gradient: const LinearGradient(
colors: [
Color(0xFFDCEFFC),
Color(0xFF71FFCF),
Color(0xFFE4CF1D),
Color(0xFFFFA11B),
Color(0xFFFF5945),
Color(0xFFFFFFFF),
],
),
),
),
SizedBox(height: 24.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
for (final item in const ['关闭', '三级', '二级', '一级', '特级'])
Text(
item,
style: TextStyle(color: Colors.white, fontSize: 24.sp),
),
],
),
],
),
);
}
Widget _buildOxygenTimerPanel() {
return _PanelFrame(
backgroundAsset: _assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _PanelTitle('供氧计时'),
const Spacer(),
Row(
children: [
Text(
'00:00:00',
style: TextStyle(
color: Colors.white,
fontSize: 42.sp,
letterSpacing: 2,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
const _Pill(text: '清零'),
],
),
Divider(color: Colors.white.withValues(alpha: 0.16), height: 38.h),
Text(
'ICU舱设置重置',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
const _BigActionButton(text: '初始化设置'),
],
),
);
}
}
class _MetricControlCard extends StatelessWidget {
const _MetricControlCard({required this.metric});
final MonitoringMetricBO metric;
@override
Widget build(BuildContext context) {
final isOxygen = metric.label.contains('氧气');
final isCo2 = metric.label.contains('二氧化碳') ||
metric.label.toUpperCase().contains('CO');
return _PanelFrame(
active: isOxygen,
backgroundAsset: isOxygen ? _assetMetricGreen : _assetMetricBlue,
padding: EdgeInsets.fromLTRB(32.w, 26.h, 32.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: _PanelTitle(_title(metric.label))),
_Pill(
text: isOxygen
? 'ON'
: isCo2
? 'PPM'
: 'Off',
active: isOxygen),
],
),
const Spacer(),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
metric.value,
style: TextStyle(
color: Colors.white,
fontSize: isCo2 ? 60.sp : 82.sp,
height: 0.9,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 12.w),
Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Text(
isCo2 ? '' : metric.unit,
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w600,
),
),
),
const Spacer(),
Image.asset(
_iconAsset(metric.label),
width: 72.w,
height: 72.w,
fit: BoxFit.contain,
),
],
),
Divider(color: Colors.white.withValues(alpha: 0.18), height: 28.h),
Text(
isCo2
? '告警阈值设定'
: metric.label.contains('温度')
? '温度设定'
: '湿度设定',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.78), fontSize: 26.sp),
),
const Spacer(),
Row(
children: [
_AdjustText('-', active: isOxygen || !isCo2),
const Spacer(),
Text(
isCo2
? '4000'
: isOxygen
? '90'
: metric.label.contains('湿度')
? '10'
: '24',
style: TextStyle(
color:
isCo2 ? const Color(0xFFFFC400) : const Color(0xFF00B7F4),
fontSize: 64.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
const _AdjustText('+', active: true),
],
),
],
),
);
}
static String _title(String label) {
if (label.contains('二氧化碳')) return '二氧化碳浓度';
return label;
}
}
class _InfoPanel extends StatelessWidget {
const _InfoPanel({
required this.title,
required this.actionText,
required this.icon,
required this.body,
this.warning,
});
final String title;
final String actionText;
final IconData icon;
final String body;
final String? warning;
@override
Widget build(BuildContext context) {
return _PanelFrame(
backgroundAsset: _assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 22.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: _PanelTitle(title)),
_Pill(text: actionText),
],
),
const Spacer(),
Row(
children: [
Icon(icon, color: const Color(0xFF6AC6F2), 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),
),
],
],
),
);
}
}
class _ModeButton extends StatelessWidget {
const _ModeButton({
required this.label,
required this.icon,
this.active = false,
});
final String label;
final IconData icon;
final bool active;
@override
Widget build(BuildContext context) {
final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA);
return Container(
decoration: _buttonDecoration(
active: active,
backgroundAsset: _assetArrowButton,
),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 18.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 56.w),
SizedBox(height: 20.h),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
}
class _WideModeButton extends StatelessWidget {
const _WideModeButton({
required this.label,
required this.icon,
});
final String label;
final IconData icon;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: _buttonDecoration(backgroundAsset: _assetButton),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Row(
children: [
Icon(icon, color: const Color(0xFF7DC4EA), size: 52.w),
SizedBox(width: 34.w),
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 26.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
}
class _DigitalValueBox extends StatelessWidget {
const _DigitalValueBox({
required this.value,
required this.unit,
this.active = false,
});
final String value;
final String unit;
final bool active;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: _buttonDecoration(
active: active,
backgroundAsset: _assetButton,
),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
value,
style: TextStyle(
color: active ? const Color(0xFFC8FFFF) : Colors.white,
fontSize: 46.sp,
letterSpacing: 2,
fontWeight: FontWeight.w500,
),
),
SizedBox(width: 28.w),
Text(
unit,
style: TextStyle(
color: Colors.white,
fontSize: 30.sp,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}
class _BigActionButton extends StatelessWidget {
const _BigActionButton({required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 64.h,
decoration: _buttonDecoration(backgroundAsset: _assetButton),
alignment: Alignment.center,
child: Text(
'《《 $text 》》',
style: TextStyle(
color: const Color(0xFFC9F8FF),
fontSize: 28.sp,
fontWeight: FontWeight.w700,
),
),
);
}
}
class _PanelFrame extends StatelessWidget {
const _PanelFrame({
required this.child,
required this.padding,
this.active = false,
this.backgroundAsset,
});
final Widget child;
final EdgeInsetsGeometry padding;
final bool active;
final String? backgroundAsset;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
padding: padding,
decoration: BoxDecoration(
color: backgroundAsset == null
? active
? const Color(0xFF003F14).withValues(alpha: 0.92)
: const Color(0xFF061C34).withValues(alpha: 0.92)
: null,
image: backgroundAsset == null
? null
: DecorationImage(
image: AssetImage(backgroundAsset!),
fit: BoxFit.fill,
),
border: backgroundAsset == null
? Border.all(
color:
active ? const Color(0xFF3EFF5F) : const Color(0xFF89DFFF),
width: 3.w,
)
: null,
borderRadius: BorderRadius.circular(8.r),
boxShadow: [
BoxShadow(
color: (active ? const Color(0xFF3EFF5F) : const Color(0xFF4FC8FF))
.withValues(alpha: 0.32),
blurRadius: 18.r,
spreadRadius: 2.r,
),
],
),
child: child,
);
}
}
class _PanelTitle extends StatelessWidget {
const _PanelTitle(this.text);
final String text;
@override
Widget build(BuildContext context) {
return Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w700,
),
);
}
}
class _Pill extends StatelessWidget {
const _Pill({
required this.text,
this.active = false,
});
final String text;
final bool active;
@override
Widget build(BuildContext context) {
return Container(
height: 40.h,
constraints: BoxConstraints(minWidth: 90.w),
padding: EdgeInsets.symmetric(horizontal: 18.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: active ? 0.14 : 0.06),
border: Border.all(
color: active ? const Color(0xFF52FFBB) : const Color(0xFF75C1DD),
width: 1.w,
),
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
text,
style: TextStyle(
color: active ? const Color(0xFF52FFBB) : const Color(0xFF75C1DD),
fontSize: 22.sp,
fontWeight: FontWeight.w700,
),
),
);
}
}
class _AdjustText extends StatelessWidget {
const _AdjustText(this.text, {this.active = false});
final String text;
final bool active;
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(
color: active ? const Color(0xFF20E5FF) : const Color(0xFF4B7191),
fontSize: 58.sp,
height: 1,
fontWeight: FontWeight.w700,
),
);
}
}
class _ModeItem {
final String label;
final IconData icon;
final bool active;
const _ModeItem({
required this.label,
required this.icon,
this.active = false,
});
}
BoxDecoration _buttonDecoration(
{bool active = false, String? backgroundAsset}) {
return BoxDecoration(
color: backgroundAsset == null
? active
? const Color(0xFF063F1A).withValues(alpha: 0.96)
: const Color(0xFF0A243F).withValues(alpha: 0.96)
: null,
image: backgroundAsset == null
? null
: DecorationImage(
image: AssetImage(backgroundAsset),
fit: BoxFit.fill,
),
border: backgroundAsset == null
? Border.all(
color: active ? const Color(0xFF2DF765) : const Color(0xFF66BDE8),
width: 2,
)
: null,
borderRadius: BorderRadius.circular(6),
);
}
String _iconAsset(String label) {
if (label.contains('温度')) {
return 'lib/assets/monitoring/cabin_temperature.png';
}
if (label.contains('湿度')) {
return 'lib/assets/monitoring/cabin_humidity.png';
}
if (label.contains('氧气')) {
return 'lib/assets/monitoring/oxygen_concentration.png';
}
return 'lib/assets/monitoring/carbon_dioxide_concentration.png';
}
...@@ -8,6 +8,7 @@ import 'package:laki_icu_app/enums/video_stream_mode_enum.dart'; ...@@ -8,6 +8,7 @@ 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/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/event_bus.dart'; import 'package:laki_icu_app/utils/event_bus.dart';
import '../controller/monitoring_controller_view.dart';
import 'cubit/monitoring_index_cubit.dart'; import 'cubit/monitoring_index_cubit.dart';
import 'cubit/monitoring_index_state.dart'; import 'cubit/monitoring_index_state.dart';
import 'widgets/bluetooth_bind_dialog.dart'; import 'widgets/bluetooth_bind_dialog.dart';
...@@ -39,6 +40,7 @@ class MonitoringIndexContent extends StatefulWidget { ...@@ -39,6 +40,7 @@ class MonitoringIndexContent extends StatefulWidget {
class _MonitoringIndexContentState extends State<MonitoringIndexContent> { class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
late final MonitoringIndexCubit _monitoringIndexCubit; late final MonitoringIndexCubit _monitoringIndexCubit;
StreamSubscription<OpenBluetoothScanEvent>? _bluetoothScanEventSub; StreamSubscription<OpenBluetoothScanEvent>? _bluetoothScanEventSub;
String? _selectedEnvironmentMetricLabel;
@override @override
void initState() { void initState() {
...@@ -69,6 +71,9 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> { ...@@ -69,6 +71,9 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
state.metrics.isEmpty) { state.metrics.isEmpty) {
return _buildError(state.error); return _buildError(state.error);
} }
if (_selectedEnvironmentMetricLabel != null) {
return _buildEnvironmentDetail(state);
}
return _buildMainArea(state); return _buildMainArea(state);
}, },
); );
...@@ -154,7 +159,10 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> { ...@@ -154,7 +159,10 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
for (int i = 0; i < displayMetrics.length; i++) { for (int i = 0; i < displayMetrics.length; i++) {
children.add( children.add(
Expanded( Expanded(
child: MonitoringMetricCard(metric: displayMetrics[i]), child: MonitoringMetricCard(
metric: displayMetrics[i],
onTap: () => _openEnvironmentDetail(displayMetrics[i]),
),
), ),
); );
if (i < displayMetrics.length - 1) { if (i < displayMetrics.length - 1) {
...@@ -166,6 +174,25 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> { ...@@ -166,6 +174,25 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
); );
} }
Widget _buildEnvironmentDetail(MonitoringIndexState state) {
final metrics = _environmentMetrics(state.metrics);
return MonitoringControllerView(
metrics: metrics,
selectedMetricLabel: _selectedEnvironmentMetricLabel!,
onBack: () {
setState(() {
_selectedEnvironmentMetricLabel = null;
});
},
);
}
void _openEnvironmentDetail(MonitoringMetricBO metric) {
setState(() {
_selectedEnvironmentMetricLabel = metric.label;
});
}
/// 模式切换回调 /// 模式切换回调
void _onToggleStreamMode() { void _onToggleStreamMode() {
final cubit = _monitoringIndexCubit; final cubit = _monitoringIndexCubit;
......
...@@ -127,7 +127,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> { ...@@ -127,7 +127,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
_buildPanelStatus(context, state), _buildPanelStatus(context, state),
SizedBox(height: 28.h), SizedBox(height: 28.h),
Expanded(child: _buildDeviceList(context, state)), Expanded(child: _buildDeviceList(context, state)),
_buildLatestData(state),
], ],
), ),
); );
...@@ -138,7 +137,7 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> { ...@@ -138,7 +137,7 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
? '自动发现附近可配对的监护舱设备,搜索中...' ? '自动发现附近可配对的监护舱设备,搜索中...'
: state.bluetoothDevices.isEmpty : state.bluetoothDevices.isEmpty
? '未发现设备,可重新搜索附近蓝牙设备' ? '未发现设备,可重新搜索附近蓝牙设备'
: '自动发现附近可配对的监护舱设备'; : '蓝牙扫描结束,共发现 ${state.bluetoothDevices.length}设备';
return Row( return Row(
children: [ children: [
...@@ -266,143 +265,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> { ...@@ -266,143 +265,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
await context.read<MonitoringIndexCubit>().unbindBluetoothDevice(); await context.read<MonitoringIndexCubit>().unbindBluetoothDevice();
} }
} }
Widget _buildLatestData(MonitoringIndexState state) {
final report = state.latestMcuReport;
final rawHex = state.latestBluetoothRawHex;
final message = state.bluetoothMessage;
if (report == null &&
(rawHex == null || rawHex.isEmpty) &&
(message == null || message.isEmpty)) {
return SizedBox(height: 10.h);
}
if (report != null) {
return _BluetoothReportSummary(report: report);
}
return Padding(
padding: EdgeInsets.only(top: 18.h),
child: Text(
rawHex != null && rawHex.isNotEmpty ? '最新数据: $rawHex' : message!,
style: TextStyle(
color: rawHex != null && rawHex.isNotEmpty
? const Color(0xFF00F6FF)
: Colors.white.withValues(alpha: 0.66),
fontSize: 22.sp,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
}
class _BluetoothReportSummary extends StatelessWidget {
const _BluetoothReportSummary({required this.report});
final McuReport report;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.only(top: 18.h),
padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 14.h),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.white.withValues(alpha: 0.12)),
),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
report.sn.isEmpty ? 'SN --' : 'SN ${report.sn}',
style: TextStyle(
color: const Color(0xFF00F6FF),
fontSize: 22.sp,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
_BluetoothReportMetric(
label: '温度',
value: report.mainTemperature.toStringAsFixed(1),
unit: '℃',
),
_BluetoothReportMetric(
label: '湿度',
value: '${report.humidity}',
unit: '%',
),
_BluetoothReportMetric(
label: '氧气',
value: '${report.oxygenConcentration}',
unit: '%',
),
_BluetoothReportMetric(
label: 'CO2',
value: '${report.co2Measurement}',
unit: 'ppm',
),
],
),
);
}
}
class _BluetoothReportMetric extends StatelessWidget {
const _BluetoothReportMetric({
required this.label,
required this.value,
required this.unit,
});
final String label;
final String value;
final String unit;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 128.w,
child: RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text: '$label ',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.58),
fontSize: 20.sp,
),
),
TextSpan(
text: value,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
fontWeight: FontWeight.w600,
),
),
TextSpan(
text: unit,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.72),
fontSize: 19.sp,
),
),
],
),
),
);
}
} }
class _BluetoothDeviceRow extends StatelessWidget { class _BluetoothDeviceRow extends StatelessWidget {
......
...@@ -3,7 +3,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; ...@@ -3,7 +3,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_switch/flutter_switch.dart'; import 'package:flutter_switch/flutter_switch.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart'; import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class MonitoringMetricCard extends StatelessWidget { class MonitoringMetricCard extends StatelessWidget {
final MonitoringMetricBO metric; final MonitoringMetricBO metric;
...@@ -13,25 +12,32 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -13,25 +12,32 @@ class MonitoringMetricCard extends StatelessWidget {
/// 开关切换回调 /// 开关切换回调
final ValueChanged<bool>? onSwitchToggle; final ValueChanged<bool>? onSwitchToggle;
/// 卡片点击回调
final VoidCallback? onTap;
const MonitoringMetricCard({ const MonitoringMetricCard({
super.key, super.key,
required this.metric, required this.metric,
this.switchValue = false, this.switchValue = false,
this.onSwitchToggle, this.onSwitchToggle,
this.onTap,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final displayLabel = _displayLabel(metric.label); final displayLabel = _displayLabel(metric.label);
return Container( return GestureDetector(
decoration: BoxDecoration( onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'), image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
// border: BoxBorder.all(width: 1.w,color: Colors.red) // border: BoxBorder.all(width: 1.w,color: Colors.red)
), ),
padding: EdgeInsets.only(left: 10.w,right: 10.w, top: 54.h), padding: EdgeInsets.only(left: 10.w, right: 10.w, top: 54.h),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
...@@ -61,7 +67,7 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -61,7 +67,7 @@ class MonitoringMetricCard extends StatelessWidget {
value: switchValue, value: switchValue,
onToggle: onSwitchToggle ?? (_) {}, onToggle: onSwitchToggle ?? (_) {},
activeColor: const Color(0xFF75C1DD), activeColor: const Color(0xFF75C1DD),
inactiveColor: Color(0xFF0A1B34), inactiveColor: const Color(0xFF0A1B34),
toggleSize: 22.w, toggleSize: 22.w,
padding: 4.w, padding: 4.w,
), ),
...@@ -71,7 +77,7 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -71,7 +77,7 @@ class MonitoringMetricCard extends StatelessWidget {
), ),
// const Spacer(), // const Spacer(),
Container( Container(
padding: EdgeInsets.only(left: 32.w,top: 24.h), padding: EdgeInsets.only(left: 32.w, top: 24.h),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
...@@ -98,17 +104,12 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -98,17 +104,12 @@ class MonitoringMetricCard extends StatelessWidget {
), ),
], ],
), ),
), ),
SizedBox(height: 22.h), SizedBox(height: 22.h),
Container( Container(
width: 220.w, width: 220.w,
margin: EdgeInsets.only(left: 32.w,), margin: EdgeInsets.only(
decoration: BoxDecoration( left: 32.w,
// border: Border.all(
// color: Colors.red,
// width: 1.w
// )
), ),
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
...@@ -117,7 +118,7 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -117,7 +118,7 @@ class MonitoringMetricCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"设定24"+metric.unit, '设定24${metric.unit}',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 24.sp, fontSize: 24.sp,
...@@ -159,6 +160,7 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -159,6 +160,7 @@ class MonitoringMetricCard extends StatelessWidget {
) )
], ],
), ),
),
); );
} }
......
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