2026-03-24 17:09:12 +08:00
|
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
2026-01-18 20:21:14 +08:00
|
|
|
|
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/core/consts/tcp_consts.dart';
|
2026-04-24 15:24:43 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
2026-01-18 20:21:14 +08:00
|
|
|
|
|
|
|
|
|
|
import '../../../../core/app/app_user_cubit.dart';
|
2026-03-16 15:31:14 +08:00
|
|
|
|
import '../../../../core/router/route_paths.dart';
|
2026-06-05 19:06:21 +08:00
|
|
|
|
import '../../../devices/domain/entities/device_entity.dart';
|
2026-01-18 20:21:14 +08:00
|
|
|
|
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';
|
2026-01-19 18:24:24 +08:00
|
|
|
|
import '../widgets/top_status_bar.dart';
|
2026-01-21 13:27:55 +08:00
|
|
|
|
import '../widgets/webrtc/webrtc_local_player.dart';
|
2026-01-18 20:21:14 +08:00
|
|
|
|
|
|
|
|
|
|
class RemoteControlPage extends StatefulWidget {
|
|
|
|
|
|
const RemoteControlPage({super.key});
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
State<RemoteControlPage> createState() => _RemoteControlPageState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _RemoteControlPageState extends State<RemoteControlPage> {
|
2026-03-12 16:25:57 +08:00
|
|
|
|
String _videoStreamUrl = "";
|
2026-03-25 16:42:35 +08:00
|
|
|
|
late RemoteControlCubit _cubit;
|
2026-04-16 10:49:45 +08:00
|
|
|
|
DevicesCubit? _devicesCubit;
|
2026-06-05 19:06:21 +08:00
|
|
|
|
StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅
|
|
|
|
|
|
bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示
|
|
|
|
|
|
bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态
|
2026-03-25 16:42:35 +08:00
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void didChangeDependencies() {
|
|
|
|
|
|
super.didChangeDependencies();
|
|
|
|
|
|
_cubit = context.read<RemoteControlCubit>();
|
2026-04-16 10:49:45 +08:00
|
|
|
|
_devicesCubit = context.read<DevicesCubit>();
|
2026-06-05 19:06:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 监听权限弹窗状态变化 (只订阅一次)
|
|
|
|
|
|
if (_permissionSubscription == null) {
|
|
|
|
|
|
_permissionSubscription = _cubit.stream.listen((state) {
|
|
|
|
|
|
// 🔥 加强防重复逻辑:只在状态真正变化且不在显示弹窗时才显示
|
|
|
|
|
|
if (mounted &&
|
|
|
|
|
|
state.showPermissionRequestDialog &&
|
|
|
|
|
|
!_isShowingPermissionDialog) {
|
|
|
|
|
|
_isShowingPermissionDialog = true;
|
|
|
|
|
|
debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗');
|
|
|
|
|
|
// 使用微任务确保标志位已设置
|
|
|
|
|
|
Future.microtask(() {
|
|
|
|
|
|
_showPermissionDialog(state).then((_) {
|
|
|
|
|
|
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
|
|
|
|
|
_isShowingPermissionDialog = false; // 弹窗关闭后重置标志
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-25 16:42:35 +08:00
|
|
|
|
}
|
2026-06-05 19:06:21 +08:00
|
|
|
|
|
2026-01-18 20:21:14 +08:00
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
2026-01-19 18:24:24 +08:00
|
|
|
|
// 1. 锁定横屏
|
2026-06-05 19:06:21 +08:00
|
|
|
|
SystemChrome.setPreferredOrientations([
|
|
|
|
|
|
DeviceOrientation.landscapeLeft,
|
|
|
|
|
|
DeviceOrientation.landscapeRight,
|
|
|
|
|
|
]);
|
2026-01-19 18:24:24 +08:00
|
|
|
|
// 2. 隐藏状态栏和虚拟按键
|
2026-01-18 20:21:14 +08:00
|
|
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
2026-03-10 10:53:58 +08:00
|
|
|
|
|
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
2026-03-24 17:09:12 +08:00
|
|
|
|
final remoteCubit = context.read<RemoteControlCubit>();
|
|
|
|
|
|
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('🔍 [RemoteControl] initState - targetDevice: ${remoteCubit.state.targetDevice}');
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 检查 targetDevice 是否存在
|
|
|
|
|
|
if (remoteCubit.state.targetDevice == null) {
|
|
|
|
|
|
// debugPrint('❌ [RemoteControl] targetDevice 为空,无法进入远程控制');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 弹窗显示当前控制的设备信息
|
|
|
|
|
|
_showTargetDeviceDialog(context, remoteCubit.state.targetDevice!);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 只在控制循环未启动时才启动
|
2026-03-24 17:09:12 +08:00
|
|
|
|
remoteCubit.startControlLoop();
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('✅ [RemoteControl] 控制循环已启动');
|
2026-03-10 10:53:58 +08:00
|
|
|
|
});
|
2026-01-18 20:21:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void dispose() {
|
2026-04-16 10:49:45 +08:00
|
|
|
|
// 释放远程控制权限
|
2026-06-05 19:06:21 +08:00
|
|
|
|
_cubit.releasePermission("app");
|
2026-04-16 10:49:45 +08:00
|
|
|
|
//print("远程控制要推出啦");
|
2026-06-05 19:06:21 +08:00
|
|
|
|
//final deviceState = _devicesCubit?.state;
|
|
|
|
|
|
//if (deviceState?.selectedDevice != null) {
|
|
|
|
|
|
// _cubit.releasePermission("app");
|
|
|
|
|
|
// }
|
2026-04-16 10:49:45 +08:00
|
|
|
|
|
2026-06-05 19:06:21 +08:00
|
|
|
|
_permissionSubscription?.cancel(); // 🔥 取消订阅
|
2026-01-18 20:21:14 +08:00
|
|
|
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
|
|
|
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
2026-03-25 16:42:35 +08:00
|
|
|
|
_cubit.stopControlLoop();
|
2026-01-18 20:21:14 +08:00
|
|
|
|
super.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-01-19 18:24:24 +08:00
|
|
|
|
// 监听全局状态(这些通常不随摇杆频繁变化)
|
2026-01-18 20:21:14 +08:00
|
|
|
|
final userState = context.watch<AppUserCubit>().state;
|
|
|
|
|
|
final deviceState = context.watch<DevicesCubit>().state;
|
|
|
|
|
|
final currentDevice = deviceState.selectedDevice;
|
|
|
|
|
|
|
2026-06-05 19:06:21 +08:00
|
|
|
|
final remoteCubit = context.read<RemoteControlCubit>();
|
|
|
|
|
|
final hasPermission = remoteCubit.state.hasPermission;
|
|
|
|
|
|
|
2026-03-16 15:31:14 +08:00
|
|
|
|
///context.read<RemoteControlCubit>().requestControlPermissionS(currentDevice!.deviceName, "app");
|
2026-03-10 10:53:58 +08:00
|
|
|
|
|
2026-01-19 18:24:24 +08:00
|
|
|
|
if (currentDevice == null) {
|
2026-01-18 20:21:14 +08:00
|
|
|
|
return _buildOfflineScaffold();
|
|
|
|
|
|
}
|
2026-06-05 19:06:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 只在没有权限时自动请求,避免重复调用
|
|
|
|
|
|
if (!hasPermission && _isLoadingPermission) {
|
|
|
|
|
|
// 🔥 只使用 targetDevice
|
|
|
|
|
|
final deviceName = remoteCubit.state.targetDevice?.deviceName;
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceName != null && deviceName.isNotEmpty) {
|
|
|
|
|
|
// debugPrint('🔑 [RemoteControl] 检测到无权限,自动发送权限请求 - deviceName: $deviceName');
|
|
|
|
|
|
setState(() => _isLoadingPermission = false); // 🔥 标记为已请求
|
|
|
|
|
|
remoteCubit.requestControlPermissionS(deviceName, "app").then((_) {
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
setState(() => _isLoadingPermission = false); // 🔥 请求完成后重置
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// debugPrint('❌ [RemoteControl] 无法发送权限请求 - targetDevice 为空');
|
|
|
|
|
|
// debugPrint(' targetDevice: ${remoteCubit.state.targetDevice}');
|
|
|
|
|
|
setState(() => _isLoadingPermission = false);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// debugPrint('✅ [RemoteControl] 已有权限或已请求过,跳过');
|
|
|
|
|
|
}
|
2026-01-18 20:21:14 +08:00
|
|
|
|
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
|
backgroundColor: Colors.black,
|
|
|
|
|
|
body: Stack(
|
|
|
|
|
|
children: [
|
2026-01-21 13:27:55 +08:00
|
|
|
|
// 在 Stack 的最底层替换:
|
2026-01-18 20:21:14 +08:00
|
|
|
|
Positioned.fill(
|
2026-01-21 13:27:55 +08:00
|
|
|
|
child: BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
|
|
|
|
|
// 只有当显示隐藏状态改变时才重构,内部的拖拽由组件自身 State 处理,不影响这里
|
2026-06-05 19:06:21 +08:00
|
|
|
|
buildWhen: (p, c) =>
|
|
|
|
|
|
p.showLeftPip != c.showLeftPip ||
|
|
|
|
|
|
p.showRightPip != c.showRightPip,
|
2026-03-12 16:25:57 +08:00
|
|
|
|
|
2026-01-21 13:27:55 +08:00
|
|
|
|
builder: (context, state) {
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// 🔥 只从 targetDevice 获取 deviceId
|
|
|
|
|
|
final targetDevice = context
|
|
|
|
|
|
.watch<RemoteControlCubit>()
|
|
|
|
|
|
.state
|
|
|
|
|
|
.targetDevice;
|
|
|
|
|
|
final deviceId = targetDevice?.deviceName;
|
|
|
|
|
|
|
|
|
|
|
|
// debugPrint('🔍 [WebRTC检查] targetDevice: $targetDevice, deviceId: $deviceId');
|
|
|
|
|
|
// debugPrint('🔍 [WebRTC检查] user: ${userState.user != null}, token: ${userState.user?.token != null}');
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceId != null &&
|
|
|
|
|
|
userState.user != null &&
|
|
|
|
|
|
userState.user!.token != null) {
|
|
|
|
|
|
_videoStreamUrl =
|
|
|
|
|
|
"webrtc://${TCPConsts.TCP_IP}/live/livestream/$deviceId?token=${userState.user!.token}";
|
|
|
|
|
|
// debugPrint('🎬 [WebRTC] URL构建成功: $_videoStreamUrl');
|
2026-03-12 16:25:57 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
_videoStreamUrl = '';
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('❌ [WebRTC] URL构建失败 - deviceId: $deviceId, hasUser: ${userState.user != null}, hasToken: ${userState.user?.token != null}');
|
2026-03-12 16:25:57 +08:00
|
|
|
|
}
|
2026-06-05 19:06:21 +08:00
|
|
|
|
final int originY = context
|
|
|
|
|
|
.watch<RemoteControlCubit>()
|
|
|
|
|
|
.state
|
|
|
|
|
|
.controlEntity
|
|
|
|
|
|
.originY;
|
|
|
|
|
|
// debugPrint("${originY},originY");
|
2026-01-21 13:27:55 +08:00
|
|
|
|
return WebRTCLocalPlayer(
|
|
|
|
|
|
// 这里的 URL 拼接根据你的后端规则
|
|
|
|
|
|
// streamUrl: "webrtc://${TCPConsts.TCP_IP}/live/livestream/${currentDevice.deviceName}?token=${userState.user!.token}",
|
2026-03-12 16:25:57 +08:00
|
|
|
|
streamUrl: _videoStreamUrl,
|
2026-01-21 13:27:55 +08:00
|
|
|
|
showLeftPip: state.showLeftPip, // 从 Cubit 状态中读取
|
|
|
|
|
|
showRightPip: state.showRightPip, // 从 Cubit 状态中读取
|
2026-06-05 19:06:21 +08:00
|
|
|
|
isFrontMain:
|
|
|
|
|
|
context
|
|
|
|
|
|
.watch<RemoteControlCubit>()
|
|
|
|
|
|
.state
|
|
|
|
|
|
.controlEntity
|
|
|
|
|
|
.originY >=
|
|
|
|
|
|
0,
|
2026-01-21 13:27:55 +08:00
|
|
|
|
);
|
|
|
|
|
|
},
|
2026-01-18 20:21:14 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
2026-01-19 18:24:24 +08:00
|
|
|
|
BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
|
|
|
|
|
buildWhen: (p, c) => p.isEmergency != c.isEmergency,
|
|
|
|
|
|
builder: (context, state) {
|
2026-06-05 19:06:21 +08:00
|
|
|
|
return state.isEmergency
|
|
|
|
|
|
? const Positioned.fill(child: EmergencyOverlay())
|
|
|
|
|
|
: const SizedBox.shrink();
|
2026-01-19 18:24:24 +08:00
|
|
|
|
},
|
|
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
|
|
|
|
|
|
SafeArea(
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// 🔥 用 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);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
Expanded(
|
2026-01-19 18:24:24 +08:00
|
|
|
|
child: LayoutBuilder(
|
|
|
|
|
|
builder: (context, constraints) {
|
|
|
|
|
|
final double sideAreaWidth = constraints.maxWidth * 0.25;
|
|
|
|
|
|
final double centerAreaWidth = constraints.maxWidth * 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
// 关键:使用 BlocBuilder 局部刷新摇杆,不要 watch 整个 Page
|
2026-06-05 19:06:21 +08:00
|
|
|
|
return BlocBuilder<
|
|
|
|
|
|
RemoteControlCubit,
|
|
|
|
|
|
RemoteControlState
|
|
|
|
|
|
>(
|
2026-01-19 18:24:24 +08:00
|
|
|
|
// 只有 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,
|
2026-06-05 19:06:21 +08:00
|
|
|
|
child: LeftJoystickArea(
|
|
|
|
|
|
isLocked: remoteState.isLocked,
|
|
|
|
|
|
width: sideAreaWidth * 0.45,
|
|
|
|
|
|
),
|
2026-01-19 18:24:24 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// 中间区
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: centerAreaWidth,
|
|
|
|
|
|
child: Align(
|
|
|
|
|
|
alignment: Alignment.bottomCenter,
|
2026-06-05 19:06:21 +08:00
|
|
|
|
child: CenterControlArea(
|
|
|
|
|
|
totalWidth: centerAreaWidth,
|
|
|
|
|
|
),
|
2026-01-19 18:24:24 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// 右摇杆
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: sideAreaWidth,
|
|
|
|
|
|
child: Align(
|
|
|
|
|
|
alignment: Alignment.bottomCenter,
|
2026-06-05 19:06:21 +08:00
|
|
|
|
child: RightJoystickArea(
|
|
|
|
|
|
isLocked: remoteState.isLocked,
|
|
|
|
|
|
width: sideAreaWidth * 0.45,
|
|
|
|
|
|
),
|
2026-01-19 18:24:24 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2026-01-18 20:21:14 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-06-05 19:06:21 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 显示权限请求弹窗 (使用 showDialog 替代 Overlay)
|
|
|
|
|
|
Future<void> _showPermissionDialog(RemoteControlState state) async {
|
|
|
|
|
|
debugPrint('🔔 [权限弹窗] _showPermissionDialog 被调用');
|
|
|
|
|
|
|
|
|
|
|
|
final requestingDeviceId = state.requestingDeviceId ?? '未知设备';
|
|
|
|
|
|
final requestingPlatform = state.requestingPlatform ?? '未知平台';
|
|
|
|
|
|
final dialogContent =
|
|
|
|
|
|
'$requestingPlatform 端正在请求控制权\n\n'
|
|
|
|
|
|
'设备ID: $requestingDeviceId\n\n'
|
|
|
|
|
|
'是否同意释放控制权?';
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 关闭弹窗(在 TCP 发送完成后)
|
|
|
|
|
|
Navigator.pop(dialogContext);
|
|
|
|
|
|
},
|
|
|
|
|
|
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',
|
|
|
|
|
|
);
|
2026-01-19 18:24:24 +08:00
|
|
|
|
}
|
2026-06-05 19:06:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 关闭弹窗(在 TCP 发送完成后)
|
|
|
|
|
|
Navigator.pop(dialogContext);
|
2026-01-19 18:24:24 +08:00
|
|
|
|
},
|
2026-06-05 19:06:21 +08:00
|
|
|
|
child: Text(
|
|
|
|
|
|
AppLocalizations.of(
|
|
|
|
|
|
dialogContext,
|
|
|
|
|
|
).translate('remote_control.agree'),
|
|
|
|
|
|
),
|
2026-01-19 18:24:24 +08:00
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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),
|
2026-06-05 19:06:21 +08:00
|
|
|
|
Text(
|
|
|
|
|
|
AppLocalizations.of(
|
|
|
|
|
|
context,
|
|
|
|
|
|
).translate('remote_control.device_disconnected'),
|
|
|
|
|
|
style: const TextStyle(color: Colors.white, fontSize: 18),
|
|
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
const SizedBox(height: 20),
|
2026-06-05 19:06:21 +08:00
|
|
|
|
ElevatedButton(
|
|
|
|
|
|
onPressed: () => context.pop(),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
AppLocalizations.of(context).translate('remote_control.back'),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 19:06:21 +08:00
|
|
|
|
/// 🔥 显示当前控制设备的弹窗
|
|
|
|
|
|
void _showTargetDeviceDialog(BuildContext context, DeviceEntity device) {
|
|
|
|
|
|
showDialog(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
barrierDismissible: true,
|
|
|
|
|
|
builder: (ctx) => AlertDialog(
|
|
|
|
|
|
title: const Text('🎮 远程控制'),
|
|
|
|
|
|
content: Column(
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'设备名称: ${device.deviceName}',
|
|
|
|
|
|
style: const TextStyle(fontSize: 16),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'设备ID: ${device.deviceName}',
|
|
|
|
|
|
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
actions: [
|
2026-03-16 15:31:14 +08:00
|
|
|
|
TextButton(
|
2026-06-05 19:06:21 +08:00
|
|
|
|
onPressed: () => Navigator.pop(ctx),
|
|
|
|
|
|
child: const Text('确定'),
|
2026-03-16 15:31:14 +08:00
|
|
|
|
),
|
2026-01-18 20:21:14 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|