Files
feature-next-arch/lib/features/remote_control/presentation/pages/remote_control_page.dart
2026-01-21 13:27:55 +08:00

224 lines
8.2 KiB
Dart

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 '../../../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> {
@override
void initState() {
super.initState();
// 1. 锁定横屏
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
// 2. 隐藏状态栏和虚拟按键
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
@override
void dispose() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
super.dispose();
}
@override
Widget build(BuildContext context) {
// 监听全局状态(这些通常不随摇杆频繁变化)
final userState = context.watch<AppUserCubit>().state;
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
if (currentDevice == null) {
return _buildOfflineScaffold();
}
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) {
return WebRTCLocalPlayer(
// 这里的 URL 拼接根据你的后端规则
// streamUrl: "webrtc://${TCPConsts.TCP_IP}/live/livestream/${currentDevice.deviceName}?token=${userState.user!.token}",
streamUrl:
"webrtc://${TCPConsts.TCP_IP}/live/livestream/111?token=${userState.user!.token}",
showLeftPip: state.showLeftPip, // 从 Cubit 状态中读取
showRightPip: state.showRightPip, // 从 Cubit 状态中读取
);
},
),
),
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,
) {
return Container(
color: Colors.black54,
child: AlertDialog(
title: const Text("权限变更"),
content: Text("${state.permissionPlatform}端正请求控制权,同意释放吗?"),
actions: [
TextButton(
onPressed: () =>
context.read<RemoteControlCubit>().respondPermission(false),
child: const Text("拒绝"),
),
TextButton(
onPressed: () =>
context.read<RemoteControlCubit>().respondPermission(true),
child: const Text("同意"),
),
],
),
);
}
}