Files
ttstd_family_care/lib/features/connection/presentation/connection_controller.dart
T
TongTongStudio 41a514e9cc feat(connection): 添加远程控制路由并更新版本策略
- 新增 /connection 路由,从全局选中设备 SN 读取并渲染远程控制页
- 首页镜像操作改为静默下发 mirror 命令并跳转远程控制页
- 更新 AGENTS.md 版本管理策略为使用最新稳定版本
- 调整 pubspec.lock 中 hooks 与 record_use 版本
2026-08-26 06:16:44 +08:00

154 lines
4.9 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 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../data/connection_providers.dart';
import '../data/webrtc_orchestrator.dart';
import '../domain/connection_models.dart';
/// 远程控制会话状态(供 UI 渲染)。
class ConnectionSessionState {
final ConnectionState state;
final String? errorMessage;
final bool hasVideo;
const ConnectionSessionState({
this.state = ConnectionState.idle,
this.errorMessage,
this.hasVideo = false,
});
ConnectionSessionState copyWith({
ConnectionState? state,
String? errorMessage,
bool? hasVideo,
}) {
return ConnectionSessionState(
state: state ?? this.state,
errorMessage: errorMessage ?? this.errorMessage,
hasVideo: hasVideo ?? this.hasVideo,
);
}
}
/// 远程控制控制器:管理连接状态机(idle → connecting → connected → ended)。
class ConnectionController extends Notifier<ConnectionSessionState> {
WebRtcOrchestrator? _orchestrator;
@override
ConnectionSessionState build() {
return const ConnectionSessionState();
}
/// 目标设备 SN(页面初始化时设置)。
String? _targetSn;
set targetSn(String? value) => _targetSn = value;
/// 远程屏幕渲染器(供页面 RTCVideoView 渲染)。
RTCVideoRenderer? get remoteRenderer => _orchestrator?.remoteRenderer;
/// 发起连接。
Future<void> connect() async {
final sn = _targetSn;
if (sn == null || sn.isEmpty) {
state = const ConnectionSessionState(
state: ConnectionState.error,
errorMessage: '缺少目标设备',
);
return;
}
final signaling = ref.read(signalingClientProvider);
final clientName = ref.read(clientNameProvider);
state = const ConnectionSessionState(state: ConnectionState.connecting);
_orchestrator = WebRtcOrchestrator(
signaling: signaling,
targetSn: sn,
clientName: clientName,
);
_orchestrator!.onConnectionState = (connected) {
_onRtcState(connected);
};
signaling.onMessage = (message) {
// 先透传给 WebRTC 编排器处理(ANSWER/ICE 等),确保握手能继续
_orchestrator?.handleSignal(message);
// 仅"明确被拒"才进入错误态;TARGET_OFFLINE 可能因握手时序/目标会话短暂未就绪
// 而误报(如设备端刚连上信令、正在应答),不立即判定失败,避免闪断"设备不在线"。
if (message.type == SignalType.connectionRejected) {
state = ConnectionSessionState(
state: ConnectionState.error,
errorMessage: '连接被拒绝',
);
}
};
signaling.onError = (error) {
state = ConnectionSessionState(
state: ConnectionState.error,
errorMessage: error,
);
};
try {
await signaling.connect(signalServerUrl, targetSn: sn);
await _orchestrator!.start();
} catch (e) {
state = ConnectionSessionState(
state: ConnectionState.error,
errorMessage: '连接失败: $e',
);
}
}
void _onRtcState(bool connected) {
if (connected) {
state = const ConnectionSessionState(
state: ConnectionState.connected,
hasVideo: true,
);
} else if (state.state == ConnectionState.connected) {
state = const ConnectionSessionState(state: ConnectionState.ended);
}
}
/// 发送点击(相对坐标 0~1)。
void sendTap(double relX, double relY) {
debugPrint('[touch] TAP rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
_orchestrator?.sendTap(relX, relY);
}
void sendLongPress(double relX, double relY) {
debugPrint('[touch] LONG_PRESS rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
_orchestrator?.sendLongPress(relX, relY);
}
void sendSwipe(double relX1, double relY1, double relX2, double relY2) {
debugPrint('[touch] SWIPE rel=(${relX1.toStringAsFixed(3)}, ${relY1.toStringAsFixed(3)}) -> (${relX2.toStringAsFixed(3)}, ${relY2.toStringAsFixed(3)})');
_orchestrator?.sendSwipe(relX1, relY1, relX2, relY2);
}
/// 发送实时触摸事件(MOTION_EVENT,跟手用)。action:0=DOWN,1=UP,2=MOVE。
void sendMotionEvent(int action, double relX, double relY) {
debugPrint('[touch] MOTION action=$action rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
_orchestrator?.sendMotionEvent(action, relX, relY);
}
void sendKey(int keyCode) {
_orchestrator?.sendKey(keyCode);
}
void sendSetResolution({int width = 0, int height = 0, int fps = 0}) {
_orchestrator?.sendSetResolution(width: width, height: height, fps: fps);
}
/// 关闭并清理。
Future<void> disconnect() async {
await _orchestrator?.dispose();
_orchestrator = null;
ref.read(signalingClientProvider).dispose();
state = const ConnectionSessionState(state: ConnectionState.ended);
}
}