Files
flutterApp/lib/features/remote_control/presentation/pages/remote_control_page.dart

231 lines
9.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
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';
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/router/route_paths.dart';
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/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 = "";
StreamSubscription? _deviceStatusSubscription;
@override
void initState() {
super.initState();
// 1. 锁定横屏
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
// 2. 隐藏状态栏和虚拟按键
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
// 🔥 关键:订阅 DeviceStatusBloc 的状态流
WidgetsBinding.instance.addPostFrameCallback((_) {
final remoteCubit = context.read<RemoteControlCubit>();
final devicesCubit = context.read<DevicesCubit>();
remoteCubit.startControlLoop();
// 🔥 关键:订阅 DeviceStatusBloc 的状态流
remoteCubit.subscribeToDeviceStatus(devicesCubit.stream);
debugPrint('🚀 [遥控页面] 已启动控制循环和设备状态监听');
});
}
@override
void dispose() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
_deviceStatusSubscription?.cancel();
context.read<RemoteControlCubit>().stopControlLoop();
super.dispose();
}
@override
Widget build(BuildContext context) {
// 监听全局状态(这些通常不随摇杆频繁变化)
final userState = context.watch<AppUserCubit>().state;
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
///context.read<RemoteControlCubit>().requestControlPermissionS(currentDevice!.deviceName, "app");
if (currentDevice == null) {
return _buildOfflineScaffold();
}
context.read<RemoteControlCubit>().requestControlPermissionS(currentDevice!.deviceName, "app");
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// 在 Stack 的最底层替换:
Positioned.fill(
child: BlocBuilder<RemoteControlCubit, RemoteControlState>(
// 只有当显示隐藏状态改变时才重构,内部的拖拽由组件自身 State 处理,不影响这里
buildWhen: (p, c) => p.showLeftPip != c.showLeftPip || p.showRightPip != c.showRightPip,
builder: (context, state) {
final deviceId = context.watch<DevicesCubit>().state.selectedDevice?.deviceName;
if (deviceId != null && userState.user != null && userState.user!.token != null) {
_videoStreamUrl = "webrtc://${TCPConsts.TCP_IP}/live/livestream/$deviceId?token=${userState.user!.token}";
} else {
_videoStreamUrl = '';
}
return WebRTCLocalPlayer(
// 这里的 URL 拼接根据你的后端规则
// streamUrl: "webrtc://${TCPConsts.TCP_IP}/live/livestream/${currentDevice.deviceName}?token=${userState.user!.token}",
streamUrl: _videoStreamUrl,
showLeftPip: state.showLeftPip, // 从 Cubit 状态中读取
showRightPip: state.showRightPip, // 从 Cubit 状态中读取
isFrontMain: context.watch<RemoteControlCubit>().state.controlEntity.originY>= 0.5,
);
},
),
),
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: [
const TopStatusBar(),
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.5 * 0.7),
),
),
// 中间区
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.5 * 0.7),
),
),
],
),
);
},
);
},
),
),
],
),
),
// 权限弹窗
BlocBuilder<RemoteControlCubit, RemoteControlState>(
buildWhen: (p, c) => p.showPermissionRequestDialog != c.showPermissionRequestDialog,
builder: (context, state) {
if (state.showPermissionRequestDialog) {
return _buildPermissionDialog(context, state);
}
return const SizedBox.shrink();
},
),
],
),
);
}
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),
const Text("设备已断开连接", style: TextStyle(color: Colors.white, fontSize: 18)),
const SizedBox(height: 20),
ElevatedButton(onPressed: () => context.pop(), child: const Text("返回")),
],
),
),
);
}
Widget _buildPermissionDialog(BuildContext context, RemoteControlState state) {
final deviceId = context.read<DevicesCubit>().state.selectedDevice?.deviceName ?? "";
return Container(
color: Colors.black54,
child: AlertDialog(
title: const Text("权限变更"),
// content: Text("${state.permissionPlatform}端正请求控制权,同意释放吗?"),
content: Text("web端正请求控制权,同意释放吗?"),
actions: [
TextButton(onPressed: () => context.read<RemoteControlCubit>().respondPermission(false, deviceId), child: const Text("拒绝")),
TextButton(
onPressed: () {
context.read<RemoteControlCubit>().respondPermission(true, deviceId);
// 🔥 用户同意,延迟跳转到首页
// Future.delayed(const Duration(milliseconds: 1000), () {
// if (context.mounted) {
// context.go(RoutePaths.home);
// }
// });
},
child: const Text("同意"),
),
],
),
);
}
}