Commit 59de0036 authored by akari's avatar akari

feat: 软件设置子页面

parent 72d5f64e
...@@ -45,8 +45,7 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> { ...@@ -45,8 +45,7 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> {
for (int i = 0; i < maxRetries; i++) { for (int i = 0; i < maxRetries; i++) {
try { try {
info = await _storageService.getBluetoothReadInfo(); info = await _storageService.getBluetoothReadInfo();
Log.warn( Log.warn('**************sn 第${i + 1}次获取************: ${info.sn}');
'**************sn 第${i + 1}次获取************: ${info.sn}');
if (info.hasSn) { if (info.hasSn) {
Log.warn('**************sn 获取成功,停止重试************'); Log.warn('**************sn 获取成功,停止重试************');
break; break;
...@@ -75,7 +74,28 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> { ...@@ -75,7 +74,28 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> {
BluetoothReadUpdated event, BluetoothReadUpdated event,
Emitter<BluetoothReadState> emit, Emitter<BluetoothReadState> emit,
) { ) {
emit(state.copyWith(info: event.info, clearError: true)); emit(state.copyWith(
info: _resolveDisplayInfo(event.info), clearError: true));
}
BluetoothReadModel _resolveDisplayInfo(BluetoothReadModel next) {
final currentSn = state.info.sn;
final nextSn = next.sn;
if (currentSn == null ||
currentSn.isEmpty ||
nextSn == null ||
nextSn.isEmpty ||
currentSn == nextSn) {
return next;
}
final currentSide = currentSn[currentSn.length - 1].toUpperCase();
if ((currentSide == 'L' || currentSide == 'R') &&
currentSn.substring(0, currentSn.length - 1) == nextSn) {
return next.copyWith(sn: currentSn);
}
return next;
} }
void _onCleared( void _onCleared(
......
...@@ -588,10 +588,10 @@ class DeviceControlBloc ...@@ -588,10 +588,10 @@ class DeviceControlBloc
// ==================== 护理等级控制事件处理 ==================== // ==================== 护理等级控制事件处理 ====================
/// [用户操作] 设置护理等级(发送给MCU)—— 持久化 /// [用户操作] 设置护理等级(发送给MCU)—— 持久化
void _onCareLevelChanged( Future<void> _onCareLevelChanged(
DeviceControlCareLevelChanged event, DeviceControlCareLevelChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
careLevel: state.data.careLevel.copyWith( careLevel: state.data.careLevel.copyWith(
currentLevel: event.level, currentLevel: event.level,
...@@ -602,7 +602,12 @@ class DeviceControlBloc ...@@ -602,7 +602,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(careLevelValue: event.level), userSettings: state.userSettings.copyWith(careLevelValue: event.level),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeControlValueToBluetooth(
identifier: 0xA8,
setValue: '${_careLevelProtocolValue(event.level)}',
errorPrefix: '护理等级',
emit: emit,
);
} }
// ==================== 新风进化控制事件处理 ==================== // ==================== 新风进化控制事件处理 ====================
...@@ -653,10 +658,10 @@ class DeviceControlBloc ...@@ -653,10 +658,10 @@ class DeviceControlBloc
// ==================== 雾化时间设置事件处理 ==================== // ==================== 雾化时间设置事件处理 ====================
/// [用户操作] 设置雾化时间(发送给MCU)—— 持久化 /// [用户操作] 设置雾化时间(发送给MCU)—— 持久化
void _onNebulizationTimeChanged( Future<void> _onNebulizationTimeChanged(
DeviceControlNebulizationTimeChanged event, DeviceControlNebulizationTimeChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
nebulizationTime: state.data.nebulizationTime.copyWith( nebulizationTime: state.data.nebulizationTime.copyWith(
setMinutes: event.minutes, setMinutes: event.minutes,
...@@ -668,16 +673,21 @@ class DeviceControlBloc ...@@ -668,16 +673,21 @@ class DeviceControlBloc
state.userSettings.copyWith(nebulizationTimeMinutes: event.minutes), state.userSettings.copyWith(nebulizationTimeMinutes: event.minutes),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeControlValueToBluetooth(
identifier: 0x0C,
setValue: event.minutes,
errorPrefix: '雾化时间',
emit: emit,
);
} }
// ==================== 消毒时间设置事件处理 ==================== // ==================== 消毒时间设置事件处理 ====================
/// [用户操作] 设置消毒时间(发送给MCU)—— 持久化 /// [用户操作] 设置消毒时间(发送给MCU)—— 持久化
void _onDisinfectionTimeChanged( Future<void> _onDisinfectionTimeChanged(
DeviceControlDisinfectionTimeChanged event, DeviceControlDisinfectionTimeChanged event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
disinfectionTime: state.data.disinfectionTime.copyWith( disinfectionTime: state.data.disinfectionTime.copyWith(
setMinutes: event.minutes, setMinutes: event.minutes,
...@@ -689,7 +699,12 @@ class DeviceControlBloc ...@@ -689,7 +699,12 @@ class DeviceControlBloc
state.userSettings.copyWith(disinfectionTimeMinutes: event.minutes), state.userSettings.copyWith(disinfectionTimeMinutes: event.minutes),
clearError: true, clearError: true,
)); ));
// TODO: 发送设置到MCU await _writeControlValueToBluetooth(
identifier: 0x0D,
setValue: event.minutes,
errorPrefix: '消毒时间',
emit: emit,
);
} }
// ==================== 开放供氧控制事件处理 ==================== // ==================== 开放供氧控制事件处理 ====================
...@@ -735,10 +750,10 @@ class DeviceControlBloc ...@@ -735,10 +750,10 @@ class DeviceControlBloc
} }
/// [用户操作] 清零供氧计时 /// [用户操作] 清零供氧计时
void _onOxygenTimerCleared( Future<void> _onOxygenTimerCleared(
DeviceControlOxygenTimerCleared event, DeviceControlOxygenTimerCleared event,
Emitter<DeviceControlState> emit, Emitter<DeviceControlState> emit,
) { ) async {
final newData = state.data.copyWith( final newData = state.data.copyWith(
oxygenTimer: const OxygenTimerBO( oxygenTimer: const OxygenTimerBO(
hours: '00', hours: '00',
...@@ -747,7 +762,12 @@ class DeviceControlBloc ...@@ -747,7 +762,12 @@ class DeviceControlBloc
), ),
); );
emit(state.copyWith(data: newData, clearError: true)); emit(state.copyWith(data: newData, clearError: true));
// TODO: 发送清零指令到MCU await _writeControlValueToBluetooth(
identifier: 0x29,
setValue: '0',
errorPrefix: '供氧累计时间清零',
emit: emit,
);
} }
// ==================== 循环模式控制事件处理 ==================== // ==================== 循环模式控制事件处理 ====================
...@@ -984,6 +1004,21 @@ class DeviceControlBloc ...@@ -984,6 +1004,21 @@ class DeviceControlBloc
} }
} }
int _careLevelProtocolValue(CareLevel level) {
switch (level) {
case CareLevel.off:
return 0;
case CareLevel.level3:
return 1;
case CareLevel.level2:
return 2;
case CareLevel.level1:
return 3;
case CareLevel.special:
return 4;
}
}
List<int>? _buildControlValueCommand({ List<int>? _buildControlValueCommand({
required int identifier, required int identifier,
required String setValue, required String setValue,
......
This diff is collapsed.
...@@ -150,8 +150,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -150,8 +150,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
).copyWith(sn: reportSn.isEmpty ? null : reportSn); ).copyWith(sn: reportSn.isEmpty ? null : reportSn);
if (bluetoothReadInfo.hasSn) { if (bluetoothReadInfo.hasSn) {
await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!); await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!);
await _connectMqttForDeviceSn(bluetoothReadInfo.sn!); unawaited(_fetchCabinDetailAndConnectVideo(bluetoothReadInfo.sn!));
await _fetchCabinDetailAndConnectVideo(bluetoothReadInfo.sn!);
} }
eventBus.emit( eventBus.emit(
BluetoothReadInfoChangedEvent(bluetoothReadInfo), BluetoothReadInfoChangedEvent(bluetoothReadInfo),
...@@ -232,18 +231,24 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -232,18 +231,24 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final boundDevice = await _storageService.getBoundBluetoothDevice(); final boundDevice = await _storageService.getBoundBluetoothDevice();
if (isClosed) return; if (isClosed) return;
final deviceId = boundDevice['deviceId']; final deviceId = boundDevice['deviceId'];
final bluetoothReadInfo = await _storageService.getBluetoothReadInfo();
if (isClosed) return;
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG load_bound_device ' '🔥🔥🔥 BLE_CONNECT_DEBUG load_bound_device '
'deviceId=$deviceId deviceName=${boundDevice['deviceName']} ' 'deviceId=$deviceId deviceName=${boundDevice['deviceName']} '
'hasSn=${bluetoothReadInfo.hasSn}', 'hasDeviceId=${deviceId != null && deviceId.isNotEmpty}',
); );
emit(state.copyWith( emit(state.copyWith(
boundBluetoothDeviceId: boundBluetoothDeviceId:
deviceId == null || deviceId.isEmpty ? null : deviceId, deviceId == null || deviceId.isEmpty ? null : deviceId,
boundBluetoothDeviceName: boundDevice['deviceName'], boundBluetoothDeviceName: boundDevice['deviceName'],
)); ));
if (deviceId != null && deviceId.isNotEmpty) {
_isAutoReconnectEnabled = true;
await _connectBoundBluetoothDevice();
if (isClosed) return;
}
final bluetoothReadInfo = await _storageService.getBluetoothReadInfo();
if (isClosed) return;
if (bluetoothReadInfo.hasSn) { if (bluetoothReadInfo.hasSn) {
final sn = _appendBluetoothSideToSn(bluetoothReadInfo.sn!); final sn = _appendBluetoothSideToSn(bluetoothReadInfo.sn!);
if (sn.isNotEmpty) { if (sn.isNotEmpty) {
...@@ -253,14 +258,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -253,14 +258,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
BluetoothReadInfoChangedEvent(bluetoothReadInfo.copyWith(sn: sn)), BluetoothReadInfoChangedEvent(bluetoothReadInfo.copyWith(sn: sn)),
); );
} }
_connectMqttForDeviceSn(sn);
_fetchCabinDetailAndConnectVideo(sn); _fetchCabinDetailAndConnectVideo(sn);
} }
} }
if (deviceId != null && deviceId.isNotEmpty) {
_isAutoReconnectEnabled = true;
_connectBoundBluetoothDevice();
}
} }
/// 页面初始化入口。 /// 页面初始化入口。
...@@ -288,8 +288,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -288,8 +288,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final cameraSn = state.cabinCameraSn; final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd; final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty || if (cameraSn == null ||
wifiPwd == null || wifiPwd.isEmpty) { cameraSn.isEmpty ||
wifiPwd == null ||
wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] WebRTC 凭证未就绪,跳过连接'); debugPrint('[MonitoringIndexCubit] WebRTC 凭证未就绪,跳过连接');
return; return;
} }
...@@ -313,8 +315,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -313,8 +315,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final cameraSn = state.cabinCameraSn; final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd; final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty || if (cameraSn == null ||
wifiPwd == null || wifiPwd.isEmpty) { cameraSn.isEmpty ||
wifiPwd == null ||
wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] P2P 凭证未就绪,跳过连接'); debugPrint('[MonitoringIndexCubit] P2P 凭证未就绪,跳过连接');
return; return;
} }
...@@ -461,6 +465,18 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -461,6 +465,18 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
} }
} }
Future<void> stopBluetoothScan() async {
await _bluetoothManager.stopScan();
if (!isClosed) {
emit(state.copyWith(isBluetoothScanning: false));
}
}
Future<void> connectBoundBluetoothDevice() async {
_isAutoReconnectEnabled = true;
await _connectBoundBluetoothDevice();
}
Future<void> bindBluetoothDevice(BluetoothScanDevice device) async { Future<void> bindBluetoothDevice(BluetoothScanDevice device) async {
if (state.isBluetoothBinding) return; if (state.isBluetoothBinding) return;
...@@ -472,6 +488,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -472,6 +488,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
)); ));
try { try {
await stopBluetoothScan();
await _bluetoothManager.connectToDevice(device); await _bluetoothManager.connectToDevice(device);
if (!_bluetoothManager.isConnected) { if (!_bluetoothManager.isConnected) {
throw StateError('蓝牙连接未就绪'); throw StateError('蓝牙连接未就绪');
...@@ -573,16 +590,15 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -573,16 +590,15 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
); );
try { try {
await _bluetoothManager.connectToDeviceId(deviceId, prescan: true); await _bluetoothManager.connectToDeviceId(deviceId);
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG auto_connect_success ' '🔥🔥🔥 BLE_CONNECT_DEBUG auto_connect_success '
'deviceId=$deviceId isConnected=${_bluetoothManager.isConnected}', 'deviceId=$deviceId isConnected=${_bluetoothManager.isConnected}',
); );
if (!isClosed) { if (!isClosed) {
emit(state.copyWith( emit(state.copyWith(
bluetoothMessage: _bluetoothManager.isConnected bluetoothMessage:
? '蓝牙设备已自动连接' _bluetoothManager.isConnected ? '蓝牙设备已自动连接' : '蓝牙自动连接未就绪,等待重试',
: '蓝牙自动连接未就绪,等待重试',
)); ));
} }
if (_bluetoothManager.isConnected) { if (_bluetoothManager.isConnected) {
......
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
...@@ -91,7 +93,11 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> { ...@@ -91,7 +93,11 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
), ),
), ),
IconButton( IconButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () {
final cubit = context.read<MonitoringIndexCubit>();
Navigator.of(context).pop();
unawaited(cubit.stopBluetoothScan());
},
icon: Icon(Icons.close, color: Colors.white, size: 48.w), icon: Icon(Icons.close, color: Colors.white, size: 48.w),
tooltip: '关闭', tooltip: '关闭',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
......
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class CustomerServiceIndexState extends Equatable { class CustomerServiceIndexState extends Equatable {
const CustomerServiceIndexState(); const CustomerServiceIndexState({
this.title = '联系我们',
this.hotlineLabel = '服务热线',
this.hotline = '400-0000-0000',
this.serviceTimeLabel = '服务时间',
this.serviceTime = '工作日 早上9:00 - 下午19:00',
});
final String title;
final String hotlineLabel;
final String hotline;
final String serviceTimeLabel;
final String serviceTime;
@override @override
List<Object?> get props => []; List<Object?> get props => [
title,
hotlineLabel,
hotline,
serviceTimeLabel,
serviceTime,
];
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../widgets/settings_panel_frame.dart'; import '../../widgets/settings_panel_frame.dart';
import '../cubit/customer_service_index_cubit.dart';
import '../cubit/customer_service_index_state.dart';
class CustomerServicePanel extends StatelessWidget { class CustomerServicePanel extends StatelessWidget {
const CustomerServicePanel({super.key}); const CustomerServicePanel({super.key});
...@@ -10,18 +13,77 @@ class CustomerServicePanel extends StatelessWidget { ...@@ -10,18 +13,77 @@ class CustomerServicePanel extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SettingsPanelFrame( return SettingsPanelFrame(
backgroundAsset: settingsAssetPanelMain, backgroundAsset: settingsAssetPanelMain,
padding: EdgeInsets.all(42.w), padding: EdgeInsets.fromLTRB(42.w, 64.h, 42.w, 48.h),
child: Align( child: BlocBuilder<CustomerServiceIndexCubit, CustomerServiceIndexState>(
alignment: Alignment.topLeft, builder: (context, state) {
child: Text( return Column(
'客户服务', crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: [
color: Colors.white, Text(
fontSize: 34.sp, state.title,
fontWeight: FontWeight.w800, style: TextStyle(
color: Colors.white,
fontSize: 38.sp,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 46.h),
_ContactLine(
label: state.hotlineLabel,
value: state.hotline,
),
SizedBox(height: 30.h),
_ContactLine(
label: state.serviceTimeLabel,
value: state.serviceTime,
),
SizedBox(height: 42.h),
Divider(color: const Color(0xFF4CA7D2).withValues(alpha: 0.36)),
],
);
},
),
);
}
}
class _ContactLine extends StatelessWidget {
const _ContactLine({
required this.label,
required this.value,
});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
width: 220.w,
child: Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 29.sp,
fontWeight: FontWeight.w800,
),
), ),
), ),
), Expanded(
child: Text(
value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 29.sp,
fontWeight: FontWeight.w800,
),
),
),
],
); );
} }
} }
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class RemoteControlAccount extends Equatable {
const RemoteControlAccount({
required this.phone,
});
final String phone;
@override
List<Object?> get props => [phone];
}
class RemoteControlIndexState extends Equatable { class RemoteControlIndexState extends Equatable {
const RemoteControlIndexState(); const RemoteControlIndexState({
this.productName = 'TSAAS ICU 动物医疗监护舱',
this.modelName = '730SE-CICU-A',
this.appName = 'TSAAS 移动APP',
this.adminAccounts = const [
RemoteControlAccount(phone: '130****1233'),
],
this.monitorAccounts = const [
RemoteControlAccount(phone: '130****1233'),
],
});
final String productName;
final String modelName;
final String appName;
final List<RemoteControlAccount> adminAccounts;
final List<RemoteControlAccount> monitorAccounts;
@override @override
List<Object?> get props => []; List<Object?> get props => [
productName,
modelName,
appName,
adminAccounts,
monitorAccounts,
];
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../widgets/settings_panel_frame.dart'; import '../../widgets/settings_panel_frame.dart';
import '../cubit/remote_control_index_cubit.dart';
import '../cubit/remote_control_index_state.dart';
class RemoteControlPanel extends StatelessWidget { class RemoteControlPanel extends StatelessWidget {
const RemoteControlPanel({super.key}); const RemoteControlPanel({super.key});
...@@ -10,17 +13,233 @@ class RemoteControlPanel extends StatelessWidget { ...@@ -10,17 +13,233 @@ class RemoteControlPanel extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SettingsPanelFrame( return SettingsPanelFrame(
backgroundAsset: settingsAssetPanelMain, backgroundAsset: settingsAssetPanelMain,
padding: EdgeInsets.all(42.w), padding: EdgeInsets.fromLTRB(42.w, 54.h, 42.w, 48.h),
child: Align( child: BlocBuilder<RemoteControlIndexCubit, RemoteControlIndexState>(
alignment: Alignment.topLeft, builder: (context, state) {
child: Text( return Column(
'远程控制', crossAxisAlignment: CrossAxisAlignment.stretch,
style: TextStyle( children: [
color: Colors.white, _RemoteHeader(state: state),
fontSize: 34.sp, SizedBox(height: 34.h),
fontWeight: FontWeight.w800, Divider(color: const Color(0xFF4CA7D2).withValues(alpha: 0.36)),
SizedBox(height: 32.h),
Expanded(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_AccountSection(
title: '管理员',
description: '可以设置更改设备各项参数,启动TSAAS等功能。',
accounts: state.adminAccounts,
),
SizedBox(height: 32.h),
_AccountSection(
title: '监测员',
description: '只能查看设备各项参数,记录诊疗状态等权限。',
accounts: state.monitorAccounts,
),
],
),
),
),
],
);
},
),
);
}
}
class _RemoteHeader extends StatelessWidget {
const _RemoteHeader({required this.state});
final RemoteControlIndexState state;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 20.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.productName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 42.sp,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 22.h),
Text(
state.modelName,
style: TextStyle(
color: Colors.white,
fontSize: 36.sp,
fontWeight: FontWeight.w800,
),
),
],
),
), ),
), ),
SizedBox(width: 40.w),
_AppQrCard(appName: state.appName),
],
);
}
}
class _AppQrCard extends StatelessWidget {
const _AppQrCard({required this.appName});
final String appName;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 226.w,
child: Column(
children: [
Container(
width: 190.w,
height: 190.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18.r),
),
),
SizedBox(height: 20.h),
Text(
appName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.w800,
),
),
],
),
);
}
}
class _AccountSection extends StatelessWidget {
const _AccountSection({
required this.title,
required this.description,
required this.accounts,
});
final String title;
final String description;
final List<RemoteControlAccount> accounts;
@override
Widget build(BuildContext context) {
final items = [
for (final account in accounts)
_AccountActionCard(
text: account.phone,
icon: Icons.manage_accounts_outlined,
),
const _AccountActionCard(
text: '新增账户',
icon: Icons.person_add_alt_1_outlined,
),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: 38.sp,
fontWeight: FontWeight.w800,
),
),
SizedBox(width: 24.w),
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: 6.h),
child: Text(
description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: const Color(0xFFAAEFFF),
fontSize: 17.sp,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
SizedBox(height: 28.h),
Wrap(
spacing: 20.w,
runSpacing: 18.h,
children: items,
),
],
);
}
}
class _AccountActionCard extends StatelessWidget {
const _AccountActionCard({
required this.text,
required this.icon,
});
final String text;
final IconData icon;
@override
Widget build(BuildContext context) {
return Container(
width: 235.w,
height: 92.h,
padding: EdgeInsets.symmetric(horizontal: 22.w),
decoration: settingsButtonDecoration(),
child: Row(
children: [
Icon(
icon,
color: const Color(0xFF78C8F5).withValues(alpha: 0.78),
size: 42.w,
),
SizedBox(width: 16.w),
Expanded(
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 22.sp,
fontWeight: FontWeight.w800,
),
),
),
],
), ),
); );
} }
......
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class SoftwareUpdateInfo extends Equatable {
const SoftwareUpdateInfo({
required this.title,
required this.currentVersion,
required this.latestVersion,
required this.updateNotes,
required this.actionText,
required this.canUpdate,
this.footerText,
});
final String title;
final String currentVersion;
final String latestVersion;
final List<String> updateNotes;
final String actionText;
final bool canUpdate;
final String? footerText;
@override
List<Object?> get props => [
title,
currentVersion,
latestVersion,
updateNotes,
actionText,
canUpdate,
footerText,
];
}
class SoftwareUpdateIndexState extends Equatable { class SoftwareUpdateIndexState extends Equatable {
const SoftwareUpdateIndexState(); const SoftwareUpdateIndexState({
this.productName = 'TSAAS ICU 动物医疗监护舱',
this.modelName = '730SE-CICU-A',
this.tabletSystem = const SoftwareUpdateInfo(
title: '平板系统',
currentVersion: 'V2.32.4',
latestVersion: 'V2.32.5',
updateNotes: [
'1.重置UI界面',
'2.优化已知bug',
],
actionText: '立即更新',
canUpdate: true,
),
this.deviceFirmware = const SoftwareUpdateInfo(
title: '设备固件OTA(实体按键)',
currentVersion: 'V2.32.4',
latestVersion: 'V2.32.4(当前版本)',
updateNotes: [
'修复bug',
],
actionText: '已是最新版本',
canUpdate: false,
footerText: '*固件升级时间较长,请勿断电',
),
});
final String productName;
final String modelName;
final SoftwareUpdateInfo tabletSystem;
final SoftwareUpdateInfo deviceFirmware;
@override @override
List<Object?> get props => []; List<Object?> get props => [
productName,
modelName,
tabletSystem,
deviceFirmware,
];
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../widgets/settings_panel_frame.dart'; import '../../widgets/settings_panel_frame.dart';
import '../cubit/software_update_index_cubit.dart';
import '../cubit/software_update_index_state.dart';
class SoftwareUpdatePanel extends StatelessWidget { class SoftwareUpdatePanel extends StatelessWidget {
const SoftwareUpdatePanel({super.key}); const SoftwareUpdatePanel({super.key});
...@@ -10,17 +13,275 @@ class SoftwareUpdatePanel extends StatelessWidget { ...@@ -10,17 +13,275 @@ class SoftwareUpdatePanel extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SettingsPanelFrame( return SettingsPanelFrame(
backgroundAsset: settingsAssetPanelMain, backgroundAsset: settingsAssetPanelMain,
padding: EdgeInsets.all(42.w), padding: EdgeInsets.fromLTRB(42.w, 54.h, 42.w, 48.h),
child: Align( child: BlocBuilder<SoftwareUpdateIndexCubit, SoftwareUpdateIndexState>(
alignment: Alignment.topLeft, builder: (context, state) {
child: Text( return Column(
'软件更新', crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_UpdateHeader(state: state),
SizedBox(height: 30.h),
Divider(color: const Color(0xFF4CA7D2).withValues(alpha: 0.36)),
SizedBox(height: 36.h),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: _UpdateColumn(info: state.tabletSystem)),
Container(
width: 1.w,
margin: EdgeInsets.symmetric(horizontal: 38.w),
color: const Color(0xFF3A9BD2).withValues(alpha: 0.42),
),
Expanded(child: _UpdateColumn(info: state.deviceFirmware)),
],
),
),
],
);
},
),
);
}
}
class _UpdateHeader extends StatelessWidget {
const _UpdateHeader({required this.state});
final SoftwareUpdateIndexState state;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 22.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.productName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 42.sp,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 22.h),
Text(
state.modelName,
style: TextStyle(
color: Colors.white,
fontSize: 36.sp,
fontWeight: FontWeight.w800,
),
),
],
),
),
),
SizedBox(width: 34.w),
const _CheckUpdateButton(),
],
);
}
}
class _CheckUpdateButton extends StatelessWidget {
const _CheckUpdateButton();
@override
Widget build(BuildContext context) {
return Container(
width: 245.w,
height: 104.h,
padding: EdgeInsets.symmetric(horizontal: 24.w),
decoration: settingsButtonDecoration(active: true),
child: Row(
children: [
Container(
width: 52.w,
height: 52.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF78C8F5).withValues(alpha: 0.86),
width: 3.w,
),
),
child: Icon(
Icons.arrow_upward,
color: const Color(0xFF78C8F5).withValues(alpha: 0.9),
size: 32.w,
),
),
SizedBox(width: 22.w),
Expanded(
child: Text(
'检查更新',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
fontWeight: FontWeight.w800,
),
),
),
],
),
);
}
}
class _UpdateColumn extends StatelessWidget {
const _UpdateColumn({required this.info});
final SoftwareUpdateInfo info;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
info.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 34.sp, fontSize: 38.sp,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
SizedBox(height: 42.h),
_VersionLine(label: '当前版本:', value: info.currentVersion),
SizedBox(height: 20.h),
_VersionLine(label: '最新版本:', value: info.latestVersion),
SizedBox(height: 22.h),
Text(
'更新内容:',
style: TextStyle(
color: Colors.white,
fontSize: 27.sp,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 18.h),
for (final note in info.updateNotes)
Padding(
padding: EdgeInsets.only(bottom: 6.h),
child: Text(
note,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w500,
),
),
),
const Spacer(),
Center(child: _UpdateActionButton(info: info)),
if (info.footerText != null) ...[
SizedBox(height: 18.h),
Center(
child: Text(
info.footerText!,
style: TextStyle(
color: Colors.white,
fontSize: 17.sp,
fontWeight: FontWeight.w700,
),
),
),
] else
SizedBox(height: 38.h),
],
);
}
}
class _VersionLine extends StatelessWidget {
const _VersionLine({
required this.label,
required this.value,
});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Text.rich(
TextSpan(
children: [
TextSpan(text: label),
TextSpan(text: value),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 26.sp,
fontWeight: FontWeight.w800,
),
);
}
}
class _UpdateActionButton extends StatelessWidget {
const _UpdateActionButton({required this.info});
final SoftwareUpdateInfo info;
@override
Widget build(BuildContext context) {
final foreground =
info.canUpdate ? Colors.white : Colors.white.withValues(alpha: 0.42);
return Container(
width: 345.w,
height: 70.h,
padding: EdgeInsets.symmetric(horizontal: 28.w),
decoration: settingsButtonDecoration(active: info.canUpdate),
child: Row(
children: [
Text(
'《《',
style: TextStyle(
color: const Color(0xFF67C9F5)
.withValues(alpha: info.canUpdate ? 0.72 : 0.34),
fontSize: 26.sp,
fontWeight: FontWeight.w700,
),
),
Expanded(
child: Text(
info.actionText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
color: foreground,
fontSize: 26.sp,
fontWeight: FontWeight.w800,
),
),
),
Text(
'》》',
style: TextStyle(
color: const Color(0xFF67C9F5)
.withValues(alpha: info.canUpdate ? 0.72 : 0.34),
fontSize: 26.sp,
fontWeight: FontWeight.w700,
),
),
],
), ),
); );
} }
......
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