fix(tv): host automatic multiline input in the Android IME
Android TV's docked keyboard handles multiline editors natively, so `automatic` no longer diverts them to the Flutter overlay there. Only Apple TV keeps the overlay for multiline input — its modal fullscreen system keyboard cannot edit multiline text. Surfaces that want the overlay for editing ergonomics (mpv config, connection editor, dialog text areas) already pin flutterOverlay explicitly.
This commit is contained in:
@@ -12,9 +12,10 @@ import 'key_event_utils.dart';
|
||||
import 'owned_focus_node_binding.dart';
|
||||
|
||||
enum TvTextInputPresentation {
|
||||
/// Use the native platform keyboard for single-line input on every TV and
|
||||
/// the Flutter overlay for multiline input, whose newline/caret handling
|
||||
/// the TV IMEs do not cover well.
|
||||
/// Use the native platform keyboard wherever it can host the field: always
|
||||
/// on Android TV (its docked IME handles multiline input), and for
|
||||
/// single-line input on Apple TV, whose modal fullscreen keyboard cannot
|
||||
/// edit multiline text — that falls back to the Flutter overlay.
|
||||
automatic,
|
||||
|
||||
/// Always use the platform text input implementation.
|
||||
@@ -27,7 +28,8 @@ enum TvTextInputPresentation {
|
||||
bool _usesTvKeyboard({required TvTextInputPresentation presentation, TextInputType? keyboardType, int? maxLines}) {
|
||||
if (!PlatformDetector.isTV()) return false;
|
||||
return switch (presentation) {
|
||||
TvTextInputPresentation.automatic => _isMultilineTextInput(keyboardType: keyboardType, maxLines: maxLines),
|
||||
TvTextInputPresentation.automatic =>
|
||||
PlatformDetector.isAppleTV() && _isMultilineTextInput(keyboardType: keyboardType, maxLines: maxLines),
|
||||
TvTextInputPresentation.platform => false,
|
||||
TvTextInputPresentation.flutterOverlay => true,
|
||||
};
|
||||
|
||||
@@ -499,9 +499,9 @@ class _AddJellyfinScreenState extends State<AddJellyfinScreen> with AsyncFormSta
|
||||
FocusableTextFormField(
|
||||
controller: _urlController,
|
||||
focusNode: _urlFocus,
|
||||
// Native on every TV: `automatic` would route this wrap-to-4-lines
|
||||
// field to the Flutter overlay, but it is logically single-line URL
|
||||
// input and the platform IME handles it (#1051, #1079).
|
||||
// Native on every TV: on Apple TV `automatic` would route this
|
||||
// wrap-to-4-lines field to the Flutter overlay, but it is logically
|
||||
// single-line URL input the system keyboard handles (#1051, #1079).
|
||||
tvTextInputPresentation: TvTextInputPresentation.platform,
|
||||
autofocus: true,
|
||||
tvTextInputAutoOpenBehavior: deferredUrlFieldAutoOpen,
|
||||
|
||||
@@ -849,6 +849,37 @@ void main() {
|
||||
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Android TV automatic multiline input uses the platform field', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(null);
|
||||
await TvDetectionService.getInstance(forceTv: true);
|
||||
TvDetectionService.setForceTVSync(true);
|
||||
final controller = TextEditingController();
|
||||
final fieldFocusNode = FocusNode(debugLabel: 'notes_field');
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(fieldFocusNode.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: FocusableTextField(
|
||||
controller: controller,
|
||||
focusNode: fieldFocusNode,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
fieldFocusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Unlike Apple TV's modal fullscreen keyboard, the docked Android IME
|
||||
// hosts multiline input natively — no Flutter overlay.
|
||||
expect(tester.widget<TextField>(find.byType(TextField)).readOnly, isFalse);
|
||||
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Android TV after-first-focus skips initial auto-open and opens on refocus', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(null);
|
||||
await TvDetectionService.getInstance(forceTv: true);
|
||||
|
||||
Reference in New Issue
Block a user