590 lines
25 KiB
Dart
590 lines
25 KiB
Dart
import 'dart:async';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/domain/entities/device_control_mode.dart';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/presentation/widgets/control_mode_entry_gate.dart';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:maibu_satabot_v2/components/capsule_toast.dart';
|
||
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
|
||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||
|
||
import '../../../../core/app/app_user_cubit.dart';
|
||
import '../../../../core/router/route_paths.dart';
|
||
import '../../../auth/presentation/bloc/auth_cubit.dart'; // 🔥 导入 AuthCubit
|
||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||
import '../bloc/remote_control_cubit.dart';
|
||
import '../bloc/remote_control_state.dart';
|
||
import '../widgets/center_control_area.dart';
|
||
import '../widgets/emergency_overlay.dart';
|
||
import '../widgets/left_joystick_area.dart';
|
||
import '../widgets/right_joystick_area.dart';
|
||
import '../widgets/driving_guide_overlay.dart';
|
||
import '../widgets/top_status_bar.dart';
|
||
import '../widgets/webrtc/webrtc_local_player.dart';
|
||
|
||
class RemoteControlPage extends StatefulWidget {
|
||
const RemoteControlPage({super.key});
|
||
|
||
@override
|
||
State<RemoteControlPage> createState() => _RemoteControlPageState();
|
||
}
|
||
|
||
class _RemoteControlPageState extends State<RemoteControlPage> {
|
||
String _videoStreamUrl = "";
|
||
late RemoteControlCubit _cubit;
|
||
DevicesCubit? _devicesCubit;
|
||
StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅
|
||
bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示
|
||
bool? _lastPermissionDialogState; // 🔥 记录上次弹窗状态,检测状态变化
|
||
bool _controlSessionStarted = false;
|
||
late final String _modeDeviceId = _cubit.state.targetDevice?.deviceName ?? '';
|
||
bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态
|
||
|
||
/// 缓存本地化实例:dispose 阶段 context 已失效 / 弹窗中需提前拼文案,
|
||
/// 不能依赖 `AppLocalizations.of(context)`。
|
||
late AppLocalizations _l10n;
|
||
|
||
@override
|
||
void didChangeDependencies() {
|
||
super.didChangeDependencies();
|
||
_l10n = AppLocalizations.of(context);
|
||
_cubit = context.read<RemoteControlCubit>();
|
||
_devicesCubit = context.read<DevicesCubit>();
|
||
|
||
// 🔥 监听权限弹窗状态变化 (只订阅一次)
|
||
if (_permissionSubscription == null) {
|
||
_permissionSubscription = _cubit.stream.listen((state) {
|
||
// 🔥 关键修复:只有当状态更新类型是弹窗更新时才处理弹窗逻辑
|
||
// 设备状态更新(500ms)不会触发弹窗显示
|
||
if (state.updateType != 'permission_dialog') {
|
||
// 如果是设备状态更新,只重置状态记录,不显示弹窗
|
||
if (!state.showPermissionRequestDialog) {
|
||
_lastPermissionDialogState = false;
|
||
}
|
||
return; // 跳过设备状态更新
|
||
}
|
||
|
||
// 🔥 只有当状态从 false 变为 true 时才显示弹窗
|
||
// 使用三个条件确保不会重复显示
|
||
final wasFalseBefore =
|
||
_lastPermissionDialogState == null ||
|
||
_lastPermissionDialogState == false;
|
||
final shouldShow =
|
||
state.showPermissionRequestDialog &&
|
||
!_isShowingPermissionDialog &&
|
||
wasFalseBefore;
|
||
|
||
if (mounted && shouldShow) {
|
||
_lastPermissionDialogState = true; // 记录当前状态
|
||
_isShowingPermissionDialog = true;
|
||
debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗');
|
||
// 使用微任务确保标志位已设置
|
||
Future.microtask(() {
|
||
_showPermissionDialog(state).then((_) {
|
||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||
_isShowingPermissionDialog = false; // 弹窗关闭后重置标志
|
||
_lastPermissionDialogState = false; // 重置状态记录
|
||
});
|
||
});
|
||
} else if (!state.showPermissionRequestDialog) {
|
||
// 状态变为 false 时,重置记录
|
||
_lastPermissionDialogState = false;
|
||
}
|
||
});
|
||
} else {
|
||
// 🔥 如果已经订阅,重置状态记录,确保下次能正确检测状态变化
|
||
_lastPermissionDialogState = _cubit.state.showPermissionRequestDialog;
|
||
}
|
||
}
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 🔥 进入安全模式:防止异地登录推送导致控制中断
|
||
GetIt.I<AuthCubit>().enterSafeMode();
|
||
|
||
// 1. 锁定横屏
|
||
SystemChrome.setPreferredOrientations([
|
||
DeviceOrientation.landscapeLeft,
|
||
DeviceOrientation.landscapeRight,
|
||
]);
|
||
// 2. 隐藏状态栏和虚拟按键
|
||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
// 🔥 离开安全模式:启动 90 秒倒计时
|
||
GetIt.I<AuthCubit>().exitSafeMode();
|
||
|
||
// 释放远程控制权限(type=2:退出远程遥控页面释放)
|
||
final deviceId = _cubit.state.targetDevice?.deviceName;
|
||
if (_controlSessionStarted && deviceId != null && deviceId.isNotEmpty) {
|
||
_cubit.releasePermission(deviceId: deviceId, type: 2).then((success) {
|
||
CapsuleToast.show(
|
||
success
|
||
? _l10n.translate('remote_control.toast_released')
|
||
: _l10n.translate('remote_control.toast_release_failed'),
|
||
showCheck: success,
|
||
);
|
||
});
|
||
}
|
||
|
||
_permissionSubscription?.cancel(); // 🔥 取消订阅
|
||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||
// 🔥 退页先把车真正停下:归零双轴 + 连发 30 帧停止指令。
|
||
// 只靠 stopControlLoop() 断掉 200ms 循环是不够的:设备只认“最后收到的
|
||
// 运动帧”,若手指还压在摇杆上就退出(没走松手归零),已下发的高速帧会
|
||
// 让车继续跑到它自己的失联保护为止。与新版 HUD 折叠/退出的 deactivate()
|
||
// 同一口径。
|
||
// 必须放在 stopControlLoop() 之前:stopAllMovement 末尾会按 _controlWanted
|
||
// 决定是否重启循环,这里同步把意图关掉,就不会在退页后留下无主定时器。
|
||
if (_controlSessionStarted) {
|
||
unawaited(_cubit.stopAllMovement());
|
||
_cubit.stopControlLoop();
|
||
}
|
||
_cubit.stopReverseCameraMock(); // 🔥 停止倒车影像 Mock,避免定时器空转
|
||
// 🔥 重置权限状态,避免单例 hasPermission 残留导致下次进入不再查询接口
|
||
_cubit.resetPermissionState();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) => ControlModeEntryGate(
|
||
deviceId: _modeDeviceId,
|
||
target: ControlModeTarget.remote,
|
||
onReady: () {
|
||
if (_cubit.state.targetDevice?.deviceName != _modeDeviceId) return;
|
||
_controlSessionStarted = true;
|
||
_cubit.startControlLoop();
|
||
},
|
||
child: _buildPage(context),
|
||
);
|
||
|
||
Widget _buildPage(BuildContext context) {
|
||
// 🔥 只监听 RemoteControlCubit,避免其他状态变化导致频繁 rebuild
|
||
final remoteCubit = context.read<RemoteControlCubit>();
|
||
final targetDevice = remoteCubit.state.targetDevice;
|
||
|
||
// 🔥 调试日志:检查设备状态
|
||
debugPrint(
|
||
'🔍 [RemoteControlPage] build - targetDevice: ${targetDevice?.deviceName}',
|
||
);
|
||
|
||
if (targetDevice == null) {
|
||
debugPrint('❌ [RemoteControlPage] targetDevice 为空,显示离线页面');
|
||
return _buildOfflineScaffold();
|
||
}
|
||
|
||
// 🔥 进入页面只查询一次真实权限状态(不抢占),按接口 remoteControl 如实展示
|
||
if (_isLoadingPermission) {
|
||
// 直接置标志位,不在 build 期间调用 setState
|
||
_isLoadingPermission = false;
|
||
final deviceName = targetDevice.deviceName;
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (mounted && deviceName.isNotEmpty) {
|
||
debugPrint(
|
||
'🔑 [RemoteControlPage] 进入页面,仅查询权限状态 - deviceName: $deviceName',
|
||
);
|
||
remoteCubit.requestControlPermissionS(
|
||
deviceName,
|
||
'app',
|
||
source: '进入页面查询',
|
||
grabControl: false,
|
||
);
|
||
}
|
||
});
|
||
}
|
||
|
||
return Scaffold(
|
||
backgroundColor: Colors.black,
|
||
body: Stack(
|
||
children: [
|
||
// 在 Stack 的最底层替换:
|
||
Positioned.fill(
|
||
child: BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
||
// 只有当显示隐藏状态改变时才重构,内部的拖拽由组件自身 State 处理,不影响这里
|
||
// 🔥 倒车引导线放在单独嵌套 BlocBuilder 里实时刷新,不带着整块视频重建
|
||
buildWhen: (p, c) =>
|
||
p.showLeftPip != c.showLeftPip ||
|
||
p.showRightPip != c.showRightPip,
|
||
|
||
builder: (context, state) {
|
||
// 🔥 从 targetDevice 获取 deviceId
|
||
final targetDevice = context
|
||
.watch<RemoteControlCubit>()
|
||
.state
|
||
.targetDevice;
|
||
final deviceId = targetDevice?.deviceName;
|
||
|
||
// 🔥 从 AppUserCubit 获取登录 token:SRS on_play 会校验,缺失会被拒绝拉流
|
||
final token = context.read<AppUserCubit>().state.user?.token;
|
||
if (deviceId != null &&
|
||
deviceId.isNotEmpty &&
|
||
token != null &&
|
||
token.isNotEmpty) {
|
||
_videoStreamUrl =
|
||
"webrtc://${TCPConsts.TCP_IP}/live/livestream/$deviceId?token=$token";
|
||
debugPrint('🎬 [WebRTC] URL构建成功: $_videoStreamUrl');
|
||
} else {
|
||
_videoStreamUrl = '';
|
||
debugPrint(
|
||
'❌ [WebRTC] URL构建失败 - deviceId: $deviceId, hasToken: ${token != null}',
|
||
);
|
||
}
|
||
final int originY = context
|
||
.watch<RemoteControlCubit>()
|
||
.state
|
||
.controlEntity
|
||
.originY;
|
||
// debugPrint("${originY},originY");
|
||
return Stack(
|
||
children: [
|
||
WebRTCLocalPlayer(
|
||
streamUrl: _videoStreamUrl,
|
||
showLeftPip: state.showLeftPip,
|
||
showRightPip: state.showRightPip,
|
||
isFrontMain: originY >= 0,
|
||
onDoubleTap: () {
|
||
debugPrint('👆 [RemoteControlPage] 双击屏幕,切换前后视角');
|
||
// 🔥 通过 TCP 发送切换视角指令
|
||
// 这里需要调用 RemoteControlCubit 的方法来切换视角
|
||
context.read<RemoteControlCubit>().toggleCameraView();
|
||
},
|
||
),
|
||
// 🔥 倒车影像引导线动画层:由摇杆(滑块)数据实时驱动。
|
||
// 嵌套 BlocBuilder 监听完整 reverseCamera,转向/挡位一变就重绘,确保跟手。
|
||
// IgnorePointer 不阻挡双击切视角等手势
|
||
BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
||
buildWhen: (p, c) => p.reverseCamera != c.reverseCamera,
|
||
builder: (context, rs) => rs.reverseCamera.isReversing
|
||
? Positioned.fill(
|
||
child: IgnorePointer(
|
||
child: DrivingGuideOverlay(
|
||
steering: rs.reverseCamera.steeringAngle,
|
||
),
|
||
),
|
||
)
|
||
: const SizedBox.shrink(),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
),
|
||
),
|
||
|
||
BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
||
buildWhen: (p, c) => p.isEmergency != c.isEmergency,
|
||
builder: (context, state) {
|
||
return state.isEmergency
|
||
? const Positioned.fill(child: EmergencyOverlay())
|
||
: const SizedBox.shrink();
|
||
},
|
||
),
|
||
|
||
SafeArea(
|
||
child: Column(
|
||
children: [
|
||
// 🔥 用 BlocConsumer 包裹,确保 hasPermission 变化时重建
|
||
BlocConsumer<RemoteControlCubit, RemoteControlState>(
|
||
listenWhen: (p, c) => p.hasPermission != c.hasPermission,
|
||
listener: (context, state) {
|
||
debugPrint(
|
||
'🔍 [RemoteControlPage] BlocConsumer listener - hasPermission=${state.hasPermission}',
|
||
);
|
||
},
|
||
buildWhen: (p, c) =>
|
||
p.hasPermission != c.hasPermission ||
|
||
p.isLocked != c.isLocked ||
|
||
p.topRightIsExpanded != c.topRightIsExpanded ||
|
||
p.obstacleRecognitionFlag != c.obstacleRecognitionFlag ||
|
||
p.showLeftPip != c.showLeftPip ||
|
||
p.showRightPip != c.showRightPip ||
|
||
p.ping != c.ping ||
|
||
p.runningStatusModel.voltage !=
|
||
c.runningStatusModel.voltage ||
|
||
p.runningStatusModel.controlMode !=
|
||
c.runningStatusModel.controlMode ||
|
||
p.battery != c.battery,
|
||
builder: (context, state) {
|
||
// 🔥 调试日志:确认 BlocBuilder 接收到的状态值
|
||
/* debugPrint(
|
||
'🔍 [RemoteControlPage] BlocConsumer builder - hasPermission=${state.hasPermission}',
|
||
);*/
|
||
return TopStatusBar(remoteState: state);
|
||
},
|
||
),
|
||
Expanded(
|
||
child: LayoutBuilder(
|
||
builder: (context, constraints) {
|
||
final double sideAreaWidth = constraints.maxWidth * 0.25;
|
||
final double centerAreaWidth = constraints.maxWidth * 0.5;
|
||
|
||
// 关键:使用 BlocBuilder 局部刷新摇杆,不要 watch 整个 Page
|
||
return BlocBuilder<
|
||
RemoteControlCubit,
|
||
RemoteControlState
|
||
>(
|
||
// 只有 isLocked 改变时才重构摇杆区域,摇杆坐标改变由内部处理
|
||
buildWhen: (p, c) => p.isLocked != c.isLocked,
|
||
builder: (context, remoteState) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
// 左摇杆
|
||
SizedBox(
|
||
width: sideAreaWidth,
|
||
child: Align(
|
||
alignment: Alignment.bottomCenter,
|
||
child: LeftJoystickArea(
|
||
isLocked: remoteState.isLocked,
|
||
width: sideAreaWidth * 0.45,
|
||
),
|
||
),
|
||
),
|
||
|
||
// 中间区
|
||
SizedBox(
|
||
width: centerAreaWidth,
|
||
child: Align(
|
||
alignment: Alignment.bottomCenter,
|
||
child: CenterControlArea(
|
||
totalWidth: centerAreaWidth,
|
||
),
|
||
),
|
||
),
|
||
|
||
// 右摇杆
|
||
SizedBox(
|
||
width: sideAreaWidth,
|
||
child: Align(
|
||
alignment: Alignment.bottomCenter,
|
||
child: RightJoystickArea(
|
||
isLocked: remoteState.isLocked,
|
||
width: sideAreaWidth * 0.45,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// 🔥 显示权限请求弹窗 (使用 showDialog 替代 Overlay)
|
||
Future<void> _showPermissionDialog(RemoteControlState state) async {
|
||
debugPrint('🔔 [权限弹窗] _showPermissionDialog 被调用');
|
||
|
||
final requestingDeviceId =
|
||
state.requestingDeviceId ??
|
||
_l10n.translate('remote_control.perm_unknown_device');
|
||
final requestingPlatform =
|
||
state.requestingPlatform ??
|
||
_l10n.translate('remote_control.perm_unknown_platform');
|
||
final dialogContent = _l10n
|
||
.translate('remote_control.permission_request_body')
|
||
.replaceAll('{platform}', requestingPlatform)
|
||
.replaceAll('{deviceId}', requestingDeviceId);
|
||
|
||
debugPrint(
|
||
'🔔 [权限弹窗] 准备显示弹窗 - requestingPlatform=$requestingPlatform, requestingDeviceId=$requestingDeviceId',
|
||
);
|
||
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false, // 禁止点击外部关闭
|
||
builder: (dialogContext) => AlertDialog(
|
||
title: Text(
|
||
AppLocalizations.of(
|
||
dialogContext,
|
||
).translate('remote_control.permission_request_title'),
|
||
),
|
||
content: Text(dialogContent),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () async {
|
||
// debugPrint('👆 [权限弹窗] 用户点击了拒绝按钮');
|
||
final deviceId =
|
||
context
|
||
.read<DevicesCubit>()
|
||
.state
|
||
.selectedDevice
|
||
?.deviceName ??
|
||
"";
|
||
final remoteCubit = context.read<RemoteControlCubit>();
|
||
|
||
// 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去
|
||
// debugPrint('👆 [权限弹窗] ====================状态检查====================');
|
||
// debugPrint('👆 [权限弹窗] showPermissionRequestDialog: ${remoteCubit.state.showPermissionRequestDialog}');
|
||
// debugPrint('👆 [权限弹窗] targetDevice: ${remoteCubit.state.targetDevice}');
|
||
// debugPrint('👆 [权限弹窗] requestingDeviceId: ${remoteCubit.state.requestingDeviceId}');
|
||
// debugPrint('👆 [权限弹窗] hasPermission: ${remoteCubit.state.hasPermission}');
|
||
// debugPrint('👆 [权限弹窗] ==============================================');
|
||
|
||
/* debugPrint(
|
||
'📋 [权限弹窗] targetDevice=${remoteCubit.state.targetDevice}',
|
||
);*/
|
||
|
||
// 🔥 先发送 TCP 响应,等完成后再关闭弹窗
|
||
// debugPrint('🔑 [权限弹窗] 用户拒绝,调用 confirmPermissionResponse');
|
||
final targetDevice = remoteCubit.state.targetDevice;
|
||
if (targetDevice != null) {
|
||
debugPrint(
|
||
'✅ [权限弹窗] targetDevice 不为空,开始调用 confirmPermissionResponse',
|
||
);
|
||
await remoteCubit.confirmPermissionResponse(
|
||
targetDevice.deviceName,
|
||
'app',
|
||
false,
|
||
);
|
||
// debugPrint('✅ [权限弹窗] TCP响应已发送,准备关闭弹窗');
|
||
} else {
|
||
debugPrint(
|
||
'❌ [权限弹窗] targetDevice 为空,无法调用 confirmPermissionResponse',
|
||
);
|
||
}
|
||
|
||
// 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
|
||
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
|
||
// 🔥 一律用 await 之前就捕获的 remoteCubit,不再 context.read:
|
||
// 异步回来后页面可能已销毁,在失效 Element 上 context.read 会抛。
|
||
remoteCubit.resetPermissionCoolDown();
|
||
remoteCubit.togglePermissionDialog(false);
|
||
|
||
// 🔥 关闭弹窗(在状态重置之后):弹窗可能已被上一点击关闭,
|
||
// 不守卫就会在 defunct context 上 pop 抛异常。
|
||
if (dialogContext.mounted && Navigator.canPop(dialogContext)) {
|
||
Navigator.pop(dialogContext);
|
||
}
|
||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||
},
|
||
child: Text(
|
||
AppLocalizations.of(
|
||
dialogContext,
|
||
).translate('remote_control.refuse'),
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: () async {
|
||
debugPrint('👆 [权限弹窗] 用户点击了同意按钮');
|
||
final deviceId =
|
||
context
|
||
.read<DevicesCubit>()
|
||
.state
|
||
.selectedDevice
|
||
?.deviceName ??
|
||
"";
|
||
final remoteCubit = context.read<RemoteControlCubit>();
|
||
|
||
// 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去
|
||
/* debugPrint(
|
||
'👆 [权限弹窗] ====================状态检查====================',
|
||
);
|
||
debugPrint(
|
||
'👆 [权限弹窗] showPermissionRequestDialog: ${remoteCubit.state.showPermissionRequestDialog}',
|
||
);
|
||
debugPrint(
|
||
'👆 [权限弹窗] targetDevice: ${remoteCubit.state.targetDevice}',
|
||
);
|
||
debugPrint(
|
||
'👆 [权限弹窗] requestingDeviceId: ${remoteCubit.state.requestingDeviceId}',
|
||
);
|
||
debugPrint(
|
||
'👆 [权限弹窗] hasPermission: ${remoteCubit.state.hasPermission}',
|
||
);
|
||
debugPrint(
|
||
'👆 [权限弹窗] ==============================================',
|
||
);
|
||
|
||
debugPrint(
|
||
'📋 [权限弹窗] targetDevice=${remoteCubit.state.targetDevice}',
|
||
);*/
|
||
|
||
// 🔥 先发送 TCP 响应,等完成后再关闭弹窗
|
||
debugPrint('🔑 [权限弹窗] 用户同意,调用 confirmPermissionResponse');
|
||
final targetDevice = remoteCubit.state.targetDevice;
|
||
if (targetDevice != null) {
|
||
debugPrint('✅ [权限弹窗] targetDevice 不为空');
|
||
await remoteCubit.confirmPermissionResponse(
|
||
targetDevice.deviceName,
|
||
'app',
|
||
true,
|
||
);
|
||
debugPrint('✅ [权限弹窗] TCP响应已发送,准备关闭弹窗');
|
||
} else {
|
||
debugPrint(
|
||
'❌ [权限弹窗] targetDevice 为空,无法调用 confirmPermissionResponse',
|
||
);
|
||
}
|
||
|
||
// 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
|
||
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
|
||
// 🔥 同拒绝分支:用 await 前就捕获的 remoteCubit,不跨 await 用 context.read。
|
||
remoteCubit.resetPermissionCoolDown();
|
||
remoteCubit.togglePermissionDialog(false);
|
||
|
||
// 🔥 关闭弹窗(在状态重置之后):同样防重复点击后的 defunct pop。
|
||
if (dialogContext.mounted && Navigator.canPop(dialogContext)) {
|
||
Navigator.pop(dialogContext);
|
||
}
|
||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||
},
|
||
child: Text(
|
||
AppLocalizations.of(
|
||
dialogContext,
|
||
).translate('remote_control.agree'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildOfflineScaffold() {
|
||
return Scaffold(
|
||
backgroundColor: Colors.black,
|
||
body: Center(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.signal_wifi_off, color: Colors.white, size: 60),
|
||
const SizedBox(height: 20),
|
||
Text(
|
||
AppLocalizations.of(
|
||
context,
|
||
).translate('remote_control.device_disconnected'),
|
||
style: const TextStyle(color: Colors.white, fontSize: 18),
|
||
),
|
||
const SizedBox(height: 20),
|
||
ElevatedButton(
|
||
onPressed: () => context.pop(),
|
||
child: Text(
|
||
AppLocalizations.of(context).translate('remote_control.back'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 🔥 显示当前控制设备的弹窗 - 已移除:进入远程遥控页不再弹出设备确认弹窗
|
||
}
|