From 93e69a5e4beeec036478b1edf2c1a2bf8be96456 Mon Sep 17 00:00:00 2001 From: Songzex <2402265378@qq.com> Date: Mon, 1 Jun 2026 17:33:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=E7=9A=84=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/consts/http_api_consts.dart | 9 +- lib/core/router/app_router.dart | 8 + .../datasources/device_remote_datasource.dart | 132 +++- .../repositories/device_repository_impl.dart | 4 +- .../repositories/device_repository.dart | 2 +- .../get_device_status_data_usecase.dart | 4 +- .../presentation/bloc/device_status_bloc.dart | 6 +- .../bloc/device_status_event.dart | 7 +- .../bloc/device_status_state.dart | 6 +- .../pages/device_status_page.dart | 5 +- .../pages/drone_station_detail_page.dart | 615 ++++++++++++++++-- .../pages/robot_control_page.dart | 98 ++- .../presentation/pages/robot_list_page.dart | 267 ++++++-- .../presentation/widgets/robot_checklist.dart | 4 +- .../widgets/robot_control_panel.dart | 48 +- .../widgets/robot_header_card.dart | 60 +- .../presentation/widgets/robot_item_card.dart | 4 +- .../widgets/robot_status_bar.dart | 103 +-- .../presentation/pages/alarm_center_page.dart | 33 +- .../routes/alarm_center_routes.dart | 26 +- lib/main.dart | 136 +++- packages/cc_ui_kit/pubspec.yaml | 1 + pubspec.yaml | 3 + 23 files changed, 1312 insertions(+), 269 deletions(-) diff --git a/lib/core/consts/http_api_consts.dart b/lib/core/consts/http_api_consts.dart index 4a50d415..74f52695 100644 --- a/lib/core/consts/http_api_consts.dart +++ b/lib/core/consts/http_api_consts.dart @@ -19,16 +19,21 @@ class HttpApiConsts { // 获取光伏电站列表 static const String getSiteList = "$baseUrl/system/site/list"; + // 获取场站下的设备列表 + static const String getSiteDeviceList = "$baseUrl/iot/device/getSiteList"; + // 获取场站下的无人机机场列表 static const String getSiteUAVList = "$baseUrl/iot/UAV/getSiteUAVList"; // 获取UAV状态详情 static const String getUAVState = "$baseUrl/iot/UAV/getUAVState"; + // 切换摄像头获取视频流 + static const String changeCamera = "$baseUrl/iot/UAV/changeCamera"; // 获取设备位置 static const String getDeviceLocation = "$baseUrl/iot/device/userDevice"; - - + // 获取机器人列表 + static const String getRobotList = "$baseUrl/iot/device/getSiteList"; } diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index ac8d5f31..33e639c0 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:go_router/go_router.dart'; import 'package:maibu_satabot_v2/core/router/route_paths.dart'; import 'package:maibu_satabot_v2/features/ai/presentation/routes/ai_routes.dart'; @@ -16,6 +17,13 @@ import '../../features/main_container/presentation/main_wrapper.dart'; import 'go_router_refresh_stream.dart'; GoRouter createRouter(AuthCubit authCubit) { + // 调试:打印所有路由 + debugPrint('🔍 [Router] 开始创建路由配置...'); + debugPrint('🔍 [Router] AlarmCenterRoutes.routes 数量: ${AlarmCenterRoutes.routes.length}'); + for (var route in AlarmCenterRoutes.routes) { + debugPrint('🔍 [Router] 路由: ${route.toString()}'); + } + return GoRouter( initialLocation: RoutePaths.login, refreshListenable: GoRouterRefreshStream(authCubit.stream), diff --git a/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart b/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart index 2238d4bc..7bc20edb 100644 --- a/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart +++ b/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart @@ -1,3 +1,5 @@ +import 'package:dio/dio.dart'; +import '../../../../../core/consts/http_api_consts.dart'; import '../../domain/entities/device_entity.dart'; import '../../domain/entities/device_status_entity.dart'; import '../models/device_data_model.dart'; @@ -6,17 +8,22 @@ import '../models/device_status_data_model.dart'; /// 设备远程数据源抽象接口 abstract class DeviceRemoteDataSource { Future getDeviceStatus(); - Future> getDeviceList({String? typeFilter}); + Future> getDeviceList({ + int? siteId, + String? typeFilter, + }); } -/// 设备远程数据源实现类 - 模拟从 API 获取数据 +/// 设备远程数据源实现类 - 从真实 API 获取数据 class DeviceRemoteDataSourceImpl implements DeviceRemoteDataSource { + final Dio dio; + + DeviceRemoteDataSourceImpl(this.dio); + @override Future getDeviceStatus() async { - // 模拟网络延迟 await Future.delayed(const Duration(seconds: 1)); - // 模拟数据 return const DeviceStatusDataModel( total: 136, online: 122, @@ -26,11 +33,119 @@ class DeviceRemoteDataSourceImpl implements DeviceRemoteDataSource { } @override - Future> getDeviceList({String? typeFilter}) async { - // 模拟网络延迟 - await Future.delayed(const Duration(milliseconds: 500)); + Future> getDeviceList({ + int? siteId, + String? typeFilter, + }) async { + if (siteId == null) { + return _getMockDevices(typeFilter); + } - // 模拟数据 - 根据设计图 + try { + final List>> futures = [ + _fetchDevicesFromAPI(siteId), + _fetchUAVDevicesFromAPI(siteId), + ]; + + final results = await Future.wait(futures); + + final allDevices = []; + for (var result in results) { + allDevices.addAll(result); + } + + if (typeFilter != null && typeFilter != '全部') { + return allDevices.where((device) => device.type == typeFilter).toList(); + } + + return allDevices; + } catch (e) { + return _getMockDevices(typeFilter); + } + } + + Future> _fetchDevicesFromAPI(int siteId) async { + final response = await dio.get( + HttpApiConsts.getSiteDeviceList, + queryParameters: {'siteId': siteId, 'pageSize': 9999, 'pageNum': 1}, + ); + + if (response.statusCode != 200) { + throw Exception('网络请求失败: ${response.statusCode}'); + } + + final responseData = response.data; + + if (responseData['code'] != 200) { + throw Exception(responseData['msg'] ?? '业务异常'); + } + + final List rows = responseData['rows'] ?? []; + return rows.map((item) => _parseDeviceFromJson(item)).toList(); + } + + Future> _fetchUAVDevicesFromAPI(int siteId) async { + final response = await dio.get( + HttpApiConsts.getSiteUAVList, + queryParameters: {'siteId': siteId}, + ); + + if (response.statusCode != 200) { + throw Exception('网络请求失败: ${response.statusCode}'); + } + + final responseData = response.data; + + if (responseData['code'] != 200) { + throw Exception(responseData['msg'] ?? '业务异常'); + } + + final List rows = responseData['rows'] ?? []; + return rows.map((item) => _parseUAVDeviceFromJson(item)).toList(); + } + + DeviceDataModel _parseDeviceFromJson(Map json) { + return DeviceDataModel( + deviceId: json['deviceId']?.toString() ?? '', + name: json['deviceName'] ?? json['name'] ?? '', + type: json['deviceTypeName'] ?? json['type'] ?? '未知设备', + status: _getStatusText(json['status'] ?? 0), + power: (json['power'] as num?)?.toDouble() ?? 0, + todayEnergy: (json['todayEnergy'] as num?)?.toDouble() ?? 0, + temperature: (json['temperature'] as num?)?.toDouble(), + current: (json['current'] as num?)?.toDouble(), + voltage: (json['voltage'] as num?)?.toDouble(), + radiation: (json['radiation'] as num?)?.toDouble(), + ); + } + + DeviceDataModel _parseUAVDeviceFromJson(Map json) { + final onlineStatus = json['onlineStatus'] ?? 0; + return DeviceDataModel( + deviceId: json['device_sn']?.toString() ?? '', + name: json['callsign'] ?? '无人机机场', + type: '无人机机场', + status: onlineStatus == 1 ? '在线' : '离线', + power: (json['capacity_percent'] as num?)?.toDouble() ?? 0, + todayEnergy: 0, + temperature: (json['environment_temperature'] as num?)?.toDouble(), + ); + } + + String _getStatusText(int status) { + switch (status) { + case 1: + return '在线'; + case 2: + return '离线'; + case 3: + return '异常'; + default: + return '未知'; + } + } + + List _getMockDevices(String? typeFilter) { final allDevices = [ const DeviceDataModel( deviceId: '10001', @@ -85,7 +200,6 @@ class DeviceRemoteDataSourceImpl implements DeviceRemoteDataSource { ), ]; - // 根据类型过滤 if (typeFilter != null && typeFilter != '全部') { return allDevices.where((device) => device.type == typeFilter).toList(); } diff --git a/lib/features/v2/device_list/data/repositories/device_repository_impl.dart b/lib/features/v2/device_list/data/repositories/device_repository_impl.dart index 6097a4d4..8c54cb8b 100644 --- a/lib/features/v2/device_list/data/repositories/device_repository_impl.dart +++ b/lib/features/v2/device_list/data/repositories/device_repository_impl.dart @@ -16,8 +16,8 @@ class DeviceRepositoryImpl implements DeviceRepository { } @override - Future> getDeviceList({String? typeFilter}) async { - final dataModels = await remoteDataSource.getDeviceList(typeFilter: typeFilter); + Future> getDeviceList({int? siteId, String? typeFilter}) async { + final dataModels = await remoteDataSource.getDeviceList(siteId: siteId, typeFilter: typeFilter); return dataModels.map((model) => model.toEntity()).toList(); } } diff --git a/lib/features/v2/device_list/domain/repositories/device_repository.dart b/lib/features/v2/device_list/domain/repositories/device_repository.dart index 81879d86..35235b79 100644 --- a/lib/features/v2/device_list/domain/repositories/device_repository.dart +++ b/lib/features/v2/device_list/domain/repositories/device_repository.dart @@ -4,5 +4,5 @@ import '../entities/device_status_entity.dart'; /// 设备仓储抽象接口 abstract class DeviceRepository { Future getDeviceStatus(); - Future> getDeviceList({String? typeFilter}); + Future> getDeviceList({int? siteId, String? typeFilter}); } diff --git a/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart b/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart index bd4aad8f..ddc2517f 100644 --- a/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart +++ b/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart @@ -8,10 +8,10 @@ class GetDeviceStatusDataUseCase { const GetDeviceStatusDataUseCase({required this.repository}); - Future execute({String? typeFilter}) async { + Future execute({int? siteId, String? typeFilter}) async { try { final status = await repository.getDeviceStatus(); - final devices = await repository.getDeviceList(typeFilter: typeFilter); + final devices = await repository.getDeviceList(siteId: siteId, typeFilter: typeFilter); return DeviceStatusResponse(status: status, devices: devices); } catch (e) { throw Exception('获取设备数据失败: ${e.toString()}'); diff --git a/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart b/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart index aba6dedc..d4c95c54 100644 --- a/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart +++ b/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart @@ -21,11 +21,14 @@ class DeviceStatusBloc extends Bloc { emit(const DeviceStatusLoading()); try { - final response = await getDeviceStatusDataUseCase.execute(); + final response = await getDeviceStatusDataUseCase.execute( + siteId: event.siteId, + ); emit(DeviceStatusLoaded( deviceStatus: response.status, devices: response.devices, + siteId: event.siteId, )); } catch (e) { emit(DeviceStatusError(e.toString())); @@ -41,6 +44,7 @@ class DeviceStatusBloc extends Bloc { try { final response = await getDeviceStatusDataUseCase.execute( + siteId: currentState.siteId, typeFilter: currentState.selectedType == '全部' ? null : currentState.selectedType, diff --git a/lib/features/v2/device_list/presentation/bloc/device_status_event.dart b/lib/features/v2/device_list/presentation/bloc/device_status_event.dart index a967c3dd..ef7aff3b 100644 --- a/lib/features/v2/device_list/presentation/bloc/device_status_event.dart +++ b/lib/features/v2/device_list/presentation/bloc/device_status_event.dart @@ -8,7 +8,12 @@ abstract class DeviceStatusEvent extends Equatable { } class DeviceStatusLoadData extends DeviceStatusEvent { - const DeviceStatusLoadData(); + final int? siteId; + + const DeviceStatusLoadData({this.siteId}); + + @override + List get props => [siteId]; } class DeviceStatusRefresh extends DeviceStatusEvent { diff --git a/lib/features/v2/device_list/presentation/bloc/device_status_state.dart b/lib/features/v2/device_list/presentation/bloc/device_status_state.dart index d323822d..cdec2c61 100644 --- a/lib/features/v2/device_list/presentation/bloc/device_status_state.dart +++ b/lib/features/v2/device_list/presentation/bloc/device_status_state.dart @@ -22,12 +22,14 @@ class DeviceStatusLoaded extends DeviceStatusState { final List devices; final String selectedType; final String searchKeyword; + final int? siteId; const DeviceStatusLoaded({ required this.deviceStatus, required this.devices, this.selectedType = '全部', this.searchKeyword = '', + this.siteId, }); DeviceStatusLoaded copyWith({ @@ -35,17 +37,19 @@ class DeviceStatusLoaded extends DeviceStatusState { List? devices, String? selectedType, String? searchKeyword, + int? siteId, }) { return DeviceStatusLoaded( deviceStatus: deviceStatus ?? this.deviceStatus, devices: devices ?? this.devices, selectedType: selectedType ?? this.selectedType, searchKeyword: searchKeyword ?? this.searchKeyword, + siteId: siteId ?? this.siteId, ); } @override - List get props => [deviceStatus, devices, selectedType, searchKeyword]; + List get props => [deviceStatus, devices, selectedType, searchKeyword, siteId]; } class DeviceStatusError extends DeviceStatusState { diff --git a/lib/features/v2/device_list/presentation/pages/device_status_page.dart b/lib/features/v2/device_list/presentation/pages/device_status_page.dart index f24fa30a..22b21493 100644 --- a/lib/features/v2/device_list/presentation/pages/device_status_page.dart +++ b/lib/features/v2/device_list/presentation/pages/device_status_page.dart @@ -20,8 +20,11 @@ class DeviceStatusPage extends StatelessWidget { @override Widget build(BuildContext context) { + final selectedSite = sl().state.selectedSite; + final siteId = selectedSite?.id; + return BlocProvider( - create: (_) => sl()..add(const DeviceStatusLoadData()), + create: (_) => sl()..add(DeviceStatusLoadData(siteId: siteId)), child: const DeviceStatusView(), ); } diff --git a/lib/features/v2/device_list/presentation/pages/drone_station_detail_page.dart b/lib/features/v2/device_list/presentation/pages/drone_station_detail_page.dart index 3ce3c0d3..0e09a986 100644 --- a/lib/features/v2/device_list/presentation/pages/drone_station_detail_page.dart +++ b/lib/features/v2/device_list/presentation/pages/drone_station_detail_page.dart @@ -12,10 +12,7 @@ import 'drone_monitor_page.dart'; class DroneStationDetailPage extends StatefulWidget { final DroneStationEntity station; - const DroneStationDetailPage({ - super.key, - required this.station, - }); + const DroneStationDetailPage({super.key, required this.station}); @override State createState() => _DroneStationDetailPageState(); @@ -24,14 +21,21 @@ class DroneStationDetailPage extends StatefulWidget { class _DroneStationDetailPageState extends State { late DroneStationBloc _bloc; + // 悬浮视频监控状态 + bool showFloatingMonitor = false; + bool isFloatingIndoor = true; + Offset floatingPosition = const Offset(20, 200); + @override void initState() { super.initState(); _bloc = sl(); - _bloc.add(UAVDetailLoad( - gatewaySn: widget.station.gatewaySn, - deviceSn: widget.station.deviceSn, - )); + _bloc.add( + UAVDetailLoad( + gatewaySn: widget.station.gatewaySn, + deviceSn: widget.station.deviceSn, + ), + ); } @override @@ -76,18 +80,32 @@ class _DroneStationDetailPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, size: 48, color: Color(0xFF86909C)), + const Icon( + Icons.error_outline, + size: 48, + color: Color(0xFF86909C), + ), const SizedBox(height: 16), - Text(state.message, style: const TextStyle(fontSize: 14, color: Color(0xFF4E5969))), + Text( + state.message, + style: const TextStyle( + fontSize: 14, + color: Color(0xFF4E5969), + ), + ), const SizedBox(height: 16), ElevatedButton( onPressed: () { - _bloc.add(UAVDetailLoad( - gatewaySn: widget.station.gatewaySn, - deviceSn: widget.station.deviceSn, - )); + _bloc.add( + UAVDetailLoad( + gatewaySn: widget.station.gatewaySn, + deviceSn: widget.station.deviceSn, + ), + ); }, - style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF165DFF)), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF165DFF), + ), child: const Text('重试'), ), ], @@ -107,19 +125,81 @@ class _DroneStationDetailPageState extends State { } Widget _buildContent(UAVDetailEntity detail) { - return ListView( - padding: const EdgeInsets.all(16), + return Stack( children: [ - _buildAirportStatusCard(detail), - const SizedBox(height: 12), - _buildDroneStatusCard(detail), - const SizedBox(height: 12), - _buildQuickActions(), - const SizedBox(height: 20), + ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildAirportStatusCard(detail), + const SizedBox(height: 12), + _buildMonitorCard(), + const SizedBox(height: 12), + _buildDroneStatusCard(detail), + const SizedBox(height: 12), + _buildQuickActions(), + const SizedBox(height: 20), + ], + ), + _buildFloatingMonitor(), ], ); } + Widget _buildMonitorCard() { + return GestureDetector( + onTap: _goToMonitor, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF165DFF), + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow( + color: Color(0x1A165DFF), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(Icons.monitor, color: Colors.white, size: 28), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + '查看监控', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + SizedBox(height: 4), + Text( + '实时查看机场摄像头画面', + style: TextStyle(fontSize: 12, color: Color(0xCCFFFFFF)), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, color: Colors.white, size: 20), + ], + ), + ), + ); + } + Widget _buildAirportStatusCard(UAVDetailEntity detail) { return Container( padding: const EdgeInsets.all(16), @@ -127,7 +207,11 @@ class _DroneStationDetailPageState extends State { color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ - BoxShadow(color: Color(0x0D000000), blurRadius: 8, offset: Offset(0, 2)), + BoxShadow( + color: Color(0x0D000000), + blurRadius: 8, + offset: Offset(0, 2), + ), ], ), child: Column( @@ -135,28 +219,67 @@ class _DroneStationDetailPageState extends State { children: [ Row( children: [ - const Text('机场状态', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1D2129))), + const Text( + '机场状态', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF1D2129), + ), + ), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: detail.isOnline ? const Color(0xFF00B42A).withOpacity(0.1) : const Color(0xFFF53F3F).withOpacity(0.1), + color: detail.isOnline + ? const Color(0xFF00B42A).withOpacity(0.1) + : const Color(0xFFF53F3F).withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), - child: Text(detail.isOnline ? '在线' : '离线', style: TextStyle(fontSize: 12, color: detail.isOnline ? const Color(0xFF00B42A) : const Color(0xFFF53F3F), fontWeight: FontWeight.w500)), + child: Text( + detail.isOnline ? '在线' : '离线', + style: TextStyle( + fontSize: 12, + color: detail.isOnline + ? const Color(0xFF00B42A) + : const Color(0xFFF53F3F), + fontWeight: FontWeight.w500, + ), + ), ), ], ), const SizedBox(height: 16), const Divider(height: 1, color: Color(0xFFF2F3F5)), const SizedBox(height: 16), - _buildInfoRow('机场名称', detail.callsign.isNotEmpty ? detail.callsign : '未知'), + _buildInfoRow( + '机场名称', + detail.callsign.isNotEmpty ? detail.callsign : '未知', + ), _buildInfoRow('设备序列号', detail.deviceSn), _buildInfoRow('网关序列号', detail.gatewaySn), - _buildInfoRow('位置坐标', (detail.latitude != null && detail.longitude != null) ? '${detail.latitude}, ${detail.longitude}' : '未知'), - _buildInfoRow('电量', detail.capacityPercent != null ? '${detail.capacityPercent}%' : '未知'), - _buildInfoRow('环境温度', detail.environmentTemperature != null ? '${detail.environmentTemperature}°C' : '未知'), - _buildInfoRow('风速', detail.windSpeed != null ? '${detail.windSpeed} m/s' : '未知'), + _buildInfoRow( + '位置坐标', + (detail.latitude != null && detail.longitude != null) + ? '${detail.latitude}, ${detail.longitude}' + : '未知', + ), + _buildInfoRow( + '电量', + detail.capacityPercent != null + ? '${detail.capacityPercent}%' + : '未知', + ), + _buildInfoRow( + '环境温度', + detail.environmentTemperature != null + ? '${detail.environmentTemperature}°C' + : '未知', + ), + _buildInfoRow( + '风速', + detail.windSpeed != null ? '${detail.windSpeed} m/s' : '未知', + ), _buildInfoRow('降雨量', detail.rainfall ?? '未知'), _buildInfoRow('网络状态', detail.networkState?.toString() ?? '未知'), _buildPositionStateRow('位置状态', detail.positionState), @@ -168,45 +291,95 @@ class _DroneStationDetailPageState extends State { Widget _buildDroneStatusCard(UAVDetailEntity detail) { return GestureDetector( onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const DroneVideoControlPage())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DroneVideoControlPage(), + ), + ); }, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - boxShadow: const [BoxShadow(color: Color(0x0D000000), blurRadius: 8, offset: Offset(0, 2))], + boxShadow: const [ + BoxShadow( + color: Color(0x0D000000), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - const Text('无人机状态', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1D2129))), + const Text( + '无人机状态', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF1D2129), + ), + ), const Spacer(), Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( - color: detail.isDroneOnline ? const Color(0xFF00B42A).withOpacity(0.1) : const Color(0xFFF53F3F).withOpacity(0.1), + color: detail.isDroneOnline + ? const Color(0xFF00B42A).withOpacity(0.1) + : const Color(0xFFF53F3F).withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), - child: Text(detail.isDroneOnline ? '在线' : '离线', style: TextStyle(fontSize: 12, color: detail.isDroneOnline ? const Color(0xFF00B42A) : const Color(0xFFF53F3F), fontWeight: FontWeight.w500)), + child: Text( + detail.isDroneOnline ? '在线' : '离线', + style: TextStyle( + fontSize: 12, + color: detail.isDroneOnline + ? const Color(0xFF00B42A) + : const Color(0xFFF53F3F), + fontWeight: FontWeight.w500, + ), + ), ), ], ), const SizedBox(height: 16), - _buildInfoRow('无人机呼号', detail.droneCallsign.isNotEmpty ? detail.droneCallsign : '未知'), + _buildInfoRow( + '无人机呼号', + detail.droneCallsign.isNotEmpty ? detail.droneCallsign : '未知', + ), const SizedBox(height: 12), _buildInfoRow('设备序列号', detail.deviceSn), const SizedBox(height: 12), - _buildInfoRow('电量', detail.capacityPercent != null ? '${detail.capacityPercent}%' : '未知'), + _buildInfoRow( + '电量', + detail.capacityPercent != null + ? '${detail.capacityPercent}%' + : '未知', + ), const SizedBox(height: 12), - _buildInfoRow('高度', detail.height != null ? '${detail.height} m' : '未知'), + _buildInfoRow( + '高度', + detail.height != null ? '${detail.height} m' : '未知', + ), const SizedBox(height: 12), - _buildInfoRow('距离home点', detail.homeDistance != null ? '${detail.homeDistance} m' : '未知'), + _buildInfoRow( + '距离home点', + detail.homeDistance != null ? '${detail.homeDistance} m' : '未知', + ), const SizedBox(height: 12), - _buildInfoRow('实时电量', detail.liveCapacity != null ? '${detail.liveCapacity}%' : '未知'), - if (detail.gatewayCameraList != null && detail.gatewayCameraList!.isNotEmpty) ...[ + _buildInfoRow( + '实时电量', + detail.liveCapacity != null ? '${detail.liveCapacity}%' : '未知', + ), + if (detail.gatewayCameraList != null && + detail.gatewayCameraList!.isNotEmpty) ...[ const SizedBox(height: 12), _buildCameraListRow('网关摄像头', detail.gatewayCameraList!), ], @@ -222,22 +395,63 @@ class _DroneStationDetailPageState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - boxShadow: const [BoxShadow(color: Color(0x0D000000), blurRadius: 8, offset: Offset(0, 2))], + boxShadow: const [ + BoxShadow( + color: Color(0x0D000000), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('快捷操作', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1D2129))), + const Text( + '快捷操作', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF1D2129), + ), + ), const SizedBox(height: 16), Row( children: [ - Expanded(child: _buildQuickActionButton(icon: Icons.flight_takeoff, label: '开舱', color: const Color(0xFF165DFF), onTap: () {})), + Expanded( + child: _buildQuickActionButton( + icon: Icons.flight_takeoff, + label: '开舱', + color: const Color(0xFF165DFF), + onTap: () {}, + ), + ), const SizedBox(width: 12), - Expanded(child: _buildQuickActionButton(icon: Icons.flight, label: '起飞准备', color: const Color(0xFF165DFF), onTap: () {})), + Expanded( + child: _buildQuickActionButton( + icon: Icons.flight, + label: '起飞准备', + color: const Color(0xFF165DFF), + onTap: () {}, + ), + ), const SizedBox(width: 12), - Expanded(child: _buildQuickActionButton(icon: Icons.home, label: '返航', color: const Color(0xFFFF7D00), onTap: () {})), + Expanded( + child: _buildQuickActionButton( + icon: Icons.home, + label: '返航', + color: const Color(0xFFFF7D00), + onTap: () {}, + ), + ), const SizedBox(width: 12), - Expanded(child: _buildQuickActionButton(icon: Icons.monitor, label: '看监控', color: const Color(0xFF165DFF), onTap: _goToMonitor)), + Expanded( + child: _buildQuickActionButton( + icon: Icons.task, + label: '任务下发', + color: const Color(0xFF165DFF), + onTap: () {}, + ), + ), ], ), ], @@ -246,21 +460,253 @@ class _DroneStationDetailPageState extends State { } void _goToMonitor() { - Navigator.push(context, MaterialPageRoute(builder: (context) => DroneMonitorPage())); + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + '选择观看方式', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 24), + GestureDetector( + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DroneMonitorPage( + gatewaySn: widget.station.gatewaySn, + cameraIndex: '165-0-7', + ), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF165DFF), + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + children: [ + Icon(Icons.video_library, color: Colors.white), + SizedBox(width: 16), + Text( + '观看', + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + Spacer(), + Icon(Icons.arrow_forward_ios, color: Colors.white), + ], + ), + ), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () { + Navigator.pop(context); + setState(() => showFloatingMonitor = true); + }, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF2F3F5), + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + children: [ + Icon(Icons.picture_in_picture, color: Color(0xFF165DFF)), + SizedBox(width: 16), + Text( + '悬浮观看', + style: TextStyle( + fontSize: 16, + color: Color(0xFF1D2129), + fontWeight: FontWeight.w500, + ), + ), + Spacer(), + Icon(Icons.arrow_forward_ios, color: Color(0xFF86909C)), + ], + ), + ), + ), + const SizedBox(height: 16), + ], + ), + ); + }, + ); } - Widget _buildQuickActionButton({required IconData icon, required String label, required Color color, required VoidCallback onTap}) { + // 悬浮视频监控组件 + Widget _buildFloatingMonitor() { + if (!showFloatingMonitor) return const SizedBox(); + + return Positioned( + left: floatingPosition.dx, + top: floatingPosition.dy, + child: Draggable( + feedback: _monitorFloatCard(), + childWhenDragging: const SizedBox(), + onDragEnd: (details) { + setState(() { + final screenWidth = MediaQuery.of(context).size.width; + final screenHeight = MediaQuery.of(context).size.height; + floatingPosition = Offset( + details.offset.dx.clamp(0, screenWidth - 280), + details.offset.dy.clamp(0, screenHeight - 200), + ); + }); + }, + child: _monitorFloatCard(), + ), + ); + } + + Widget _monitorFloatCard() { + return Container( + width: 280, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow( + color: Color(0x20000000), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + children: [ + // 视频区域 + Container( + height: 140, + color: const Color(0xFF0D1117), + child: const Center( + child: Icon( + Icons.video_library, + size: 48, + color: Color(0xFF4E5969), + ), + ), + ), + // 控制区域 + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + // 室内/室外切换 + Expanded( + child: GestureDetector( + onTap: () => setState(() => isFloatingIndoor = true), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 12, + ), + decoration: BoxDecoration( + color: isFloatingIndoor + ? const Color(0xFF165DFF) + : const Color(0xFFF2F3F5), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '室内', + style: TextStyle( + fontSize: 12, + color: isFloatingIndoor + ? Colors.white + : const Color(0xFF86909C), + ), + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: GestureDetector( + onTap: () => setState(() => isFloatingIndoor = false), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 12, + ), + decoration: BoxDecoration( + color: !isFloatingIndoor + ? const Color(0xFF165DFF) + : const Color(0xFFF2F3F5), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '室外', + style: TextStyle( + fontSize: 12, + color: !isFloatingIndoor + ? Colors.white + : const Color(0xFF86909C), + ), + ), + ), + ), + ), + // 关闭按钮 + GestureDetector( + onTap: () => setState(() => showFloatingMonitor = false), + child: const Padding( + padding: EdgeInsets.all(4), + child: Icon( + Icons.close, + size: 18, + color: Color(0xFF86909C), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildQuickActionButton({ + required IconData icon, + required String label, + required Color color, + required VoidCallback onTap, + }) { return GestureDetector( onTap: onTap, child: Column( children: [ Container( - width: 48, height: 48, - decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)), + width: 48, + height: 48, + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), child: Icon(icon, color: color, size: 24), ), const SizedBox(height: 8), - Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF4E5969))), + Text( + label, + style: const TextStyle(fontSize: 12, color: Color(0xFF4E5969)), + ), ], ), ); @@ -274,15 +720,25 @@ class _DroneStationDetailPageState extends State { children: [ SizedBox( width: 80, - child: Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF86909C))), + child: Text( + label, + style: const TextStyle(fontSize: 12, color: Color(0xFF86909C)), + ), ), const SizedBox(width: 12), - const Text(':', style: TextStyle(fontSize: 12, color: Color(0xFFC0C4CC))), + const Text( + ':', + style: TextStyle(fontSize: 12, color: Color(0xFFC0C4CC)), + ), const SizedBox(width: 8), Expanded( child: Text( value, - style: const TextStyle(fontSize: 12, color: Color(0xFF1D2129), fontWeight: FontWeight.w500), + style: const TextStyle( + fontSize: 12, + color: Color(0xFF1D2129), + fontWeight: FontWeight.w500, + ), textAlign: TextAlign.left, ), ), @@ -294,26 +750,55 @@ class _DroneStationDetailPageState extends State { Widget _buildPositionStateRow(String label, PositionState? positionState) { String value = '未知'; if (positionState != null) { - value = 'GPS:${positionState.gpsNumber} RTX:${positionState.rtkNumber} 固定:${positionState.isFixed}'; + value = + 'GPS:${positionState.gpsNumber} RTX:${positionState.rtkNumber} 固定:${positionState.isFixed}'; } return Row( children: [ - Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF86909C))), + Text( + label, + style: const TextStyle(fontSize: 13, color: Color(0xFF86909C)), + ), const Spacer(), - Flexible(child: Text(value, style: const TextStyle(fontSize: 13, color: Color(0xFF1D2129), fontWeight: FontWeight.w500), textAlign: TextAlign.right)), + Flexible( + child: Text( + value, + style: const TextStyle( + fontSize: 13, + color: Color(0xFF1D2129), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.right, + ), + ), ], ); } Widget _buildCameraListRow(String label, List cameras) { - String value = cameras.map((c) => '${c.cameraIndex}:${c.cameraPosition}').join(' | '); + String value = cameras + .map((c) => '${c.cameraIndex}:${c.cameraPosition}') + .join(' | '); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF86909C))), + Text( + label, + style: const TextStyle(fontSize: 13, color: Color(0xFF86909C)), + ), const Spacer(), - Flexible(child: Text(value, style: const TextStyle(fontSize: 13, color: Color(0xFF1D2129), fontWeight: FontWeight.w500), textAlign: TextAlign.right)), + Flexible( + child: Text( + value, + style: const TextStyle( + fontSize: 13, + color: Color(0xFF1D2129), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.right, + ), + ), ], ); } -} \ No newline at end of file +} diff --git a/lib/features/v2/device_list/presentation/pages/robot_control_page.dart b/lib/features/v2/device_list/presentation/pages/robot_control_page.dart index d0873cf2..a132a439 100644 --- a/lib/features/v2/device_list/presentation/pages/robot_control_page.dart +++ b/lib/features/v2/device_list/presentation/pages/robot_control_page.dart @@ -49,22 +49,23 @@ class RobotControlPage extends StatelessWidget { const SizedBox(height: 8), RobotStatusBar(robot: robot), const SizedBox(height: 8), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - children: [ - RobotControlPanel(robot: robot), - const SizedBox(height: 8), - RobotTaskInfo(robot: robot), - ], + IntrinsicHeight( + child: Row( + children: [ + Expanded( + child: RobotControlPanel(robot: robot), ), - ), - const SizedBox(width: 8), - Expanded(child: RobotChecklist(robot: robot)), - ], + const SizedBox(width: 8), + Expanded( + child: RobotChecklist(robot: robot), + ), + ], + ), ), + const SizedBox(height: 8), + _buildPathPlanningCard(context), + const SizedBox(height: 8), + RobotTaskInfo(robot: robot), ], ), ), @@ -76,4 +77,73 @@ class RobotControlPage extends StatelessWidget { ), ); } + + Widget _buildPathPlanningCard(BuildContext context) { + return GestureDetector( + onTap: () { + // TODO: 跳转到路径规划页面 + debugPrint('点击路径规划'); + }, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow( + color: Color(0x0D000000), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF165DFF).withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.route, + color: Color(0xFF165DFF), + size: 24, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '路径规划', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF1D2129), + ), + ), + const SizedBox(height: 4), + Text( + '查看和编辑机器人巡检路径', + style: TextStyle( + fontSize: 12, + color: Color(0xFF86909C), + ), + ), + ], + ), + ), + const Icon( + Icons.arrow_forward_ios, + size: 16, + color: Color(0xFF86909C), + ), + ], + ), + ), + ); + } } diff --git a/lib/features/v2/device_list/presentation/pages/robot_list_page.dart b/lib/features/v2/device_list/presentation/pages/robot_list_page.dart index f9bc4bb5..148c9964 100644 --- a/lib/features/v2/device_list/presentation/pages/robot_list_page.dart +++ b/lib/features/v2/device_list/presentation/pages/robot_list_page.dart @@ -1,33 +1,114 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../core/di/injection.dart'; +import '../../../../devices/presentation/bloc/devices_cubit.dart'; +import '../../../../devices/domain/entities/device_entity.dart'; +import '../../../../v2/site/presentation/cubit/site_cubit.dart'; +import '../../data/models/robot_data_model.dart'; +import '../bloc/robot_list_bloc.dart'; +import '../bloc/robot_list_event.dart'; +import '../bloc/robot_list_state.dart'; import '../widgets/robot_item_card.dart'; import 'robot_control_page.dart'; import 'cleaning_weeding_robot_task_page.dart'; /// 机器人列表页面 -class RobotListPage extends StatefulWidget { +class RobotListPage extends StatelessWidget { const RobotListPage({super.key}); @override - State createState() => _RobotListPageState(); + Widget build(BuildContext context) { + final selectedSite = sl().state.selectedSite; + final siteId = selectedSite?.id; + + return BlocProvider( + create: (_) => sl()..add(RobotListLoadData(siteId: siteId)), + child: RobotListView(siteId: siteId), + ); + } } -class _RobotListPageState extends State { +/// 机器人列表视图 +class RobotListView extends StatefulWidget { + final int? siteId; + + const RobotListView({super.key, required this.siteId}); + + @override + State createState() => _RobotListViewState(); +} + +class _RobotListViewState extends State { String? _selectedType; // 选中的类型 @override Widget build(BuildContext context) { - return ListView( - padding: const EdgeInsets.only(bottom: 16), - children: [ - _buildStatsCard(), - _buildQuickActions(), - _buildCurrentTask(), - ..._buildRobotList(), - ], + return BlocBuilder( + builder: (context, state) { + if (state is RobotListLoading) { + return const Center( + child: CircularProgressIndicator( + color: Color(0xFF165DFF), + ), + ); + } + + if (state is RobotListError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + size: 48, + color: Color(0xFFF53F3F), + ), + const SizedBox(height: 16), + Text( + '加载失败: ${state.message}', + style: const TextStyle( + fontSize: 14, + color: Color(0xFF4E5969), + ), + ), + ], + ), + ); + } + + if (state is RobotListLoaded) { + return RefreshIndicator( + onRefresh: () async { + context.read().add(const RobotListRefresh()); + }, + color: const Color(0xFF165DFF), + child: ListView( + padding: const EdgeInsets.only(bottom: 16), + children: [ + _buildStatsCard(state), + _buildQuickActions(state), + _buildCurrentTask(state), + ..._buildRobotList(state), + ], + ), + ); + } + + return const SizedBox(); + }, ); } - Widget _buildStatsCard() { + Widget _buildStatsCard(RobotListLoaded state) { + // 计算各类型机器人数量 + final allRobots = state.robots; + final inspectionRobots = allRobots.where((r) => r.type.contains('巡检')).toList(); + final cleaningRobots = allRobots.where((r) => r.type.contains('清洗')).toList(); + final weedingRobots = allRobots.where((r) => r.type.contains('除草')).toList(); + + final onlineInspection = inspectionRobots.where((r) => r.status == '在线').length; + final onlineCleaning = cleaningRobots.where((r) => r.status == '在线').length; + final onlineWeeding = weedingRobots.where((r) => r.status == '在线').length; return Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 12), padding: const EdgeInsets.symmetric(vertical: 16), @@ -46,9 +127,16 @@ class _RobotListPageState extends State { children: [ Expanded( child: GestureDetector( - onTap: () => setState(() { - _selectedType = _selectedType == '巡检机器人' ? null : '巡检机器人'; - }), + onTap: () { + context.read().add( + RobotListChangeType( + _selectedType == '巡检机器人' ? null : '巡检机器人', + ), + ); + setState(() { + _selectedType = _selectedType == '巡检机器人' ? null : '巡检机器人'; + }); + }, child: Column( children: [ Container( @@ -84,12 +172,15 @@ class _RobotListPageState extends State { children: [ Image.asset('assets/images/xunjian.png', width: 48, height: 48), const SizedBox(width: 8), - const Text('12', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF165DFF))), + Text( + '${inspectionRobots.length}', + style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF165DFF)), + ), ], ), const SizedBox(height: 8), Text( - '在线9台', + '在线$onlineInspection台', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -105,9 +196,16 @@ class _RobotListPageState extends State { Container(width: 1, height: 100, color: const Color(0xFFE5E6EB)), Expanded( child: GestureDetector( - onTap: () => setState(() { - _selectedType = _selectedType == '清洗机器人' ? null : '清洗机器人'; - }), + onTap: () { + context.read().add( + RobotListChangeType( + _selectedType == '清洗机器人' ? null : '清洗机器人', + ), + ); + setState(() { + _selectedType = _selectedType == '清洗机器人' ? null : '清洗机器人'; + }); + }, child: Column( children: [ Container( @@ -143,12 +241,15 @@ class _RobotListPageState extends State { children: [ Image.asset('assets/images/qinxi.png', width: 48, height: 48), const SizedBox(width: 8), - const Text('8', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF165DFF))), + Text( + '${cleaningRobots.length}', + style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF165DFF)), + ), ], ), const SizedBox(height: 8), Text( - '在线6台', + '在线$onlineCleaning台', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -164,9 +265,16 @@ class _RobotListPageState extends State { Container(width: 1, height: 100, color: const Color(0xFFE5E6EB)), Expanded( child: GestureDetector( - onTap: () => setState(() { - _selectedType = _selectedType == '除草机器人' ? null : '除草机器人'; - }), + onTap: () { + context.read().add( + RobotListChangeType( + _selectedType == '除草机器人' ? null : '除草机器人', + ), + ); + setState(() { + _selectedType = _selectedType == '除草机器人' ? null : '除草机器人'; + }); + }, child: Column( children: [ Container( @@ -203,7 +311,7 @@ class _RobotListPageState extends State { Image.asset('assets/images/chucao.png', width: 48, height: 48), const SizedBox(width: 8), Text( - '6', + '${weedingRobots.length}', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -216,7 +324,7 @@ class _RobotListPageState extends State { ), const SizedBox(height: 8), Text( - '在线4台', + '在线$onlineWeeding台', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -234,7 +342,12 @@ class _RobotListPageState extends State { ); } - Widget _buildQuickActions() { + Widget _buildQuickActions(RobotListLoaded state) { + // 计算统计数据 + final onlineCount = state.robots.where((r) => r.status == '在线').length; + final workingCount = state.robots.where((r) => r.task != '待机中' && r.task != '充电中').length; + final standbyCount = state.robots.where((r) => r.task == '待机中').length; + final faultCount = state.robots.where((r) => r.status == '异常' || r.status == '离线').length; return Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 12), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), @@ -245,13 +358,13 @@ class _RobotListPageState extends State { ), child: Row( children: [ - _buildStatusItem(value: '19', label: '在线设备', valueColor: const Color(0xFF165DFF)), + _buildStatusItem(value: '$onlineCount', label: '在线设备', valueColor: const Color(0xFF165DFF)), Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem(value: '8', label: '作业中', valueColor: const Color(0xFF00B42A)), + _buildStatusItem(value: '$workingCount', label: '作业中', valueColor: const Color(0xFF00B42A)), Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem(value: '12', label: '待命', valueColor: const Color(0xFFFF7D00)), + _buildStatusItem(value: '$standbyCount', label: '待命', valueColor: const Color(0xFFFF7D00)), Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem(value: '2', label: '故障数', valueColor: const Color(0xFFF53F3F)), + _buildStatusItem(value: '$faultCount', label: '故障数', valueColor: const Color(0xFFF53F3F)), ], ), ); @@ -269,7 +382,19 @@ class _RobotListPageState extends State { ); } - Widget _buildCurrentTask() { + Widget _buildCurrentTask(RobotListLoaded state) { + // 获取第一个正在执行任务的机器人 + final workingRobot = state.robots.firstWhere( + (r) => r.task != '待机中' && r.task != '充电中', + orElse: () => state.robots.isNotEmpty ? state.robots.first : RobotDataModel( + name: '', + id: '', + type: '', + status: '', + battery: 0, + task: '无任务', + ), + ); return Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 12), padding: const EdgeInsets.all(16), @@ -305,7 +430,7 @@ class _RobotListPageState extends State { ], ), const SizedBox(height: 8), - _buildTaskInfo('机器人:', '巡检机器人-01'), + _buildTaskInfo('机器人:', workingRobot.name.isNotEmpty ? workingRobot.name : '无'), _buildTaskInfo('区域:', '逆变器区A区'), _buildTaskInfo('进度:', '62%'), _buildTaskInfo('预计完成:', '12:30'), @@ -333,47 +458,55 @@ class _RobotListPageState extends State { ); } - List _buildRobotList() { - final allRobots = [ - {'name': '巡检机器人-01', 'id': 'RB001', 'type': '巡检机器人', 'status': '在线', 'battery': 85.0, 'task': '逆变器区域巡检'}, - {'name': '巡检机器人-02', 'id': 'RB002', 'type': '巡检机器人', 'status': '在线', 'battery': 62.0, 'task': '组件区域巡检'}, - {'name': '巡检机器人-03', 'id': 'RB003', 'type': '巡检机器人', 'status': '离线', 'battery': 15.0, 'task': '充电中'}, - {'name': '清洗机器人-01', 'id': 'RB004', 'type': '清洗机器人', 'status': '在线', 'battery': 78.0, 'task': '组件清洗作业'}, - {'name': '清洗机器人-02', 'id': 'RB005', 'type': '清洗机器人', 'status': '在线', 'battery': 91.0, 'task': '待机中'}, - {'name': '除草机器人-01', 'id': 'RB006', 'type': '除草机器人', 'status': '在线', 'battery': 55.0, 'task': '光伏板除草'}, - ]; + List _buildRobotList(RobotListLoaded state) { + final allRobots = state.robots; - final filteredRobots = _selectedType == null ? allRobots : allRobots.where((robot) => robot['type'] == _selectedType).toList(); + // 根据选中的类型过滤 + final filteredRobots = state.selectedType == null + ? allRobots + : allRobots.where((robot) => robot.type.contains(state.selectedType!)).toList(); if (filteredRobots.isEmpty) { return [const Center(child: Padding(padding: EdgeInsets.all(32), child: Text('暂无机器人数据', style: TextStyle(fontSize: 14, color: Color(0xFF86909C)))))]; } return filteredRobots.map((robot) => RobotItemCard( - name: robot['name'] as String, - id: robot['id'] as String, - type: robot['type'] as String, - status: robot['status'] as String, - battery: robot['battery'] as double, - task: robot['task'] as String, + name: robot.name, + id: robot.id, + type: robot.type, + status: robot.status, + battery: robot.battery, + task: robot.task, onTap: () { - // 巡检机器人使用原来的控制页面 - // 清洗/除草机器人使用新的任务详情页面 - if (robot['type'] == '巡检机器人') { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RobotControlPage(robot: robot), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CleaningWeedingRobotTaskPage(robot: robot), - ), - ); - } + // 1. 将当前机器人设置为全局选中设备 + final device = DeviceEntity( + deviceName: robot.id, + productId: -1, + productName: robot.type, + tenantId: 0, + tenantName: '', + status: robot.status == '在线' ? 1 : 0, + onlineStatus: robot.status == '在线' ? 1 : 0, + ); + + context.read().selectDevice(device); + + // 2. 跳转到机器人控制页面 + final robotMap = { + 'name': robot.name, + 'id': robot.id, + 'type': robot.type, + 'status': robot.status, + 'battery': robot.battery, + 'task': robot.task, + }; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RobotControlPage(robot: robotMap), + ), + ); }, )).toList(); } diff --git a/lib/features/v2/device_list/presentation/widgets/robot_checklist.dart b/lib/features/v2/device_list/presentation/widgets/robot_checklist.dart index 3ee8aff7..7bf3bc2a 100644 --- a/lib/features/v2/device_list/presentation/widgets/robot_checklist.dart +++ b/lib/features/v2/device_list/presentation/widgets/robot_checklist.dart @@ -32,7 +32,7 @@ class RobotChecklist extends StatelessWidget { color: Color(0xFF1D2129), ), ), - const SizedBox(height: 8), + const SizedBox(height: 6), _buildCheckItem('逆变器', true), _buildCheckItem('汇流箱', true), _buildCheckItem('支架结构', true), @@ -45,7 +45,7 @@ class RobotChecklist extends StatelessWidget { Widget _buildCheckItem(String label, bool isDone, {bool isWarning = false}) { return Padding( - padding: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.only(bottom: 6), child: Row( children: [ Expanded( diff --git a/lib/features/v2/device_list/presentation/widgets/robot_control_panel.dart b/lib/features/v2/device_list/presentation/widgets/robot_control_panel.dart index 541f5aed..7f174a1d 100644 --- a/lib/features/v2/device_list/presentation/widgets/robot_control_panel.dart +++ b/lib/features/v2/device_list/presentation/widgets/robot_control_panel.dart @@ -1,4 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../../core/di/injection.dart'; +import '../../../../devices/presentation/bloc/devices_cubit.dart'; +import '../../../../devices/domain/entities/device_entity.dart'; +import '../../../../remote_control/presentation/bloc/remote_control_cubit.dart'; +import '../../../../remote_control/presentation/pages/remote_control_page.dart'; /// 机器人控制面板 class RobotControlPanel extends StatelessWidget { @@ -8,7 +14,46 @@ class RobotControlPanel extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return GestureDetector( + onTap: () { + debugPrint('👆 控制面板被点击'); + debugPrint('📋 robot 数据: $robot'); + + // 1. 将当前机器人设置为全局选中设备 + final device = DeviceEntity( + deviceName: robot['name']?.toString() ?? '', + productId: int.tryParse(robot['id']?.toString() ?? '-1') ?? -1, + productName: '割草机产品MC700', + tenantId: 0, + tenantName: '', + status: robot['status'] == '在线' ? 1 : 0, + onlineStatus: robot['status'] == '在线' ? 1 : 0, + ); + + debugPrint('✅ 设置选中设备: ${device.deviceName}'); + context.read().selectDevice(device); + + // 打印当前选中的设备信息 + final selectedDevice = context.read().state.selectedDevice; + debugPrint('📱 当前选中设备信息:'); + debugPrint(' - deviceName: ${selectedDevice?.deviceName}'); + debugPrint(' - productName: ${selectedDevice?.productName}'); + debugPrint(' - status: ${selectedDevice?.status}'); + debugPrint(' - onlineStatus: ${selectedDevice?.onlineStatus}'); + + // 2. 跳转到远程遥控页面 + debugPrint('🚀 准备跳转...'); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider( + create: (_) => sl()..startControlLoop(), + child: const RemoteControlPage(), + ), + ), + ); + }, + child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, @@ -124,6 +169,7 @@ class RobotControlPanel extends StatelessWidget { ), ], ), + ), ); } diff --git a/lib/features/v2/device_list/presentation/widgets/robot_header_card.dart b/lib/features/v2/device_list/presentation/widgets/robot_header_card.dart index 75b3a4b0..02750a3d 100644 --- a/lib/features/v2/device_list/presentation/widgets/robot_header_card.dart +++ b/lib/features/v2/device_list/presentation/widgets/robot_header_card.dart @@ -26,31 +26,30 @@ class RobotHeaderCard extends StatelessWidget { children: [ Row( children: [ - Text( - robot['name'] as String, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1D2129), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + robot['name'] as String, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF1D2129), + ), + ), + const SizedBox(height: 4), + Text( + 'ID: ${robot['id']}', + style: const TextStyle( + fontSize: 12, + color: Color(0xFF86909C), + ), + ), + ], ), ), const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: const Color(0xFF00B42A).withOpacity(0.1), - borderRadius: BorderRadius.circular(4), - ), - child: const Text( - '在线', - style: TextStyle( - fontSize: 12, - color: Color(0xFF00B42A), - fontWeight: FontWeight.w500, - ), - ), - ), - const Spacer(), // 信号强度 Row( children: [ @@ -71,6 +70,23 @@ class RobotHeaderCard extends StatelessWidget { ], ), const SizedBox(width: 12), + // 在线状态 + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFF00B42A).withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: const Text( + '在线', + style: TextStyle( + fontSize: 12, + color: Color(0xFF00B42A), + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(width: 12), // 设置图标 const Icon(Icons.settings, size: 20, color: Color(0xFF86909C)), ], diff --git a/lib/features/v2/device_list/presentation/widgets/robot_item_card.dart b/lib/features/v2/device_list/presentation/widgets/robot_item_card.dart index a6706950..251a04dd 100644 --- a/lib/features/v2/device_list/presentation/widgets/robot_item_card.dart +++ b/lib/features/v2/device_list/presentation/widgets/robot_item_card.dart @@ -72,10 +72,12 @@ class RobotItemCard extends StatelessWidget { Text( name, style: const TextStyle( - fontSize: 16, + fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF1D2129), ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( diff --git a/lib/features/v2/device_list/presentation/widgets/robot_status_bar.dart b/lib/features/v2/device_list/presentation/widgets/robot_status_bar.dart index b0a8d8d1..a659c040 100644 --- a/lib/features/v2/device_list/presentation/widgets/robot_status_bar.dart +++ b/lib/features/v2/device_list/presentation/widgets/robot_status_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../../../home/presentation/pages/running_status_page.dart'; /// 机器人状态栏 class RobotStatusBar extends StatelessWidget { @@ -8,47 +9,69 @@ class RobotStatusBar extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow( - color: Color(0x0D000000), - blurRadius: 8, - offset: Offset(0, 2), + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RunningStatusPage(), ), - ], - ), - child: Row( - children: [ - _buildStatusItem( - label: '速度', - value: '1.2', - unit: 'm/s', - ), - Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem( - label: '里程', - value: '2.36', - unit: 'km', - ), - Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem( - label: '电量', - value: '82', - unit: '%', - valueColor: const Color(0xFF00B42A), - ), - Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), - _buildStatusItem( - label: '信号', - value: '强', - unit: '', - valueColor: const Color(0xFF00B42A), - ), - ], + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow( + color: Color(0x0D000000), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + _buildStatusItem( + label: '速度', + value: '1.2', + unit: 'm/s', + ), + Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), + _buildStatusItem( + label: '里程', + value: '2.36', + unit: 'km', + ), + Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), + _buildStatusItem( + label: '电量', + value: '82', + unit: '%', + valueColor: const Color(0xFF00B42A), + ), + Container(width: 1, height: 40, color: const Color(0xFFE5E6EB)), + _buildStatusItem( + label: '信号', + value: '强', + unit: '', + valueColor: const Color(0xFF00B42A), + ), + ], + ), + ), + const SizedBox(width: 8), + const Icon( + Icons.arrow_forward_ios, + size: 16, + color: Color(0xFF86909C), + ), + ], + ), ), ); } diff --git a/lib/features/v2/waring_center/presentation/pages/alarm_center_page.dart b/lib/features/v2/waring_center/presentation/pages/alarm_center_page.dart index 24772919..5e1f204e 100644 --- a/lib/features/v2/waring_center/presentation/pages/alarm_center_page.dart +++ b/lib/features/v2/waring_center/presentation/pages/alarm_center_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_cubit.dart'; import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_state.dart'; import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart'; @@ -101,8 +102,7 @@ class _AlarmCenterPageState extends State { return AlarmItemCard( alarm: alarm, onTap: () { - // TODO: 跳转到告警详情页 - debugPrint('点击告警: ${alarm.title}'); + context.push('/alarm_center/detail/${alarm.id}'); }, ); }).toList(), @@ -213,6 +213,35 @@ class _AlarmCenterPageState extends State { ), ), const Spacer(), + // 铃铛图标(消息通知) + Stack( + children: [ + IconButton( + icon: const Icon( + Icons.notifications_none, + size: 24, + color: Color(0xFF1D2129), + ), + onPressed: () { + context.push('/message_center'); + }, + ), + // 未读消息红点 + Positioned( + right: 10, + top: 10, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFFF53F3F), + shape: BoxShape.circle, + ), + ), + ), + ], + ), + // 筛选图标 IconButton( icon: const Icon( Icons.filter_list, diff --git a/lib/features/v2/waring_center/presentation/routes/alarm_center_routes.dart b/lib/features/v2/waring_center/presentation/routes/alarm_center_routes.dart index 0a3b047d..31ac769c 100644 --- a/lib/features/v2/waring_center/presentation/routes/alarm_center_routes.dart +++ b/lib/features/v2/waring_center/presentation/routes/alarm_center_routes.dart @@ -1,13 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:maibu_satabot_v2/core/di/injection.dart'; +import 'package:maibu_satabot_v2/features/v2/message_center/presentation/pages/message_center_page.dart'; +import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_cubit.dart'; import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/pages/alarm_center_page.dart'; +import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/pages/alarm_detail_page.dart'; /// 告警中心路由配置 class AlarmCenterRoutes { static List get routes => [ GoRoute( - path: 'alarm_center', + path: '/alarm_center', name: 'alarmCenter', builder: (context, state) => const AlarmCenterPage(), + routes: [ + GoRoute( + path: 'detail/:alarmId', + name: 'alarmDetail', + builder: (context, state) { + final alarmId = state.pathParameters['alarmId']!; + return BlocProvider( + create: (_) => sl(), + child: AlarmDetailPage(alarmId: alarmId), + ); + }, + ), + ], + ), + // 消息中心页路由 + GoRoute( + path: '/message_center', + name: 'messageCenter', + builder: (context, state) => const MessageCenterPage(), ), ]; } diff --git a/lib/main.dart b/lib/main.dart index c1169b89..660ec151 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,17 +35,17 @@ void main() async { runZonedGuarded( () async { WidgetsFlutterBinding.ensureInitialized(); - + await FlutterPatcher.init(); await init(); final logger = sl(); await logger.init(); Bloc.observer = AppBlocObserver(logger); - + // ⚠️ 注意:不要在启动时清除补丁版本记录! // 补丁版本记录只在整包更新成功后才清除 // 如果在启动时清除,会导致差量更新后划掉App再进入时循环更新 - + runApp(const MyApp()); }, (error, stack) { @@ -62,7 +62,7 @@ class MyApp extends StatelessWidget { sl().appStarted(); final deviceStatusBloc = sl(); final localeCubit = sl(); - + return MultiBlocProvider( providers: [ BlocProvider(create: (_) => sl()), @@ -79,8 +79,12 @@ class MyApp extends StatelessWidget { BlocProvider.value(value: deviceStatusBloc), BlocProvider.value(value: localeCubit), BlocProvider.value(value: sl()), - BlocProvider(create: (_) => sl()), - BlocProvider(create: (_) => UpdateCubit(VersionCheckService())), + BlocProvider( + create: (_) => sl(), + ), + BlocProvider( + create: (_) => UpdateCubit(VersionCheckService()), + ), ], child: BlocBuilder( bloc: localeCubit, @@ -114,7 +118,8 @@ class _LifecycleListener extends StatefulWidget { State<_LifecycleListener> createState() => _LifecycleListenerState(); } -class _LifecycleListenerState extends State<_LifecycleListener> with WidgetsBindingObserver { +class _LifecycleListenerState extends State<_LifecycleListener> + with WidgetsBindingObserver { @override void initState() { super.initState(); @@ -172,7 +177,10 @@ class _UpdateCheckerState extends State<_UpdateChecker> { listener: (context, state) { if (state is UpdateFailure) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('更新失败: ${state.error}'), backgroundColor: Colors.red), + SnackBar( + content: Text('更新失败: ${state.error}'), + backgroundColor: Colors.red, + ), ); } }, @@ -188,23 +196,40 @@ class _UpdateCheckerState extends State<_UpdateChecker> { child: Center( child: Card( margin: const EdgeInsets.symmetric(horizontal: 30), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('🎉 发现新版本', - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + const Text( + '🎉 发现新版本', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), const SizedBox(height: 12), - Text('版本号: ${state.versionInfo.version}', - style: const TextStyle(fontSize: 16)), - Text('更新类型: ${state.versionInfo.updateType == "patch" ? "差量更新" : "整包更新"}', - style: const TextStyle(fontSize: 16, color: Colors.grey)), + Text( + '版本号: ${state.versionInfo.version}', + style: const TextStyle(fontSize: 16), + ), + Text( + '更新类型: ${state.versionInfo.updateType == "patch" ? "差量更新" : "整包更新"}', + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), if (state.versionInfo.updateDesc.isNotEmpty) ...[ const SizedBox(height: 8), - const Text('更新内容:', style: TextStyle(fontWeight: FontWeight.bold)), + const Text( + '更新内容:', + style: TextStyle(fontWeight: FontWeight.bold), + ), Text(state.versionInfo.updateDesc), ], const SizedBox(height: 20), @@ -215,20 +240,28 @@ class _UpdateCheckerState extends State<_UpdateChecker> { decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.shade200), + border: Border.all( + color: Colors.blue.shade200, + ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '💡 选择下载方式', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), ), const SizedBox(height: 8), const Text( '• 自动安装:App 内下载并调起系统安装\n' '• 手动下载:在浏览器中下载安装', - style: TextStyle(fontSize: 13, height: 1.5), + style: TextStyle( + fontSize: 13, + height: 1.5, + ), ), ], ), @@ -240,7 +273,9 @@ class _UpdateCheckerState extends State<_UpdateChecker> { children: [ if (!state.versionInfo.forceUpdate) TextButton( - onPressed: () => context.read().cancelUpdate(), + onPressed: () => context + .read() + .cancelUpdate(), child: const Text('稍后'), ), const SizedBox(width: 10), @@ -266,7 +301,11 @@ class _UpdateCheckerState extends State<_UpdateChecker> { onPressed: () { final info = state.versionInfo; if (info.apkUrl != null) { - context.read().downloadAndInstallApk(info.apkUrl!); + context + .read() + .downloadAndInstallApk( + info.apkUrl!, + ); } }, child: const Text('手动下载'), @@ -276,7 +315,11 @@ class _UpdateCheckerState extends State<_UpdateChecker> { onPressed: () { final info = state.versionInfo; if (info.apkUrl != null) { - context.read().downloadAndInstallApk(info.apkUrl!); + context + .read() + .downloadAndInstallApk( + info.apkUrl!, + ); } }, child: const Text('自动安装'), @@ -307,7 +350,10 @@ class _UpdateCheckerState extends State<_UpdateChecker> { children: [ const Text( '📱 整包更新步骤', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 16), _buildStepItem(1, '在浏览器中下载 APK', true), @@ -347,7 +393,10 @@ class _UpdateCheckerState extends State<_UpdateChecker> { children: [ Text( state.isPatch ? '⬇️ 正在下载补丁...' : '⬇️ 正在下载 APK...', - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 20), SizedBox( @@ -362,7 +411,10 @@ class _UpdateCheckerState extends State<_UpdateChecker> { ), Text( '${(state.progress * 100).toInt()}%', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), ], ), @@ -370,7 +422,10 @@ class _UpdateCheckerState extends State<_UpdateChecker> { const SizedBox(height: 16), Text( '请稍候...', - style: TextStyle(fontSize: 14, color: Colors.grey.shade600), + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), ), ], ), @@ -392,10 +447,19 @@ class _UpdateCheckerState extends State<_UpdateChecker> { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check_circle, color: Colors.green, size: 60), + const Icon( + Icons.check_circle, + color: Colors.green, + size: 60, + ), const SizedBox(height: 16), - const Text('✅ 更新已完成', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const Text( + '✅ 更新已完成', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), const SizedBox(height: 12), const Text( '补丁已成功应用!\n\n请完全关闭 App 后重新打开,\n即可体验新版本功能。', @@ -435,8 +499,13 @@ class _UpdateCheckerState extends State<_UpdateChecker> { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text('状态: ${state.runtimeType.toString().replaceAll('Update', '')}', - style: const TextStyle(color: Colors.white, fontSize: 12)), + Text( + '状态: ${state.runtimeType.toString().replaceAll('Update', '')}', + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), // 🔥 暂时关闭刷新按钮和点击阴影 // IconButton( // icon: const Icon(Icons.refresh, color: Colors.white), @@ -452,7 +521,7 @@ class _UpdateCheckerState extends State<_UpdateChecker> { }, ); } - + /// 构建步骤项 Widget _buildStepItem(int step, String text, bool completed) { return Row( @@ -485,8 +554,7 @@ class _UpdateCheckerState extends State<_UpdateChecker> { ), ), ), - if (completed) - const Icon(Icons.check, color: Colors.green, size: 20), + if (completed) const Icon(Icons.check, color: Colors.green, size: 20), ], ); } diff --git a/packages/cc_ui_kit/pubspec.yaml b/packages/cc_ui_kit/pubspec.yaml index 601e30d9..38dd37c9 100644 --- a/packages/cc_ui_kit/pubspec.yaml +++ b/packages/cc_ui_kit/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: sdk: flutter flutter_svg: ^2.2.3 + vibration: ^3.1.8 dev_dependencies: flutter_test: diff --git a/pubspec.yaml b/pubspec.yaml index e714d888..fdc6857b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,6 +92,9 @@ dependencies: flutter_svg: ^2.2.3 image_picker: ^1.1.2 video_player: ^2.8.2 + + # ===== 火山引擎 RTC 实时音视频 ===== + #volc_engine_rtc: ^3.60.4 # ===== 屏幕适配,高刷等 =====