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,
);
},
);
}
}
...@@ -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 {
......
...@@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; ...@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; 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,151 +12,154 @@ class MonitoringMetricCard extends StatelessWidget { ...@@ -13,151 +12,154 @@ 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,
image: DecorationImage( behavior: HitTestBehavior.opaque,
image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'), child: Container(
fit: BoxFit.fill, decoration: const BoxDecoration(
), image: DecorationImage(
// border: BoxBorder.all(width: 1.w,color: Colors.red) image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'),
), fit: BoxFit.fill,
padding: EdgeInsets.only(left: 10.w,right: 10.w, top: 54.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SizedBox(width: 32.w),
Expanded(
flex: 3,
child: Text(
displayLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: FlutterSwitch(
width: 60.w,
height: 30.h,
value: switchValue,
onToggle: onSwitchToggle ?? (_) {},
activeColor: const Color(0xFF75C1DD),
inactiveColor: Color(0xFF0A1B34),
toggleSize: 22.w,
padding: 4.w,
),
),
),
],
), ),
// const Spacer(), // border: BoxBorder.all(width: 1.w,color: Colors.red)
Container( ),
padding: EdgeInsets.only(left: 32.w,top: 24.h), padding: EdgeInsets.only(left: 10.w, right: 10.w, top: 54.h),
child: Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [ children: [
Text( SizedBox(width: 32.w),
metric.value, Expanded(
style: TextStyle( flex: 3,
color: const Color(0xFF9DF5FF),
fontSize: 90.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 18.w),
Padding(
padding: EdgeInsets.only(bottom: 3.h),
child: Text( child: Text(
metric.unit, displayLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 30.sp, fontSize: 28.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: FlutterSwitch(
width: 60.w,
height: 30.h,
value: switchValue,
onToggle: onSwitchToggle ?? (_) {},
activeColor: const Color(0xFF75C1DD),
inactiveColor: const Color(0xFF0A1B34),
toggleSize: 22.w,
padding: 4.w,
), ),
), ),
), ),
], ],
), ),
// const Spacer(),
), Container(
SizedBox(height: 22.h), padding: EdgeInsets.only(left: 32.w, top: 24.h),
Container( child: Row(
width: 220.w, crossAxisAlignment: CrossAxisAlignment.end,
margin: EdgeInsets.only(left: 32.w,), children: [
decoration: BoxDecoration( Text(
// border: Border.all( metric.value,
// color: Colors.red, style: TextStyle(
// width: 1.w color: const Color(0xFF9DF5FF),
// ) fontSize: 90.sp,
), height: 1,
child: Stack( fontWeight: FontWeight.bold,
clipBehavior: Clip.none, ),
children: [ ),
Column( SizedBox(width: 18.w),
crossAxisAlignment: CrossAxisAlignment.start, Padding(
children: [ padding: EdgeInsets.only(bottom: 3.h),
Text( child: Text(
"设定24"+metric.unit, metric.unit,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 24.sp, fontSize: 30.sp,
fontWeight: FontWeight.w500,
), ),
), ),
Container( ),
height: 5.h, ],
margin: EdgeInsets.only(top: 18.h), ),
decoration: BoxDecoration( ),
borderRadius: BorderRadius.circular(3.r), SizedBox(height: 22.h),
gradient: const LinearGradient( Container(
colors: [Color(0xFF20E5FF), Color(0x0020E5FF)], width: 220.w,
margin: EdgeInsets.only(
left: 32.w,
),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设定24${metric.unit}',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
), ),
), ),
Container(
height: 5.h,
margin: EdgeInsets.only(top: 18.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.r),
gradient: const LinearGradient(
colors: [Color(0xFF20E5FF), Color(0x0020E5FF)],
),
),
),
],
),
Positioned(
right: -65.w,
top: -20.h,
child: Image.asset(
_iconAsset(metric.label),
width: 102.w,
height: 102.w,
fit: BoxFit.contain,
), ),
],
),
Positioned(
right: -65.w,
top: -20.h,
child: Image.asset(
_iconAsset(metric.label),
width: 102.w,
height: 102.w,
fit: BoxFit.contain,
), ),
), ],
], ),
), ),
), Container(
Container( margin: EdgeInsets.only(left: 2.w),
margin: EdgeInsets.only(left: 2.w), child: Image.asset(
child: Image.asset(
'lib/assets/monitoring/box_bottom_char.png', 'lib/assets/monitoring/box_bottom_char.png',
// width: 140.w, // width: 140.w,
// height: 60.w, // height: 60.w,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
) )
], ],
),
), ),
); );
} }
......
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