fix(tv): route remote keys to native text input

This commit is contained in:
edde746
2026-05-23 19:19:59 +02:00
parent 4c43d6fdec
commit 993a0e4666
2 changed files with 22 additions and 23 deletions
+2 -6
View File
@@ -185,13 +185,9 @@ bool _shouldPassNativeTvKeyToPlatform({required bool usesTvKeyboard, required bo
// Android TV provides its own IME. Remote keys must reach the platform so
// users can move around that keyboard instead of escaping the app field.
// Some remotes (Chromecast) are reported by Flutter as keyboard events, so
// native TV navigation cannot rely on deviceType.
final key = event.logicalKey;
final isEngineSynthesizedTvSelect = key == LogicalKeyboardKey.select || key == LogicalKeyboardKey.gameButtonA;
if (event.isPhysicalKeyboardEvent && !isEngineSynthesizedTvSelect) {
_logTvTextInput('native-pass=false reason=physical-keyboard key=(${_describeTextInputKey(event)})');
return false;
}
final shouldPass = key.isDpadDirection || key.isBackKey || event.isTvSelectEvent;
_logTvTextInput(
'native-pass=$shouldPass reason=${shouldPass ? "remote-navigation-key" : "not-navigation-key"} '
+20 -17
View File
@@ -317,6 +317,7 @@ void main() {
final downResult = handler(fieldFocusNode, _remoteKey(LogicalKeyboardKey.arrowDown));
final selectResult = handler(fieldFocusNode, _remoteKey(LogicalKeyboardKey.select));
final backResult = handler(fieldFocusNode, _remoteKey(LogicalKeyboardKey.goBack));
final keyboardDownResult = handler(fieldFocusNode, _keyboardDpadKey(LogicalKeyboardKey.arrowDown));
final synthesizedSelectResult = handler(
fieldFocusNode,
const KeyDownEvent(
@@ -326,12 +327,15 @@ void main() {
deviceType: ui.KeyEventDeviceType.keyboard,
),
);
final keyboardBackResult = handler(fieldFocusNode, _keyboardDpadKey(LogicalKeyboardKey.goBack));
await tester.pump();
expect(downResult, KeyEventResult.skipRemainingHandlers);
expect(selectResult, KeyEventResult.skipRemainingHandlers);
expect(backResult, KeyEventResult.skipRemainingHandlers);
expect(keyboardDownResult, KeyEventResult.skipRemainingHandlers);
expect(synthesizedSelectResult, KeyEventResult.skipRemainingHandlers);
expect(keyboardBackResult, KeyEventResult.skipRemainingHandlers);
expect(fieldFocusNode.hasPrimaryFocus, isTrue);
expect(nextFocusNode.hasFocus, isFalse);
expect(selects, 0);
@@ -394,30 +398,19 @@ void main() {
expect(gamepadFocusStates, [true, false]);
});
testWidgets('Android TV physical keyboard still uses field navigation', (tester) async {
testWidgets('Android TV physical keyboard text keys fall through to the field', (tester) async {
TvDetectionService.debugSetAppleTVOverride(null);
await TvDetectionService.getInstance(forceTv: true);
TvDetectionService.setForceTVSync(true);
final controller = TextEditingController();
final fieldFocusNode = FocusNode(debugLabel: 'name_field');
final nextFocusNode = FocusNode(debugLabel: 'next_button');
addTearDown(controller.dispose);
addTearDown(fieldFocusNode.dispose);
addTearDown(nextFocusNode.dispose);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Column(
children: [
FocusableTextField(
controller: controller,
focusNode: fieldFocusNode,
onNavigateDown: nextFocusNode.requestFocus,
),
FilledButton(focusNode: nextFocusNode, onPressed: () {}, child: const Text('Next')),
],
),
body: FocusableTextField(controller: controller, focusNode: fieldFocusNode),
),
),
);
@@ -427,16 +420,17 @@ void main() {
final result = fieldFocusNode.onKeyEvent!(
fieldFocusNode,
const KeyDownEvent(
physicalKey: PhysicalKeyboardKey.arrowDown,
logicalKey: LogicalKeyboardKey.arrowDown,
physicalKey: PhysicalKeyboardKey.keyA,
logicalKey: LogicalKeyboardKey.keyA,
character: 'a',
timeStamp: Duration.zero,
deviceType: ui.KeyEventDeviceType.keyboard,
),
);
await tester.pump();
expect(result, KeyEventResult.handled);
expect(nextFocusNode.hasPrimaryFocus, isTrue);
expect(result, KeyEventResult.ignored);
expect(fieldFocusNode.hasPrimaryFocus, isTrue);
});
testWidgets('tvOS engine-synthesized select opens the virtual keyboard', (tester) async {
@@ -579,6 +573,15 @@ KeyDownEvent _remoteKey(LogicalKeyboardKey key) {
);
}
KeyDownEvent _keyboardDpadKey(LogicalKeyboardKey key) {
return KeyDownEvent(
physicalKey: _physicalKeyFor(key),
logicalKey: key,
timeStamp: Duration.zero,
deviceType: ui.KeyEventDeviceType.keyboard,
);
}
PhysicalKeyboardKey _physicalKeyFor(LogicalKeyboardKey key) {
if (key == LogicalKeyboardKey.arrowDown) return PhysicalKeyboardKey.arrowDown;
if (key == LogicalKeyboardKey.goBack) return PhysicalKeyboardKey.escape;