feat(remote): 修复键盘输入映射并重构连接流程
- Android端注入按键时显式设置SOURCE_KEYBOARD,修复IME丢弃事件导致输入框无法输入文字的问题 - Flutter端新增Flutter逻辑键到Android keyCode的转换映射,避免功能键误触发 - 重构RemoteController,分离信令连接与远程控制逻辑,支持设备列表选择与配对码兑换 - AuthRepository接口新增getBindings、getOnlineDevices、redeemPairingCode方法 - DioAuthRepository实现401自动刷新token重试机制 - ConnectionSessionState扩展设备列表、在线状态、配对码等状态字段
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:webrtc_controller_flutter/core/proto/control_message.pb.dart';
|
||||
|
||||
/// 控制指令构造工具,对应 Android 端 RemoteTouchView 的指令格式。
|
||||
@@ -8,6 +10,63 @@ import 'package:webrtc_controller_flutter/core/proto/control_message.pb.dart';
|
||||
class ControlCommands {
|
||||
ControlCommands._();
|
||||
|
||||
/// 将 Flutter 的 [LogicalKeyboardKey] 转换为 Android 标准 [KeyEvent.keyCode]。
|
||||
///
|
||||
/// 说明:Flutter 的 [LogicalKeyboardKey.keyId] 是 Unicode 抽象值(如字母 'w'
|
||||
/// 的 keyId 为 0x77=119),而 Android 端注入按键时使用的是 [KeyEvent.keyCode]
|
||||
/// (如 KEYCODE_W=50)。两者语义不同,若直接把 Flutter 的 keyId 当作 Android
|
||||
/// keyCode 发给被控端,'w' 会被误识别为 KEYCODE_F8(119) 等,导致触发系统组合键
|
||||
/// /功能键(屏幕乱跳、输入框无字符输入)。因此这里统一转换成 Android keyCode。
|
||||
static int toAndroidKeyCode(LogicalKeyboardKey key) {
|
||||
// 字母 a-z / A-Z:Android KEYCODE_A=29 起
|
||||
const a = 0x61; // 'a'
|
||||
const z = 0x7a; // 'z'
|
||||
const au = 0x41; // 'A'
|
||||
const zu = 0x5a; // 'Z'
|
||||
final id = key.keyId;
|
||||
if (id >= a && id <= z) return id - a + 29;
|
||||
if (id >= au && id <= zu) return id - au + 29;
|
||||
// 数字 0-9:Android KEYCODE_0=7 起
|
||||
if (id >= 0x30 && id <= 0x39) return id - 0x30 + 7;
|
||||
|
||||
// 其余按键用 LogicalKeyboardKey 常量精确匹配,避免魔法数字。
|
||||
// 注意:LogicalKeyboardKey 重写了 ==/hashCode,不能作为 const map 的 key,
|
||||
// 因此使用普通(非 const)Map 字面量。
|
||||
const home = LogicalKeyboardKey.home;
|
||||
const end = LogicalKeyboardKey.end;
|
||||
final map = <LogicalKeyboardKey, int>{
|
||||
LogicalKeyboardKey.space: 62,
|
||||
LogicalKeyboardKey.enter: 66,
|
||||
LogicalKeyboardKey.numpadEnter: 66,
|
||||
LogicalKeyboardKey.backspace: 67,
|
||||
LogicalKeyboardKey.tab: 61,
|
||||
LogicalKeyboardKey.escape: 111,
|
||||
LogicalKeyboardKey.delete: 112,
|
||||
home: 122,
|
||||
end: 123,
|
||||
LogicalKeyboardKey.pageUp: 92,
|
||||
LogicalKeyboardKey.pageDown: 93,
|
||||
LogicalKeyboardKey.arrowLeft: 21,
|
||||
LogicalKeyboardKey.arrowUp: 19,
|
||||
LogicalKeyboardKey.arrowRight: 22,
|
||||
LogicalKeyboardKey.arrowDown: 20,
|
||||
// 功能键 F1-F12:Android 131..142
|
||||
LogicalKeyboardKey.f1: 131,
|
||||
LogicalKeyboardKey.f2: 132,
|
||||
LogicalKeyboardKey.f3: 133,
|
||||
LogicalKeyboardKey.f4: 134,
|
||||
LogicalKeyboardKey.f5: 135,
|
||||
LogicalKeyboardKey.f6: 136,
|
||||
LogicalKeyboardKey.f7: 137,
|
||||
LogicalKeyboardKey.f8: 138,
|
||||
LogicalKeyboardKey.f9: 139,
|
||||
LogicalKeyboardKey.f10: 140,
|
||||
LogicalKeyboardKey.f11: 141,
|
||||
LogicalKeyboardKey.f12: 142,
|
||||
};
|
||||
return map[key] ?? 0;
|
||||
}
|
||||
|
||||
/// 单击指令。坐标 x/y 为相对于屏幕的百分比(0.0 ~ 1.0)。
|
||||
static ControlMessage touch(double x, double y) => ControlMessage(
|
||||
action: Action.TOUCH,
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:webrtc_controller_flutter/core/utils/control_commands.dart';
|
||||
|
||||
/// 远端触摸控制层(对应 Android 端 RemoteTouchView)。
|
||||
///
|
||||
/// 捕获指针(触摸/鼠标)事件,将其转换为相对于控件区域的百分比坐标
|
||||
@@ -43,6 +45,10 @@ class RemoteTouchView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RemoteTouchViewState extends State<RemoteTouchView> {
|
||||
/// 用于捕获物理键盘事件的焦点节点。
|
||||
/// 设置 [FocusNode.skipTraversal] 避免桌面端获得焦点时绘制一圈绿色高亮边框。
|
||||
final FocusNode _focusNode = FocusNode(skipTraversal: true);
|
||||
|
||||
Offset? _startPosition;
|
||||
Offset? _currentPosition;
|
||||
DateTime? _startTime;
|
||||
@@ -77,19 +83,41 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
||||
@override
|
||||
void dispose() {
|
||||
_cancelLongPress();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 关闭框架的焦点高亮描边:本控件需要 autofocus 捕获物理键盘,但框架在
|
||||
// 检测到键盘交互(传统高亮模式)后会给焦点节点绘制一圈绿色高亮边框。
|
||||
// 将高亮策略设为 alwaysTouch 后边框不再绘制,键盘事件捕获不受影响。
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (node, event) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
focusColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
),
|
||||
child: Focus(
|
||||
autofocus: true,
|
||||
focusNode: _focusNode,
|
||||
onKeyEvent: (node, event) {
|
||||
// 必须将 Flutter 的逻辑键转换为 Android keyCode 再下发,否则会被控端
|
||||
// 误识别为功能键/组合键(屏幕乱跳、输入框无法输入文字)。
|
||||
final int androidKeyCode = ControlCommands.toAndroidKeyCode(event.logicalKey);
|
||||
if (androidKeyCode == 0) return KeyEventResult.ignored;
|
||||
if (event is KeyDownEvent) {
|
||||
widget.onKey(event.logicalKey.keyId, 0);
|
||||
widget.onKey(androidKeyCode, 0);
|
||||
return KeyEventResult.handled;
|
||||
} else if (event is KeyUpEvent) {
|
||||
widget.onKey(event.logicalKey.keyId, 1);
|
||||
widget.onKey(androidKeyCode, 1);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
@@ -177,6 +205,7 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
||||
child: SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user