207 lines
7.4 KiB
Dart
207 lines
7.4 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/my_video_player.dart';
|
||
import '../widgets/right_joystick_area.dart';
|
||
import '../widgets/top_status_bar.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: [
|
||
// 【优化层 1】底层视频:传入固定参数,加上 RepaintBoundary
|
||
Positioned.fill(
|
||
child: MyVideoPlayer(
|
||
deviceIp: TCPConsts.TCP_IP,
|
||
device: currentDevice,
|
||
user: userState.user!,
|
||
),
|
||
),
|
||
|
||
// 【优化层 2】中层:急停效果(只在 isEmergency 变化时才触发显示/隐藏)
|
||
BlocBuilder<RemoteControlCubit, RemoteControlState>(
|
||
buildWhen: (p, c) => p.isEmergency != c.isEmergency,
|
||
builder: (context, state) {
|
||
return state.isEmergency ? const Positioned.fill(child: EmergencyOverlay()) : const SizedBox.shrink();
|
||
},
|
||
),
|
||
|
||
// 【优化层 3】顶层:UI 控制层
|
||
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("同意"),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|