Commit 2160220c authored by akari's avatar akari

feat: 中控页功能提交

parent 54acb255
......@@ -628,10 +628,10 @@ class DeviceControlBloc
// ==================== 风速调节控制事件处理 ====================
/// [用户操作] 设置风速模式(发送给MCU)—— 持久化
void _onWindSpeedModeChanged(
Future<void> _onWindSpeedModeChanged(
DeviceControlWindSpeedModeChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
windSpeed: state.data.windSpeed.copyWith(
currentMode: event.mode,
......@@ -642,7 +642,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(windSpeedModeValue: event.mode),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeControlValueToBluetooth(
identifier: 0xB3,
setValue: '${_windSpeedProtocolValue(event.mode)}',
errorPrefix: '风速调节',
emit: emit,
);
}
// ==================== 雾化时间设置事件处理 ====================
......@@ -690,10 +695,10 @@ class DeviceControlBloc
// ==================== 开放供氧控制事件处理 ====================
/// [用户操作] 设置开放供氧开关(发送给MCU)—— 持久化
void _onOpenOxygenSwitchChanged(
Future<void> _onOpenOxygenSwitchChanged(
DeviceControlOpenOxygenSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
openOxygen: state.data.openOxygen.copyWith(
isOn: event.isOn,
......@@ -704,7 +709,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(openOxygenIsOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x26,
isOn: event.isOn,
errorPrefix: '开放供氧',
emit: emit,
);
}
// ==================== 供氧计时事件处理 ====================
......@@ -743,10 +753,10 @@ class DeviceControlBloc
// ==================== 循环模式控制事件处理 ====================
/// [用户操作] 设置循环模式(发送给MCU)—— 持久化
void _onCirculationModeChanged(
Future<void> _onCirculationModeChanged(
DeviceControlCirculationModeChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
circulationMode: state.data.circulationMode.copyWith(
currentMode: event.mode,
......@@ -758,16 +768,21 @@ class DeviceControlBloc
state.userSettings.copyWith(circulationModeValue: event.mode),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x06,
isOn: event.mode == CirculationMode.external,
errorPrefix: event.mode == CirculationMode.external ? '外循环' : '内循环',
emit: emit,
);
}
// ==================== 灯光控制事件处理 ====================
/// [用户操作] 设置检查灯开关(发送给MCU)—— 持久化
void _onCheckLightSwitchChanged(
Future<void> _onCheckLightSwitchChanged(
DeviceControlCheckLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
checkLightOn: event.isOn,
......@@ -778,14 +793,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(checkLightOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x0A,
isOn: event.isOn,
errorPrefix: '检查灯',
emit: emit,
);
}
/// [用户操作] 设置照明灯开关(发送给MCU)—— 持久化
void _onIlluminationLightSwitchChanged(
Future<void> _onIlluminationLightSwitchChanged(
DeviceControlIlluminationLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
illuminationOn: event.isOn,
......@@ -796,14 +816,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(illuminationOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x09,
isOn: event.isOn,
errorPrefix: '照明灯',
emit: emit,
);
}
/// [用户操作] 设置蓝光灯开关(发送给MCU)—— 持久化
void _onBlueLightSwitchChanged(
Future<void> _onBlueLightSwitchChanged(
DeviceControlBlueLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
blueLightOn: event.isOn,
......@@ -814,14 +839,19 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(blueLightOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x1E,
isOn: event.isOn,
errorPrefix: '蓝光',
emit: emit,
);
}
/// [用户操作] 设置红光灯开关(发送给MCU)—— 持久化
void _onRedLightSwitchChanged(
Future<void> _onRedLightSwitchChanged(
DeviceControlRedLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
redLightOn: event.isOn,
......@@ -832,7 +862,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(redLightOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeSwitchValueToBluetooth(
identifier: 0x1F,
isOn: event.isOn,
errorPrefix: '红光',
emit: emit,
);
}
// ==================== 全局统计数据事件处理 ====================
......@@ -920,6 +955,35 @@ class DeviceControlBloc
}
}
Future<void> _writeSwitchValueToBluetooth({
required int identifier,
required bool isOn,
required String errorPrefix,
required Emitter<DeviceControlState> emit,
}) async {
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG switch_set_value '
'name=$errorPrefix isOn=$isOn',
);
await _writeControlValueToBluetooth(
identifier: identifier,
setValue: isOn ? '1' : '0',
errorPrefix: errorPrefix,
emit: emit,
);
}
int _windSpeedProtocolValue(WindSpeedMode mode) {
switch (mode) {
case WindSpeedMode.comfort:
return 0;
case WindSpeedMode.strong:
return 1;
case WindSpeedMode.sleep:
return 2;
}
}
List<int>? _buildControlValueCommand({
required int identifier,
required String setValue,
......
This diff is collapsed.
......@@ -2,6 +2,9 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'cubit/device_control_index_cubit.dart';
......@@ -130,23 +133,32 @@ class _DeviceControlLayout extends StatelessWidget {
SizedBox(height: 24.h),
Expanded(
flex: 58,
child: Row(
children: [
const Expanded(
flex: 48,
child: DeviceControlModePanel(),
),
SizedBox(width: 24.w),
const Expanded(
flex: 23,
child: DeviceControlWindPanel(),
),
SizedBox(width: 24.w),
const Expanded(
flex: 29,
child: DeviceControlTimePanel(),
),
],
child: LayoutBuilder(
builder: (context, constraints) {
final gap = 24.w;
final metricCardWidth =
(constraints.maxWidth - gap * 3) / 4;
final modePanelWidth = metricCardWidth * 2 + gap;
return Row(
children: [
SizedBox(
width: modePanelWidth,
child: const DeviceControlModePanel(),
),
SizedBox(width: gap),
SizedBox(
width: metricCardWidth,
child: const DeviceControlWindPanel(),
),
SizedBox(width: gap),
SizedBox(
width: metricCardWidth,
child: const DeviceControlTimePanel(),
),
],
);
},
),
),
],
......@@ -169,14 +181,27 @@ class _DeviceControlLayout extends StatelessWidget {
),
),
SizedBox(height: 24.h),
const Expanded(
Expanded(
flex: 20,
child: DeviceControlInfoPanel(
title: '开放供氧',
actionText: 'Off',
icon: Icons.air,
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。',
warning: '*保持设备周围空气相对禁止,严禁烟火',
child:
BlocBuilder<DeviceControlBloc, DeviceControlState>(
buildWhen: (previous, current) =>
previous.data.openOxygen !=
current.data.openOxygen,
builder: (context, state) {
final isOn = state.data.openOxygen.isOn;
return DeviceControlInfoPanel(
title: '开放供氧',
actionText: isOn ? 'ON' : 'Off',
active: isOn,
icon: Icons.air,
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。',
warning: '*保持设备周围空气相对禁止,严禁烟火',
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlOpenOxygenSwitchChanged(!isOn),
),
);
},
),
),
SizedBox(height: 24.h),
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
......@@ -9,43 +14,174 @@ class DeviceControlCarePanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DeviceControlPanelFrame(
backgroundAsset: assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('护理等级'),
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),
return BlocBuilder<DeviceControlBloc, DeviceControlState>(
buildWhen: (previous, current) =>
previous.data.careLevel != current.data.careLevel,
builder: (context, state) {
final currentLevel = state.data.careLevel.currentLevel;
return DeviceControlPanelFrame(
backgroundAsset: assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 26.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('护理等级'),
const Spacer(),
_CareLevelBar(currentLevel: currentLevel),
SizedBox(height: 24.h),
Row(
children: [
for (final item in _careLevelItems)
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(item.level),
),
child: Text(
item.label,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
),
),
),
),
],
),
),
],
),
SizedBox(height: 24.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
);
},
);
}
}
class _CareLevelBar extends StatelessWidget {
const _CareLevelBar({required this.currentLevel});
final CareLevel currentLevel;
@override
Widget build(BuildContext context) {
final activeIndex = _careLevelItems.indexWhere(
(item) => item.level == currentLevel,
);
return LayoutBuilder(
builder: (context, constraints) {
final segmentGap = 4.w;
final knobSize = 54.w;
final segmentWidth =
(constraints.maxWidth - segmentGap * (_careLevelItems.length - 1)) /
_careLevelItems.length;
final knobLeft = (segmentWidth + segmentGap) * activeIndex +
segmentWidth -
knobSize / 2;
return SizedBox(
height: knobSize,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerLeft,
children: [
for (final item in const ['关闭', '三级', '二级', '一级', '特级'])
Text(
item,
style: TextStyle(color: Colors.white, fontSize: 24.sp),
Positioned(
left: 0,
right: 0,
top: (knobSize - 20.h) / 2,
child: Row(
children: [
for (var index = 0;
index < _careLevelItems.length;
index++) ...[
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () =>
context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(
_careLevelItems[index].level,
),
),
child: Container(
height: 20.h,
decoration: BoxDecoration(
color: index <= activeIndex
? _careLevelItems[index].color
: const Color(0xFF6A7481),
borderRadius: _segmentRadius(index),
boxShadow: index <= activeIndex
? [
BoxShadow(
color: _careLevelItems[index]
.color
.withValues(alpha: 0.35),
blurRadius: 8.r,
),
]
: null,
),
),
),
),
if (index < _careLevelItems.length - 1)
SizedBox(width: segmentGap),
],
],
),
),
Positioned(
left: knobLeft.clamp(0, constraints.maxWidth - knobSize),
child: Container(
width: knobSize,
height: knobSize,
decoration: BoxDecoration(
color: const Color(0xFFF7F7F7),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.white.withValues(alpha: 0.72),
blurRadius: 10.r,
),
],
),
),
),
],
),
],
),
);
},
);
}
BorderRadius _segmentRadius(int index) {
final radius = Radius.circular(12.r);
if (index == 0) {
return BorderRadius.horizontal(left: radius);
}
if (index == _careLevelItems.length - 1) {
return BorderRadius.horizontal(right: radius);
}
return BorderRadius.zero;
}
}
const _careLevelItems = [
_CareLevelItem('关闭', CareLevel.off, Color(0xFFE5F7FF)),
_CareLevelItem('三级', CareLevel.level3, Color(0xFF60F2B6)),
_CareLevelItem('二级', CareLevel.level2, Color(0xFFFFCE1B)),
_CareLevelItem('一级', CareLevel.level1, Color(0xFFFF8D2E)),
_CareLevelItem('特级', CareLevel.special, Color(0xFFFF554C)),
];
class _CareLevelItem {
const _CareLevelItem(this.label, this.level, this.color);
final String label;
final CareLevel level;
final Color color;
}
......@@ -12,6 +12,8 @@ class DeviceControlInfoPanel extends StatelessWidget {
required this.icon,
required this.body,
this.warning,
this.active = false,
this.onTap,
});
final String title;
......@@ -19,49 +21,61 @@ class DeviceControlInfoPanel extends StatelessWidget {
final IconData icon;
final String body;
final String? warning;
final bool active;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return DeviceControlPanelFrame(
backgroundAsset: assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 22.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: DeviceControlPanelTitle(title)),
DeviceControlPill(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,
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: DeviceControlPanelFrame(
backgroundAsset: assetPanelWide,
padding: EdgeInsets.fromLTRB(30.w, 22.h, 30.w, 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: DeviceControlPanelTitle(title)),
DeviceControlPill(text: actionText, active: active),
],
),
const Spacer(),
Row(
children: [
Icon(
icon,
color: active
? const Color(0xFF45F36F)
: 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,
),
),
],
),
if (warning != null) ...[
SizedBox(height: 8.h),
Text(
warning!,
style: TextStyle(
color: const Color(0xFFFFC400),
fontSize: 16.sp,
),
),
],
],
),
),
);
}
......
......@@ -5,6 +5,7 @@ import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/numeric_keyboard.dart';
import 'device_control_common.dart';
......@@ -64,6 +65,12 @@ class DeviceControlMetricCard extends StatelessWidget {
isOxygen: isOxygen,
isCo2: isCo2,
);
final setValueEventBuilder = _setValueEventBuilder(
isTemperature: isTemperature,
isHumidity: isHumidity,
isOxygen: isOxygen,
isCo2: isCo2,
);
return DeviceControlPanelFrame(
active: isOxygen,
......@@ -147,15 +154,30 @@ class DeviceControlMetricCard extends StatelessWidget {
),
),
const Spacer(),
Text(
setValue,
style: TextStyle(
color: isCo2
? const Color(0xFFFFC400)
: const Color(0xFF00B7F4),
fontSize: 64.sp,
height: 1,
fontWeight: FontWeight.bold,
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: setValueEventBuilder == null
? null
: () => _showSetValueKeyboard(
context,
initialValue: setValue,
decimalEnabled: isTemperature,
eventBuilder: setValueEventBuilder,
),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 18.w, vertical: 8.h),
child: Text(
setValue,
style: TextStyle(
color: isCo2
? const Color(0xFFFFC400)
: const Color(0xFF00B7F4),
fontSize: 64.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
),
),
const Spacer(),
......@@ -206,6 +228,46 @@ class DeviceControlMetricCard extends StatelessWidget {
}
return null;
}
static DeviceControlEvent Function(String value)? _setValueEventBuilder({
required bool isTemperature,
required bool isHumidity,
required bool isOxygen,
required bool isCo2,
}) {
if (isTemperature) {
return (value) => DeviceControlCabinTempSetValueChanged(value);
}
if (isHumidity) {
return (value) => DeviceControlCabinHumiditySetValueChanged(value);
}
if (isOxygen) {
return (value) => DeviceControlOxygenConcentrationSetValueChanged(value);
}
if (isCo2) {
return (value) =>
DeviceControlCO2ConcentrationAlarmThresholdChanged(value);
}
return null;
}
static Future<void> _showSetValueKeyboard(
BuildContext context, {
required String initialValue,
required bool decimalEnabled,
required DeviceControlEvent Function(String value) eventBuilder,
}) async {
final result = await NumericKeyboard.show(
context,
initialValue: initialValue == '--' ? '' : initialValue,
decimalEnabled: decimalEnabled,
);
if (!context.mounted || result == null) return;
final value = result.trim();
if (value.isEmpty || value == '.') return;
context.read<DeviceControlBloc>().add(eventBuilder(value));
}
}
class _MetricAdjustButton extends StatelessWidget {
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
......@@ -9,33 +14,97 @@ class DeviceControlModePanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
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 DeviceControlPanelFrame(
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,
return BlocBuilder<DeviceControlBloc, DeviceControlState>(
buildWhen: (previous, current) =>
previous.data.circulationMode != current.data.circulationMode ||
previous.data.lightControl != current.data.lightControl,
builder: (context, state) {
final circulationMode = state.data.circulationMode.currentMode;
final lightControl = state.data.lightControl;
final items = [
_ModeItem(
label: '外循环',
icon: Icons.sync,
active: circulationMode == CirculationMode.external,
event: const DeviceControlCirculationModeChanged(
CirculationMode.external,
),
],
),
),
_ModeItem(
label: '检查灯',
icon: Icons.light,
active: lightControl.checkLightOn,
event: DeviceControlCheckLightSwitchChanged(
!lightControl.checkLightOn,
),
),
_ModeItem(
label: '照明灯',
icon: Icons.lightbulb,
active: lightControl.illuminationOn,
event: DeviceControlIlluminationLightSwitchChanged(
!lightControl.illuminationOn,
),
),
_ModeItem(
label: '内循环',
icon: Icons.autorenew,
active: circulationMode == CirculationMode.internal,
event: const DeviceControlCirculationModeChanged(
CirculationMode.internal,
),
),
_ModeItem(
label: '蓝光',
icon: Icons.wb_sunny_outlined,
active: lightControl.blueLightOn,
event: DeviceControlBlueLightSwitchChanged(
!lightControl.blueLightOn,
),
),
_ModeItem(
label: '红光',
icon: Icons.wb_sunny,
active: lightControl.redLightOn,
event: DeviceControlRedLightSwitchChanged(
!lightControl.redLightOn,
),
),
];
return DeviceControlPanelFrame(
backgroundAsset: assetPanelLarge,
padding: EdgeInsets.fromLTRB(30.w, 30.h, 30.w, 30.h),
child: LayoutBuilder(
builder: (context, constraints) {
final horizontalGap = 22.w;
final verticalGap = 22.h;
final itemWidth = (constraints.maxWidth - horizontalGap * 2) / 3;
final itemHeight = (constraints.maxHeight - verticalGap) / 2;
return Wrap(
spacing: horizontalGap,
runSpacing: verticalGap,
children: [
for (final item in items)
SizedBox(
width: itemWidth,
height: itemHeight,
child: _ModeButton(
label: item.label,
icon: item.icon,
active: item.active,
onTap: () => context.read<DeviceControlBloc>().add(
item.event,
),
),
),
],
);
},
),
);
},
);
}
}
......@@ -45,37 +114,51 @@ class _ModeButton extends StatelessWidget {
required this.label,
required this.icon,
this.active = false,
this.onTap,
});
final String label;
final IconData icon;
final bool active;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA);
return Container(
decoration: deviceControlButtonDecoration(
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,
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
decoration: deviceControlButtonDecoration(
active: active,
backgroundAsset: assetArrowButton,
),
padding: EdgeInsets.fromLTRB(12.w, 6.h, 12.w, 16.h),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 28.h,
child: Icon(icon, color: color, size: 100.w),
),
),
],
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Text(
label,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w700,
),
),
),
],
),
),
);
}
......@@ -85,10 +168,12 @@ class _ModeItem {
final String label;
final IconData icon;
final bool active;
final DeviceControlEvent event;
const _ModeItem({
required this.label,
required this.icon,
required this.event,
this.active = false,
});
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/utils/numeric_keyboard.dart';
import 'device_control_common.dart';
......@@ -9,28 +14,97 @@ class DeviceControlTimePanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DeviceControlPanelFrame(
backgroundAsset: assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('设置雾化时间'),
SizedBox(height: 18.h),
const Expanded(
child: DeviceControlDigitalValueBox(value: '00', unit: '分钟'),
),
Divider(color: Colors.white.withValues(alpha: 0.16), height: 36.h),
const DeviceControlPanelTitle('设置消毒时间'),
SizedBox(height: 18.h),
const Expanded(
child: DeviceControlDigitalValueBox(
value: '14',
unit: '分钟',
active: true,
),
return BlocBuilder<DeviceControlBloc, DeviceControlState>(
buildWhen: (previous, current) =>
previous.data.nebulizationTime != current.data.nebulizationTime ||
previous.data.disinfectionTime != current.data.disinfectionTime,
builder: (context, state) {
final nebulizationMinutes = state.data.nebulizationTime.setMinutes;
final disinfectionMinutes = state.data.disinfectionTime.setMinutes;
return DeviceControlPanelFrame(
backgroundAsset: assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('设置雾化时间'),
SizedBox(height: 18.h),
Expanded(
child: _TimeValueButton(
value: nebulizationMinutes,
active: false,
onTap: () => _showTimeKeyboard(
context,
initialValue: nebulizationMinutes,
eventBuilder: (value) =>
DeviceControlNebulizationTimeChanged(value),
),
),
),
Divider(
color: Colors.white.withValues(alpha: 0.16),
height: 36.h,
),
const DeviceControlPanelTitle('设置消毒时间'),
SizedBox(height: 18.h),
Expanded(
child: _TimeValueButton(
value: disinfectionMinutes,
active: true,
onTap: () => _showTimeKeyboard(
context,
initialValue: disinfectionMinutes,
eventBuilder: (value) =>
DeviceControlDisinfectionTimeChanged(value),
),
),
),
],
),
],
);
},
);
}
static Future<void> _showTimeKeyboard(
BuildContext context, {
required String initialValue,
required DeviceControlEvent Function(String value) eventBuilder,
}) async {
final result = await NumericKeyboard.show(
context,
initialValue: initialValue == '--' ? '' : initialValue,
decimalEnabled: false,
);
if (!context.mounted || result == null) return;
final value = result.trim();
if (value.isEmpty) return;
context.read<DeviceControlBloc>().add(eventBuilder(value));
}
}
class _TimeValueButton extends StatelessWidget {
const _TimeValueButton({
required this.value,
required this.active,
required this.onTap,
});
final String value;
final bool active;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: DeviceControlDigitalValueBox(
value: value,
unit: '分钟',
active: active,
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_bloc.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_event.dart';
import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
......@@ -9,33 +14,62 @@ class DeviceControlWindPanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DeviceControlPanelFrame(
backgroundAsset: assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('风速调节'),
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),
return BlocBuilder<DeviceControlBloc, DeviceControlState>(
buildWhen: (previous, current) =>
previous.data.windSpeed != current.data.windSpeed,
builder: (context, state) {
final currentMode = state.data.windSpeed.currentMode;
return DeviceControlPanelFrame(
backgroundAsset: assetPanelTall,
padding: EdgeInsets.fromLTRB(34.w, 28.h, 34.w, 28.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DeviceControlPanelTitle('风速调节'),
SizedBox(height: 24.h),
Expanded(
child: _WideModeButton(
label: '睡眠模式',
icon: Icons.nightlight_round,
active: currentMode == WindSpeedMode.sleep,
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.sleep,
),
),
),
),
SizedBox(height: 18.h),
Expanded(
child: _WideModeButton(
label: '舒适模式',
icon: Icons.favorite_border,
active: currentMode == WindSpeedMode.comfort,
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.comfort,
),
),
),
),
SizedBox(height: 18.h),
Expanded(
child: _WideModeButton(
label: '强劲模式',
icon: Icons.air,
active: currentMode == WindSpeedMode.strong,
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.strong,
),
),
),
),
],
),
],
),
);
},
);
}
}
......@@ -44,34 +78,46 @@ class _WideModeButton extends StatelessWidget {
const _WideModeButton({
required this.label,
required this.icon,
this.active = false,
this.onTap,
});
final String label;
final IconData icon;
final bool active;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: deviceControlButtonDecoration(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,
final color = active ? const Color(0xFF45F36F) : const Color(0xFF7DC4EA);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
width: double.infinity,
decoration: deviceControlButtonDecoration(
active: active,
backgroundAsset: assetButton,
),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Row(
children: [
Icon(icon, color: color, size: 52.w),
SizedBox(width: 34.w),
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: color,
fontSize: 26.sp,
fontWeight: FontWeight.w600,
),
),
),
),
],
],
),
),
);
}
......
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