diff --git a/lib/core/network/tcp/tcp_client.dart b/lib/core/network/tcp/tcp_client.dart index 2e7a4ad6..7f20dcda 100644 --- a/lib/core/network/tcp/tcp_client.dart +++ b/lib/core/network/tcp/tcp_client.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:get_it/get_it.dart'; import 'package:maibu_satabot_v2/features/devices/data/repositories/route_planning_repository_impl.dart'; +import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_cubit.dart'; import '../../../features/auth/data/datasources/auth_tcp_datasource.dart'; import '../../../features/devices/data/models/route_plan_send_entity.dart'; @@ -273,11 +274,40 @@ class TcpClient { return; } - _reconnectTimer = Timer(const Duration(seconds: 5), () { + _reconnectTimer = Timer(const Duration(seconds: 5), () async { _reconnectTimer = null; //debugPrint('⏰ 被动-定时器触发,开始执行重连...'); _logger.logWithLevel('⏰ 被动-定时器触发,开始执行重连...', shouldLog: true); - connectBySwitch(host: _lastHost!, port: _lastPort!, deviceName: devname); + + // 🔥 修复:重连时使用 RemoteControlCubit.targetDevice,而不是记住的设备名 + try { + final remoteControlCubit = GetIt.I(); + final targetDevice = remoteControlCubit.state.targetDevice; + + if (targetDevice != null) { + _logger.logWithLevel('🔄 [TCP] 重连使用 targetDevice: ${targetDevice.deviceName}', shouldLog: true); + connectBySwitch( + host: _lastHost!, + port: _lastPort!, + deviceName: targetDevice.deviceName, + ); + } else { + // 如果 targetDevice 为 null,使用传入的设备名作为兜底 + _logger.logWithLevel('⚠️ [TCP] targetDevice 为 null,使用传入的设备名: $devname', shouldLog: true); + connectBySwitch( + host: _lastHost!, + port: _lastPort!, + deviceName: devname, + ); + } + } catch (e) { + _logger.logWithLevel('❌ [TCP] 获取 targetDevice 失败,使用传入的设备名: $devname, 错误: $e', shouldLog: true); + connectBySwitch( + host: _lastHost!, + port: _lastPort!, + deviceName: devname, + ); + } }); await _sendAuthPacketBySwitch(devname); } @@ -808,11 +838,24 @@ class TcpClient { _logger.logWithLevel('🔑 被动[TCP] 已发送认证包 (0x03): $authString'); try { - // 1. 获取 Either 结果 - await switchDeviceUseCase.deviceRepository.switchDevice( - "app", - deviceName, - ); + // 🔥 修复:使用 RemoteControlCubit.targetDevice,而不是传入的 deviceName + final remoteControlCubit = GetIt.I(); + final targetDevice = remoteControlCubit.state.targetDevice; + + if (targetDevice != null) { + debugPrint('🔀 [TCP] 使用 targetDevice 进行切换: ${targetDevice.deviceName}'); + await switchDeviceUseCase.deviceRepository.switchDevice( + "app", + targetDevice.deviceName, + ); + } else { + // 兜底:如果 targetDevice 为 null,使用传入的 deviceName + debugPrint('⚠️ [TCP] targetDevice 为 null,使用传入的 deviceName: $deviceName'); + await switchDeviceUseCase.deviceRepository.switchDevice( + "app", + deviceName, + ); + } } catch (e) { //debugPrint('❌ 被动[AuthTcp] 设备订阅流程异常:$e'); _logger.logWithLevel('❌ 被动[AuthTcp] 设备订阅流程异常:$e'); diff --git a/lib/features/home/presentation/pages/home_page.dart b/lib/features/home/presentation/pages/home_page.dart index 5ac8e702..337b9ca7 100644 --- a/lib/features/home/presentation/pages/home_page.dart +++ b/lib/features/home/presentation/pages/home_page.dart @@ -9,6 +9,7 @@ import 'package:maibu_satabot_v2/core/network/tcp/tcp_client.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart'; // 👈 必须加 import 'package:maibu_satabot_v2/features/remote_control/domain/repositories/remote_control_repository.dart'; +import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_cubit.dart'; import '../../../devices/domain/entities/device_entity.dart'; import '../bloc/permission_request_bloc.dart'; @@ -44,6 +45,17 @@ class _HomePageState extends State { final devicesCubit = context.read(); + // 🔥 修复:检查是否已有 targetDevice,如果有则不需要加载设备列表 + final remoteControlState = context.read().state; + if (remoteControlState.targetDevice != null) { + debugPrint('✅ [HomePage] 已有 targetDevice: ${remoteControlState.targetDevice!.deviceName},跳过设备列表加载'); + setState(() { + _isDevicesLoading = false; + _hasLoadedDevices = true; + }); + return; + } + // 🔥 关键:等待设备加载完成 await devicesCubit.fetchAllDevices(username); diff --git a/lib/features/home/presentation/pages/route_plan_page.dart b/lib/features/home/presentation/pages/route_plan_page.dart index 2a3f19e8..377ac068 100644 --- a/lib/features/home/presentation/pages/route_plan_page.dart +++ b/lib/features/home/presentation/pages/route_plan_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/app/app_user_cubit.dart'; -import '../../../devices/presentation/bloc/devices_cubit.dart'; +import '../../../remote_control/presentation/bloc/remote_control_cubit.dart'; import '../widgets/map/test_amap_page.dart'; import '../widgets/map/testmap_pages.dart'; @@ -11,17 +11,87 @@ class RoutePlanPage extends StatelessWidget { @override Widget build(BuildContext context) { - // 同时监听用户和设备状态 - final userState = context.read().state; - final deviceState = context.read().state; - final currentDevice = deviceState.selectedDevice; + try { + // 🔥 修复:使用 RemoteControlCubit 的 targetDevice 作为唯一设备数据源 + final userState = context.watch().state; + final remoteControlState = context.watch().state; + final targetDevice = remoteControlState.targetDevice; - if (currentDevice == null) - return const Scaffold(body: Center(child: Text("加载中..."))); + debugPrint('🔍 [RoutePlanPage] targetDevice: ${targetDevice?.deviceName ?? "null"}'); - return Scaffold( - backgroundColor: const Color(0xFFF7F7F7), - body: MapPageEnterprise(), - ); + // 检查是否有选中的设备 + if (targetDevice == null) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.devices_other, + size: 64, + color: Color(0xFF86909C), + ), + const SizedBox(height: 16), + const Text( + '请先选择设备', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF1D2129), + ), + ), + const SizedBox(height: 8), + Text( + '在设备列表或场站中选择要控制的设备', + style: TextStyle( + fontSize: 12, + color: Color(0xFF86909C), + ), + ), + ], + ), + ), + ); + } + + return Scaffold( + backgroundColor: const Color(0xFFF7F7F7), + body: MapPageEnterprise(), + ); + } catch (e, stackTrace) { + debugPrint('❌ [RoutePlanPage] 构建错误: $e'); + debugPrint('堆栈跟踪: $stackTrace'); + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + size: 64, + color: Colors.red, + ), + const SizedBox(height: 16), + const Text( + '页面加载失败', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF1D2129), + ), + ), + const SizedBox(height: 8), + Text( + '错误: $e', + style: const TextStyle( + fontSize: 12, + color: Colors.red, + ), + ), + ], + ), + ), + ); + } } } diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 4c7a6e47..b41bbe7c 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -192,10 +192,14 @@ class _MapPageEnterpriseState extends State { @override void initState() { super.initState(); + debugPrint('🚀 [MapPage] initState 开始'); _currentStation = _stationList.first; _traceManager = TracePoint(); - _loadSavedData(); + // 🔥 修复:异步加载数据,避免阻塞 + _loadSavedData().catchError((e) { + debugPrint('❌ [MapPage] 加载本地数据失败: $e'); + }); // 示例:切换到导航模式 _traceManager.setMode(TPMode.LOCATION); @@ -204,7 +208,10 @@ class _MapPageEnterpriseState extends State { // 🔥 核心修复:使用 addPostFrameCallback 延迟获取地图中心(渲染完成后执行) WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted && _mapController.camera != null) { + if (!mounted) return; + + debugPrint('🗺️ [MapPage] addPostFrameCallback 执行'); + if (_mapController.camera != null) { final originalCenter = _mapController.camera!.center; final highPrecisionCenter = LatLng( originalCenter.latitude, @@ -242,6 +249,8 @@ class _MapPageEnterpriseState extends State { } } }); + + debugPrint('✅ [MapPage] initState 完成'); } // 🔥 核心修复:监听路径规划应答流 @@ -2835,7 +2844,8 @@ class _MapPageEnterpriseState extends State { } return BlocBuilder( builder: (context, state) { - final devicesState = context.watch().state; + // 🔥 修复:使用 read 而非 watch,避免不必要的重建和接口调用 + final devicesState = context.read().state; bool isFinishWork = devicesState.isFinshWork ?? false; double? arriLatitude = devicesState.arriLatitude; double? arriLongitude = devicesState.arriLongitude; 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 7514a954..d3155741 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 @@ -1,11 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:volc_engine_rtc/volc_engine_rtc.dart' as volc; -import 'package:agora_rtc_engine/agora_rtc_engine.dart' as agora; import '../../../../../core/di/injection.dart'; import '../../domain/entities/drone_station_entity.dart'; -import '../../domain/entities/video_stream_entity.dart'; import '../bloc/drone_station_bloc.dart'; import '../bloc/drone_station_event.dart'; import '../bloc/drone_station_state.dart'; @@ -14,9 +11,6 @@ import 'drone_mission_control_page.dart'; import 'drone_monitor_page.dart'; import '../widgets/flight_task_selector_modal.dart'; -// SDK 类型枚举 -enum RtcSdkType { volcengine, agora } - class DroneStationDetailPage extends StatefulWidget { final DroneStationEntity station; @@ -33,30 +27,7 @@ class _DroneStationDetailPageState extends State { UAVDetailEntity? _detail; String? _droneSn; - // 悬浮视频监控状态 - bool showFloatingMonitor = false; - bool isFloatingIndoor = true; - Offset floatingPosition = const Offset(20, 200); - bool _isFloatingMonitorEnabled = true; // 悬浮窗默认开启 - // 视频流状态 - VideoStreamEntity? _floatingVideoStream; - bool _isFloatingLoading = false; - String? _floatingErrorMessage; - String? _floatingRemoteUserId; - bool _isFloatingAgora = false; - - // 火山引擎 RTC - volc.RTCEngine? _floatingRtcEngine; - volc.RTCRoom? _floatingRtcRoom; - volc.RTCViewContext? _floatingRemoteRenderContext; - - // Agora RTC - agora.RtcEngine? _floatingAgoraEngine; - - // 加载超时计时器 - Timer? _floatingLoadingTimer; - static const _floatingLoadingTimeout = Duration(seconds: 15); // 无人机状态轮询计时器 Timer? _droneStatusPollingTimer; @@ -76,35 +47,11 @@ class _DroneStationDetailPageState extends State { _startDroneStatusPolling(); } - /// 检查并自动打开悬浮窗 - void _checkAndShowFloatingMonitor(UAVDetailEntity detail) { - debugPrint('🔍 检查悬浮窗条件:'); - debugPrint(' - 开关状态: $_isFloatingMonitorEnabled'); - debugPrint(' - 是否已显示: $showFloatingMonitor'); - debugPrint(' - 无人机在线: ${detail.droneOnlineStatus}'); - debugPrint(' - 机场摄像头: ${detail.gatewayCameraList?.length ?? 0}'); - if (!_isFloatingMonitorEnabled || showFloatingMonitor) { - debugPrint('❌ 不满足条件,退出检查'); - return; // 开关关闭或已显示,不执行 - } - - // 无人机在线且有机场摄像头,自动打开悬浮窗 - if (detail.droneOnlineStatus == 1 && - detail.gatewayCameraList != null && - detail.gatewayCameraList!.isNotEmpty) { - debugPrint('✅ 检测到无人机在线,自动打开悬浮窗'); - _loadFloatingVideoStream(); - } else { - debugPrint('❌ 无人机离线或无摄像头'); - } - } @override void dispose() { _bloc.close(); - _destroyFloatingRtcEngine(); - _floatingLoadingTimer?.cancel(); _droneStatusPollingTimer?.cancel(); // 停止轮询 super.dispose(); } @@ -123,8 +70,8 @@ class _DroneStationDetailPageState extends State { final currentState = _bloc.state; if (currentState is UAVDetailLoaded) { if (currentState.detail.droneOnlineStatus == 1) { - // 无人机在线时,每15秒轮询一次 - interval = const Duration(seconds: 15); + // 无人机在线时,每30秒轮询一次 + interval = const Duration(seconds: 30); } else { // 无人机离线时,每60秒轮询一次(降低频率) interval = const Duration(seconds: 60); @@ -143,53 +90,12 @@ class _DroneStationDetailPageState extends State { ), ); - // 如果悬浮窗开启且无人机上线,自动显示悬浮窗 - if (_isFloatingMonitorEnabled) { - final state = _bloc.state; - if (state is UAVDetailLoaded && - state.detail.droneOnlineStatus == 1 && - !showFloatingMonitor) { - debugPrint('✅ 无人机已上线,自动打开悬浮窗'); - _loadFloatingVideoStream(); - } - } - // 重新调度下一次轮询(动态周期) _scheduleDroneStatusPoll(); }); } - // 销毁悬浮窗的 RTC 引擎 - void _destroyFloatingRtcEngine() async { - // 销毁火山引擎 RTC - if (_floatingRtcRoom != null) { - try { - await _floatingRtcRoom?.leaveRoom(); - } catch (_) {} - _floatingRtcRoom = null; - } - if (_floatingRtcEngine != null) { - try { - _floatingRtcEngine?.destroy(); - } catch (_) {} - _floatingRtcEngine = null; - } - // 销毁 Agora RTC - if (_floatingAgoraEngine != null) { - try { - await _floatingAgoraEngine?.leaveChannel(); - } catch (_) {} - try { - _floatingAgoraEngine?.release(); - } catch (_) {} - _floatingAgoraEngine = null; - } - - _floatingRemoteRenderContext = null; - _floatingRemoteUserId = null; - _isFloatingAgora = false; - } @override Widget build(BuildContext context) { @@ -215,19 +121,7 @@ class _DroneStationDetailPageState extends State { centerTitle: true, ), body: BlocConsumer( - listener: (context, state) { - // 监听视频流加载状态 - if (state is VideoStreamLoaded) { - _floatingLoadingTimer?.cancel(); - setState(() { - _floatingVideoStream = state.videoStream; - }); - _initFloatingRtcEngine(); - } else if (state is VideoStreamError) { - _floatingLoadingTimer?.cancel(); - setState(() {}); - } - }, + listener: (context, state) {}, builder: (context, state) { if (state is UAVDetailLoading) { return const Center( @@ -274,8 +168,6 @@ class _DroneStationDetailPageState extends State { } if (state is UAVDetailLoaded) { - // 检查是否自动打开悬浮窗 - _checkAndShowFloatingMonitor(state.detail); return _buildContent(state.detail); } @@ -289,24 +181,36 @@ class _DroneStationDetailPageState extends State { Widget _buildContent(UAVDetailEntity detail) { _detail = detail; // 保存详情数据供其他方法使用 _droneSn = detail.deviceSn; // 保存无人机序列号 - return Stack( - children: [ - 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(), - ], + return RefreshIndicator( + onRefresh: _handleRefresh, + color: const Color(0xFF165DFF), + child: 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), + ], + ), + ); + } + + /// 处理下拉刷新 + Future _handleRefresh() async { + // 重新加载无人机详情 + _bloc.add( + UAVDetailLoad( + gatewaySn: widget.station.gatewaySn, + deviceSn: widget.station.deviceSn, + ), ); + // 重置轮询计时器,使用新的状态 + _scheduleDroneStatusPoll(); } Widget _buildMonitorCard() { @@ -693,37 +597,6 @@ class _DroneStationDetailPageState extends State { ), ), ), - // 悬浮观看功能已禁用 - // GestureDetector( - // onTap: () { - // Navigator.pop(context); - // _loadFloatingVideoStream(); - // }, - // 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), ], ), ); @@ -771,146 +644,7 @@ class _DroneStationDetailPageState extends State { ); } - // 悬浮视频监控组件 - 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); - _loadFloatingVideoStream(); - }, - 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: () { - _destroyFloatingRtcEngine(); - setState(() { - showFloatingMonitor = false; - _isFloatingMonitorEnabled = false; // 关闭开关 - }); - }, - child: const Padding( - padding: EdgeInsets.all(4), - child: Icon( - Icons.close, - size: 18, - color: Color(0xFF86909C), - ), - ), - ), - ], - ), - ), - ], - ), - ); - } Widget _buildQuickActionButton({ required IconData icon, @@ -1004,350 +738,7 @@ class _DroneStationDetailPageState extends State { ); } - // 加载悬浮视频流 - void _loadFloatingVideoStream() { - _floatingLoadingTimer?.cancel(); - // 从当前状态中获取摄像头索引 - final currentState = _bloc.state; - String cameraIndex = '165-0-7'; // 默认值 - - if (currentState is UAVDetailLoaded && - currentState.detail.gatewayCameraList != null && - currentState.detail.gatewayCameraList!.isNotEmpty) { - cameraIndex = currentState.detail.gatewayCameraList!.first.cameraIndex; - } - - setState(() { - _isFloatingLoading = true; - _floatingErrorMessage = null; - showFloatingMonitor = true; - _isFloatingMonitorEnabled = true; // 开启开关 - }); - - _destroyFloatingRtcEngine(); - - // 设置加载超时计时器 - _floatingLoadingTimer = Timer(_floatingLoadingTimeout, () { - if (!mounted) return; - if (_isFloatingLoading) { - debugPrint('⚠️ 悬浮窗视频加载超时'); - setState(() { - _isFloatingLoading = false; - _floatingErrorMessage = '视频加载超时,请检查网络连接或点击刷新重试'; - }); - } - }); - - _bloc.add( - VideoStreamLoad( - sn: widget.station.gatewaySn, - cameraIndex: cameraIndex, - cameraPosition: isFloatingIndoor ? 'indoor' : 'outdoor', - ), - ); - } - - // 初始化悬浮窗的 RTC 引擎 - Future _initFloatingRtcEngine() async { - if (_floatingVideoStream == null) return; - - final appId = _floatingVideoStream!.appId; - final roomId = _floatingVideoStream!.roomId; - final token = _floatingVideoStream!.token; - final userId = _floatingVideoStream!.userId.isNotEmpty - ? _floatingVideoStream!.userId - : 'user_${DateTime.now().millisecondsSinceEpoch}'; - - if (appId.isEmpty || roomId.isEmpty || token.isEmpty) { - setState(() { - _floatingErrorMessage = 'RTC 参数缺失'; - _isFloatingLoading = false; - }); - return; - } - - debugPrint('=== 悬浮窗 RTC 初始化 ==='); - debugPrint('AppId: $appId'); - debugPrint('RoomId: $roomId'); - debugPrint('UserId: $userId'); - debugPrint('URL Type: ${_floatingVideoStream!.urlType}'); - - final sdkType = _floatingVideoStream!.urlType.toLowerCase() == 'agora' - ? RtcSdkType.agora - : RtcSdkType.volcengine; - - if (sdkType == RtcSdkType.agora) { - await _initFloatingAgoraEngine(appId, roomId, token, userId); - } else { - await _initFloatingVolcEngine(appId, roomId, token, userId); - } - } - - // 初始化悬浮窗的 Agora 引擎 - Future _initFloatingAgoraEngine( - String appId, - String channelId, - String token, - String userId, - ) async { - try { - debugPrint('=== Agora 悬浮窗初始化 ==='); - _floatingAgoraEngine = agora.createAgoraRtcEngine(); - await _floatingAgoraEngine!.initialize( - agora.RtcEngineContext(appId: appId), - ); - debugPrint('Agora 引擎初始化成功'); - - // 启用视频模块 - _floatingAgoraEngine!.enableVideo(); - debugPrint('Agora 视频模块已启用'); - - _floatingAgoraEngine!.registerEventHandler( - agora.RtcEngineEventHandler( - onJoinChannelSuccess: (agora.RtcConnection connection, int elapsed) { - debugPrint('✅ 悬浮窗 Agora 加入频道成功: ${connection.channelId}'); - }, - onUserJoined: (agora.RtcConnection connection, int uid, int elapsed) { - debugPrint('✅ 悬浮窗 Agora 用户加入: uid=$uid'); - setState(() { - _floatingRemoteUserId = uid.toString(); - _isFloatingAgora = true; - _isFloatingLoading = false; - }); - }, - onUserOffline: - ( - agora.RtcConnection connection, - int uid, - agora.UserOfflineReasonType reason, - ) { - debugPrint('悬浮窗 Agora 用户离开: $uid'); - if (uid.toString() == _floatingRemoteUserId) { - setState(() { - _floatingRemoteUserId = null; - }); - } - }, - onError: (agora.ErrorCodeType err, String msg) { - debugPrint('❌ 悬浮窗 Agora 错误: $err - $msg'); - setState(() { - _floatingErrorMessage = 'Agora RTC 错误:$err'; - _isFloatingLoading = false; - }); - }, - ), - ); - - await _floatingAgoraEngine!.joinChannel( - token: token, - channelId: channelId, - uid: int.tryParse(userId) ?? 0, - options: agora.ChannelMediaOptions( - channelProfile: - agora.ChannelProfileType.channelProfileLiveBroadcasting, - clientRoleType: agora.ClientRoleType.clientRoleAudience, - autoSubscribeVideo: true, - autoSubscribeAudio: false, - ), - ); - } catch (e) { - debugPrint('❌ 悬浮窗 Agora RTC 初始化失败: $e'); - setState(() { - _floatingErrorMessage = 'Agora RTC 初始化失败:$e'; - _isFloatingLoading = false; - }); - } - } - - // 初始化悬浮窗的火山引擎 - Future _initFloatingVolcEngine( - String appId, - String roomId, - String token, - String userId, - ) async { - try { - debugPrint('=== 悬浮窗 VolcEngine 初始化 ==='); - final engineEventHandler = volc.IRTCEngineEventHandler( - onWarning: (volc.WarningCode code) { - debugPrint('Volc 悬浮窗 Warning: $code'); - }, - onError: (volc.ErrorCode code) { - debugPrint('Volc 悬浮窗 Error: $code'); - setState(() { - _floatingErrorMessage = 'Volc RTC 错误:$code'; - _isFloatingLoading = false; - }); - }, - onConnectionStateChanged: (state, reason) { - debugPrint( - 'Volc 悬浮窗 Connection State Changed: $state, reason: $reason', - ); - }, - ); - - _floatingRtcEngine = await volc.RTCEngine.createRTCEngine( - volc.RTCVideoContext(appId: appId, eventHandler: engineEventHandler), - ); - - if (_floatingRtcEngine == null) { - debugPrint('Volc 悬浮窗引擎创建失败'); - setState(() { - _floatingErrorMessage = 'Volc RTC 引擎创建失败'; - _isFloatingLoading = false; - }); - return; - } - - _floatingRtcRoom = await _floatingRtcEngine!.createRTCRoom(roomId); - - if (_floatingRtcRoom == null) { - debugPrint('Volc 悬浮窗房间创建失败'); - setState(() { - _floatingErrorMessage = 'Volc RTC 房间创建失败'; - _isFloatingLoading = false; - }); - return; - } - - final roomEventHandler = volc.IRTCRoomEventHandler( - onUserPublishStreamVideo: - (String userId, volc.StreamInfo streamInfo, bool isPublish) { - debugPrint('Volc 悬浮窗 远端用户 $userId 视频流状态: $isPublish'); - setState(() { - if (isPublish) { - _floatingRemoteUserId = userId; - _isFloatingAgora = false; - _floatingRemoteRenderContext = - volc.RTCViewContext.remoteContext( - roomId: roomId, - userId: userId, - ); - _isFloatingLoading = false; - } else { - if (userId == _floatingRemoteUserId) { - _floatingRemoteRenderContext = null; - _floatingRemoteUserId = null; - } - } - }); - }, - onUserLeave: (String userId, int reason) { - debugPrint('Volc 悬浮窗 用户离开: $userId'); - if (userId == _floatingRemoteUserId) { - setState(() { - _floatingRemoteRenderContext = null; - _floatingRemoteUserId = null; - }); - } - }, - ); - - await _floatingRtcRoom?.setRTCRoomEventHandler(roomEventHandler); - - await _floatingRtcRoom?.joinRoom( - token: token, - userInfo: volc.UserInfo(userId: userId, extraInfo: ''), - userVisibility: true, - roomConfig: volc.RoomConfig( - isPublishAudio: false, - isPublishVideo: false, - isAutoSubscribeAudio: false, - isAutoSubscribeVideo: true, - ), - ); - } catch (e) { - debugPrint('❌ 悬浮窗 Volc RTC 初始化失败: $e'); - setState(() { - _floatingErrorMessage = 'Volc RTC 初始化失败:$e'; - _isFloatingLoading = false; - }); - } - } - - // 构建悬浮窗视频内容 - Widget _buildFloatingVideoContent() { - if (_isFloatingLoading) { - return Container( - height: 140, - color: const Color(0xFF0D1117), - child: const Center( - child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), - ), - ); - } - - if (_floatingErrorMessage != null) { - return Container( - height: 140, - color: const Color(0xFF0D1117), - child: Center( - child: Text( - _floatingErrorMessage!, - style: const TextStyle(color: Colors.red, fontSize: 12), - textAlign: TextAlign.center, - ), - ), - ); - } - - if (_floatingRemoteUserId == null) { - return Container( - height: 140, - color: const Color(0xFF0D1117), - child: const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.video_camera_front, size: 32, color: Colors.grey), - SizedBox(height: 8), - Text( - '等待视频流...', - style: TextStyle(color: Colors.grey, fontSize: 12), - ), - ], - ), - ), - ); - } - - if (_isFloatingAgora && _floatingAgoraEngine != null) { - return SizedBox( - height: 140, - child: agora.AgoraVideoView( - controller: agora.VideoViewController.remote( - rtcEngine: _floatingAgoraEngine!, - canvas: agora.VideoCanvas(uid: int.parse(_floatingRemoteUserId!)), - connection: agora.RtcConnection( - channelId: _floatingVideoStream?.roomId ?? '', - ), - ), - ), - ); - } - - if (!_isFloatingAgora && _floatingRemoteRenderContext != null) { - return SizedBox( - height: 140, - child: volc.RTCSurfaceView( - context: _floatingRemoteRenderContext!, - renderMode: volc.VideoRenderMode.hidden, - ), - ); - } - - return Container( - height: 140, - color: const Color(0xFF0D1117), - child: const Center( - child: Text( - '视频初始化中...', - style: TextStyle(color: Colors.grey, fontSize: 12), - ), - ), - ); - } Widget _buildCameraListRow(String label, List cameras) { String value = cameras diff --git a/lib/features/v2/device_list/presentation/widgets/drone_station_item_card.dart b/lib/features/v2/device_list/presentation/widgets/drone_station_item_card.dart index 272dde3b..1bd315c7 100644 --- a/lib/features/v2/device_list/presentation/widgets/drone_station_item_card.dart +++ b/lib/features/v2/device_list/presentation/widgets/drone_station_item_card.dart @@ -95,7 +95,7 @@ class DroneStationItemCard extends StatelessWidget { ), ), // 机场状态徽章 - _buildStatusBadge('机场', station.isOnline), + _buildStatusBadge(station.isOnline), ], ), ), @@ -224,9 +224,9 @@ class DroneStationItemCard extends StatelessWidget { } /// 状态徽章 - Widget _buildStatusBadge(String label, bool isOnline) { + Widget _buildStatusBadge( bool isOnline) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration( gradient: LinearGradient( colors: isOnline @@ -257,7 +257,7 @@ class DroneStationItemCard extends StatelessWidget { ), const SizedBox(width: 5), Text( - '$label\n${isOnline ? '在线' : '离线'}', + '${isOnline ? '在线' : '离线'}', textAlign: TextAlign.center, style: const TextStyle( fontSize: 10,