fix(tvos): unify remote and text input ownership
This commit is contained in:
@@ -11,30 +11,50 @@ import 'dpad_navigator.dart';
|
||||
import 'key_event_utils.dart';
|
||||
import 'owned_focus_node_binding.dart';
|
||||
|
||||
bool _usesTvKeyboard(bool enableTvKeyboard) => enableTvKeyboard && PlatformDetector.isTV();
|
||||
enum TvTextInputPresentation {
|
||||
/// Use the native platform keyboard for single-line Apple TV input and the
|
||||
/// Flutter overlay on other TVs or for multiline input.
|
||||
automatic,
|
||||
|
||||
/// Always use the platform text input implementation.
|
||||
platform,
|
||||
|
||||
/// Always use Plezy's in-app Flutter keyboard overlay.
|
||||
flutterOverlay,
|
||||
}
|
||||
|
||||
bool _usesTvKeyboard({required TvTextInputPresentation presentation, TextInputType? keyboardType, int? maxLines}) {
|
||||
if (!PlatformDetector.isTV()) return false;
|
||||
return switch (presentation) {
|
||||
TvTextInputPresentation.automatic =>
|
||||
!PlatformDetector.isAppleTV() || _isMultilineTextInput(keyboardType: keyboardType, maxLines: maxLines),
|
||||
TvTextInputPresentation.platform => false,
|
||||
TvTextInputPresentation.flutterOverlay => true,
|
||||
};
|
||||
}
|
||||
|
||||
String? _keyboardHint(InputDecoration? decoration) => decoration?.hintText ?? decoration?.labelText;
|
||||
|
||||
enum TvKeyboardAutoOpenBehavior {
|
||||
/// Open the TV virtual keyboard whenever the field receives focus.
|
||||
enum TvTextInputAutoOpenBehavior {
|
||||
/// Open the selected TV text input presentation whenever the field receives
|
||||
/// focus.
|
||||
onFocus,
|
||||
|
||||
/// Keep initial focus on the field without opening the keyboard, then open
|
||||
/// Keep initial focus on the field without opening text input, then open it
|
||||
/// automatically on later focus entries. Explicit tap/select still opens it.
|
||||
afterFirstFocus,
|
||||
|
||||
/// Never auto-open the TV virtual keyboard on focus. Explicit tap/select
|
||||
/// still opens it.
|
||||
/// Never auto-open text input on focus. Explicit tap/select still opens it.
|
||||
never,
|
||||
}
|
||||
|
||||
/// Imperative handle to the TV on-screen keyboard of a [FocusableTextField] /
|
||||
/// Imperative handle to TV text input for a [FocusableTextField] /
|
||||
/// [FocusableTextFormField]. Pass the same instance to the field's
|
||||
/// `tvKeyboardController`; the field's host attaches itself on mount.
|
||||
/// `tvTextInputController`; the field's host attaches itself on mount.
|
||||
///
|
||||
/// Only meaningful on TV. On other platforms the OSK never exists, so every
|
||||
/// method is an effective no-op.
|
||||
class TvKeyboardController {
|
||||
/// Only meaningful on TV. On other platforms every method is an effective
|
||||
/// no-op.
|
||||
class TvTextInputController {
|
||||
_FocusableTextInputHostState? _host;
|
||||
|
||||
void _attach(_FocusableTextInputHostState host) => _host = host;
|
||||
@@ -42,14 +62,13 @@ class TvKeyboardController {
|
||||
if (identical(_host, host)) _host = null;
|
||||
}
|
||||
|
||||
/// Dismiss the OSK if it is open (and prevent it from auto-reopening while
|
||||
/// the field keeps focus). No-op when nothing is open.
|
||||
void closeKeyboard() => _host?._dismissTvKeyboard();
|
||||
/// Dismiss the Flutter overlay if it is open and prevent it from reopening
|
||||
/// while the field keeps focus.
|
||||
void closeTextInput() => _host?._dismissTvKeyboard();
|
||||
|
||||
/// Focus the field but do NOT open the OSK for this focus entry. Used as a
|
||||
/// fallback landing spot (e.g. a remote search that returned no results) so
|
||||
/// the remote isn't stranded on an off-screen element.
|
||||
void focusInputWithoutKeyboard() => _host?._focusWithoutKeyboard();
|
||||
/// Focus the field without opening either native or Flutter text input for
|
||||
/// this focus entry.
|
||||
void focusInputWithoutOpening() => _host?._focusWithoutKeyboard();
|
||||
}
|
||||
|
||||
String _describeTextInputKey(KeyEvent event) {
|
||||
@@ -117,6 +136,8 @@ KeyEventResult _handleInputKey({
|
||||
required bool usesTvKeyboard,
|
||||
required bool enabled,
|
||||
required VoidCallback openKeyboard,
|
||||
required bool activateNativeTextInput,
|
||||
required VoidCallback activateNativeTextInputCallback,
|
||||
required KeyEvent event,
|
||||
TextInputType? keyboardType,
|
||||
TextInputAction? textInputAction,
|
||||
@@ -154,7 +175,17 @@ KeyEventResult _handleInputKey({
|
||||
);
|
||||
}
|
||||
|
||||
if (_shouldPassNativeTvKeyToPlatform(usesTvKeyboard: usesTvKeyboard, enabled: enabled, event: event)) {
|
||||
if (activateNativeTextInput && enabled && event.isTvSelectEvent) {
|
||||
if (event is KeyDownEvent) activateNativeTextInputCallback();
|
||||
return finish(KeyEventResult.handled, 'activate-native-tv-text-input');
|
||||
}
|
||||
|
||||
if (_shouldPassNativeTvKeyToPlatform(
|
||||
usesTvKeyboard: usesTvKeyboard,
|
||||
nativeTextInputActive: !activateNativeTextInput,
|
||||
enabled: enabled,
|
||||
event: event,
|
||||
)) {
|
||||
return finish(KeyEventResult.skipRemainingHandlers, 'pass-native-tv-key-to-platform');
|
||||
}
|
||||
|
||||
@@ -247,11 +278,17 @@ KeyEventResult _handleInputKey({
|
||||
return finish(KeyEventResult.ignored, 'fall-through');
|
||||
}
|
||||
|
||||
bool _shouldPassNativeTvKeyToPlatform({required bool usesTvKeyboard, required bool enabled, required KeyEvent event}) {
|
||||
if (!enabled || usesTvKeyboard || !PlatformDetector.isTV()) {
|
||||
bool _shouldPassNativeTvKeyToPlatform({
|
||||
required bool usesTvKeyboard,
|
||||
required bool nativeTextInputActive,
|
||||
required bool enabled,
|
||||
required KeyEvent event,
|
||||
}) {
|
||||
if (!enabled || usesTvKeyboard || !nativeTextInputActive || !PlatformDetector.isTV()) {
|
||||
if (TextInputDiagnostics.enabled) {
|
||||
_logTvTextInput(
|
||||
'native-pass=false reason=disabled-or-custom-keyboard enabled=$enabled usesTvKeyboard=$usesTvKeyboard '
|
||||
'native-pass=false reason=inactive-disabled-or-custom enabled=$enabled '
|
||||
'usesTvKeyboard=$usesTvKeyboard nativeTextInputActive=$nativeTextInputActive '
|
||||
'isTv=${PlatformDetector.isTV()} key=(${_describeTextInputKey(event)})',
|
||||
);
|
||||
}
|
||||
@@ -546,9 +583,9 @@ abstract class _FocusableTextInputBase extends StatelessWidget {
|
||||
final VoidCallback? onBack;
|
||||
final bool autofocus;
|
||||
final bool enabled;
|
||||
final bool enableTvKeyboard;
|
||||
final TvKeyboardAutoOpenBehavior tvKeyboardAutoOpenBehavior;
|
||||
final TvKeyboardController? tvKeyboardController;
|
||||
final TvTextInputPresentation tvTextInputPresentation;
|
||||
final TvTextInputAutoOpenBehavior tvTextInputAutoOpenBehavior;
|
||||
final TvTextInputController? tvTextInputController;
|
||||
final bool obscureText;
|
||||
final bool autocorrect;
|
||||
final bool enableSuggestions;
|
||||
@@ -580,9 +617,9 @@ abstract class _FocusableTextInputBase extends StatelessWidget {
|
||||
this.onBack,
|
||||
this.autofocus = false,
|
||||
this.enabled = true,
|
||||
this.enableTvKeyboard = true,
|
||||
this.tvKeyboardAutoOpenBehavior = TvKeyboardAutoOpenBehavior.onFocus,
|
||||
this.tvKeyboardController,
|
||||
this.tvTextInputPresentation = TvTextInputPresentation.automatic,
|
||||
this.tvTextInputAutoOpenBehavior = TvTextInputAutoOpenBehavior.onFocus,
|
||||
this.tvTextInputController,
|
||||
this.obscureText = false,
|
||||
this.autocorrect = true,
|
||||
this.enableSuggestions = true,
|
||||
@@ -599,7 +636,8 @@ abstract class _FocusableTextInputBase extends StatelessWidget {
|
||||
this.onNavigateDown,
|
||||
});
|
||||
|
||||
bool get _hasTvKeyboard => _usesTvKeyboard(enableTvKeyboard);
|
||||
bool get _hasTvKeyboard =>
|
||||
_usesTvKeyboard(presentation: tvTextInputPresentation, keyboardType: keyboardType, maxLines: maxLines);
|
||||
bool get _usesNativeTvKeyboard => PlatformDetector.isTV() && !_hasTvKeyboard;
|
||||
|
||||
VoidCallback? get _effectiveOnEditingComplete {
|
||||
@@ -627,23 +665,41 @@ abstract class _FocusableTextInputBase extends StatelessWidget {
|
||||
bool? enableInteractiveSelection,
|
||||
VoidCallback? onTap,
|
||||
})
|
||||
_tvInputConfiguration(bool usesTvKeyboard, VoidCallback openKeyboard) {
|
||||
_tvInputConfiguration({
|
||||
required bool usesTvKeyboard,
|
||||
required bool nativeTextInputReadOnly,
|
||||
required VoidCallback openKeyboard,
|
||||
required VoidCallback activateNativeTextInput,
|
||||
}) {
|
||||
return (
|
||||
keyboardType: usesTvKeyboard ? TextInputType.none : keyboardType,
|
||||
readOnly: usesTvKeyboard,
|
||||
showCursor: usesTvKeyboard ? true : null,
|
||||
readOnly: usesTvKeyboard || nativeTextInputReadOnly,
|
||||
showCursor: usesTvKeyboard || nativeTextInputReadOnly ? true : null,
|
||||
enableInteractiveSelection: usesTvKeyboard ? false : enableInteractiveSelection,
|
||||
onTap: usesTvKeyboard ? openKeyboard : null,
|
||||
onTap: usesTvKeyboard
|
||||
? openKeyboard
|
||||
: nativeTextInputReadOnly
|
||||
? activateNativeTextInput
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
KeyEventResult _handleKey(BuildContext context, FocusNode node, KeyEvent event, VoidCallback openKeyboard) {
|
||||
KeyEventResult _handleKey(
|
||||
BuildContext context,
|
||||
FocusNode node,
|
||||
KeyEvent event,
|
||||
VoidCallback openKeyboard, {
|
||||
required bool activateNativeTextInput,
|
||||
required VoidCallback activateNativeTextInputCallback,
|
||||
}) {
|
||||
return _handleInputKey(
|
||||
controller: controller,
|
||||
node: node,
|
||||
usesTvKeyboard: _hasTvKeyboard,
|
||||
enabled: enabled,
|
||||
openKeyboard: openKeyboard,
|
||||
activateNativeTextInput: activateNativeTextInput,
|
||||
activateNativeTextInputCallback: activateNativeTextInputCallback,
|
||||
event: event,
|
||||
keyboardType: keyboardType,
|
||||
textInputAction: textInputAction,
|
||||
@@ -662,16 +718,23 @@ abstract class _FocusableTextInputBase extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFocusableInput(
|
||||
Widget Function(bool usesTvKeyboard, FocusNode focusNode, VoidCallback openKeyboard) builder,
|
||||
) {
|
||||
Widget buildFocusableInput(_FocusableTextInputBuilder builder) {
|
||||
return _FocusableTextInputHost(input: this, builder: builder);
|
||||
}
|
||||
}
|
||||
|
||||
typedef _FocusableTextInputBuilder =
|
||||
Widget Function({
|
||||
required bool usesTvKeyboard,
|
||||
required bool nativeTextInputReadOnly,
|
||||
required FocusNode focusNode,
|
||||
required VoidCallback openKeyboard,
|
||||
required VoidCallback activateNativeTextInput,
|
||||
});
|
||||
|
||||
class _FocusableTextInputHost extends StatefulWidget {
|
||||
final _FocusableTextInputBase input;
|
||||
final Widget Function(bool usesTvKeyboard, FocusNode focusNode, VoidCallback openKeyboard) builder;
|
||||
final _FocusableTextInputBuilder builder;
|
||||
|
||||
const _FocusableTextInputHost({required this.input, required this.builder});
|
||||
|
||||
@@ -693,6 +756,9 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
bool _suppressTvKeyboardAutoOpen = false;
|
||||
bool _hasSeenTvKeyboardFocus = false;
|
||||
bool _suppressTvKeyboardForCurrentFocus = false;
|
||||
bool _nativeTextInputActivated = false;
|
||||
bool _hasSeenNativeTextInputFocus = false;
|
||||
bool _suppressNativeTextInputForCurrentFocus = false;
|
||||
|
||||
FocusNode get _effectiveFocusNode => _focusNodeBinding.node;
|
||||
|
||||
@@ -700,15 +766,15 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNodeBinding.bind(externalNode: widget.input.focusNode, debugLabel: 'FocusableTextInput');
|
||||
widget.input.tvKeyboardController?._attach(this);
|
||||
widget.input.tvTextInputController?._attach(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_FocusableTextInputHost oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!identical(oldWidget.input.tvKeyboardController, widget.input.tvKeyboardController)) {
|
||||
oldWidget.input.tvKeyboardController?._detach(this);
|
||||
widget.input.tvKeyboardController?._attach(this);
|
||||
if (!identical(oldWidget.input.tvTextInputController, widget.input.tvTextInputController)) {
|
||||
oldWidget.input.tvTextInputController?._detach(this);
|
||||
widget.input.tvTextInputController?._attach(this);
|
||||
}
|
||||
if (oldWidget.input.focusNode != widget.input.focusNode) {
|
||||
// An open keyboard dialog intentionally survives rebuilds and focusNode
|
||||
@@ -719,13 +785,24 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
_tvKeyboardOpenScheduled = false;
|
||||
_hasSeenTvKeyboardFocus = false;
|
||||
_suppressTvKeyboardForCurrentFocus = false;
|
||||
_nativeTextInputActivated = false;
|
||||
_hasSeenNativeTextInputFocus = false;
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
}
|
||||
_handleFocusChanged();
|
||||
if (oldWidget.input.tvTextInputPresentation != widget.input.tvTextInputPresentation ||
|
||||
oldWidget.input.tvTextInputAutoOpenBehavior != widget.input.tvTextInputAutoOpenBehavior) {
|
||||
_nativeTextInputActivated = false;
|
||||
_hasSeenNativeTextInputFocus = false;
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _handleFocusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.input.tvKeyboardController?._detach(this);
|
||||
widget.input.tvTextInputController?._detach(this);
|
||||
_restoreInstalledHandler();
|
||||
// The keyboard is a navigator route — it must not outlive the field that
|
||||
// opened it (e.g. a form section swapped out while the keyboard is up).
|
||||
@@ -739,10 +816,53 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
}
|
||||
|
||||
void _handleFocusChanged() {
|
||||
_syncNativeTextInputActivation();
|
||||
_syncNativeTextInputFocus();
|
||||
_syncTvKeyboardAutoOpen();
|
||||
}
|
||||
|
||||
void _syncNativeTextInputActivation() {
|
||||
final input = widget.input;
|
||||
final focused = _installedFocusNode?.hasFocus == true && input.enabled && input._usesNativeTvKeyboard;
|
||||
if (!focused) {
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
_setNativeTextInputActivated(false);
|
||||
return;
|
||||
}
|
||||
if (_suppressNativeTextInputForCurrentFocus) return;
|
||||
|
||||
switch (input.tvTextInputAutoOpenBehavior) {
|
||||
case TvTextInputAutoOpenBehavior.onFocus:
|
||||
_hasSeenNativeTextInputFocus = true;
|
||||
_setNativeTextInputActivated(true);
|
||||
case TvTextInputAutoOpenBehavior.afterFirstFocus:
|
||||
if (!_hasSeenNativeTextInputFocus) {
|
||||
_hasSeenNativeTextInputFocus = true;
|
||||
_suppressNativeTextInputForCurrentFocus = true;
|
||||
return;
|
||||
}
|
||||
_setNativeTextInputActivated(true);
|
||||
case TvTextInputAutoOpenBehavior.never:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _setNativeTextInputActivated(bool activated) {
|
||||
if (_nativeTextInputActivated == activated) return;
|
||||
if (!mounted) {
|
||||
_nativeTextInputActivated = activated;
|
||||
return;
|
||||
}
|
||||
setState(() => _nativeTextInputActivated = activated);
|
||||
}
|
||||
|
||||
void _activateNativeTextInput() {
|
||||
if (!widget.input.enabled || !widget.input._usesNativeTvKeyboard) return;
|
||||
_hasSeenNativeTextInputFocus = true;
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
_setNativeTextInputActivated(true);
|
||||
}
|
||||
|
||||
void _syncNativeTextInputFocus() {
|
||||
final focused = _installedFocusNode?.hasFocus == true && widget.input.enabled && widget.input._usesNativeTvKeyboard;
|
||||
if (TextInputDiagnostics.enabled) {
|
||||
@@ -761,7 +881,7 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
if (TextInputDiagnostics.enabled) {
|
||||
_logTvTextInput(
|
||||
'Host.syncTvKeyboardAutoOpen focused=$focused open=$_tvKeyboardOpen scheduled=$_tvKeyboardOpenScheduled '
|
||||
'suppressed=$_suppressTvKeyboardAutoOpen behavior=${widget.input.tvKeyboardAutoOpenBehavior} '
|
||||
'suppressed=$_suppressTvKeyboardAutoOpen behavior=${widget.input.tvTextInputAutoOpenBehavior} '
|
||||
'seenFocus=$_hasSeenTvKeyboardFocus suppressCurrent=$_suppressTvKeyboardForCurrentFocus '
|
||||
'installed=${_installedFocusNode?.debugLabel} '
|
||||
'hasFocus=${_installedFocusNode?.hasFocus} enabled=${widget.input.enabled} '
|
||||
@@ -797,17 +917,17 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
}
|
||||
|
||||
bool _shouldAutoOpenTvKeyboardForCurrentFocus() {
|
||||
switch (widget.input.tvKeyboardAutoOpenBehavior) {
|
||||
case TvKeyboardAutoOpenBehavior.onFocus:
|
||||
switch (widget.input.tvTextInputAutoOpenBehavior) {
|
||||
case TvTextInputAutoOpenBehavior.onFocus:
|
||||
return true;
|
||||
case TvKeyboardAutoOpenBehavior.afterFirstFocus:
|
||||
case TvTextInputAutoOpenBehavior.afterFirstFocus:
|
||||
if (!_hasSeenTvKeyboardFocus) {
|
||||
_hasSeenTvKeyboardFocus = true;
|
||||
_suppressTvKeyboardForCurrentFocus = true;
|
||||
return false;
|
||||
}
|
||||
return !_suppressTvKeyboardForCurrentFocus;
|
||||
case TvKeyboardAutoOpenBehavior.never:
|
||||
case TvTextInputAutoOpenBehavior.never:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -886,19 +1006,22 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
_tvKeyboardHandle?.close();
|
||||
}
|
||||
|
||||
/// Focus the field without opening the OSK for this focus entry. Suppress is
|
||||
/// set BEFORE requestFocus so the resulting focus-change sync already sees it;
|
||||
/// the flag persists for this focus and resets on the next unfocus. If a
|
||||
/// same-turn focus request supersedes this one, clear the unused suppression
|
||||
/// after Flutter resolves its pending focus change.
|
||||
/// Focus the field without opening either native or Flutter text input for
|
||||
/// this focus entry. Suppression is set before requestFocus so the resulting
|
||||
/// focus-change callback cannot open either presentation.
|
||||
void _focusWithoutKeyboard() {
|
||||
_suppressTvKeyboardAutoOpen = true;
|
||||
_suppressNativeTextInputForCurrentFocus = true;
|
||||
_tvKeyboardOpenScheduled = false;
|
||||
if (widget.input._usesNativeTvKeyboard) {
|
||||
_setNativeTextInputActivated(false);
|
||||
}
|
||||
final focusNode = _installedFocusNode ?? _effectiveFocusNode;
|
||||
focusNode.requestFocus();
|
||||
scheduleMicrotask(() {
|
||||
if (!mounted || focusNode.hasFocus || _tvKeyboardOpen || _tvKeyboardOpenScheduled) return;
|
||||
_suppressTvKeyboardAutoOpen = false;
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -921,7 +1044,41 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
);
|
||||
if (result != KeyEventResult.ignored) return result;
|
||||
}
|
||||
return widget.input._handleKey(context, node, event, _openTvKeyboard);
|
||||
var activateNativeTextInput = widget.input._usesNativeTvKeyboard && !_nativeTextInputActivated;
|
||||
final isRemoteNavigation = event.logicalKey.isDpadDirection || event.logicalKey.isBackKey || event.isTvSelectEvent;
|
||||
if (PlatformDetector.isAppleTV() &&
|
||||
widget.input._usesNativeTvKeyboard &&
|
||||
_nativeTextInputActivated &&
|
||||
event is KeyDownEvent &&
|
||||
isRemoteNavigation) {
|
||||
// Remote navigation events are system-owned while the native keyboard
|
||||
// is active. Receiving one here proves that UIKit has dismissed the
|
||||
// keyboard while Flutter focus stayed on the field. Restore the
|
||||
// read-only gate so this press navigates Flutter instead of reopening
|
||||
// the input connection.
|
||||
_suppressNativeTextInputForCurrentFocus = true;
|
||||
_setNativeTextInputActivated(false);
|
||||
activateNativeTextInput = true;
|
||||
if (event.logicalKey.isBackKey) {
|
||||
// This is the Menu press that dismissed UIKit's keyboard. Consume its
|
||||
// Flutter continuation so one press cannot also pop the app route.
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (event.isTvSelectEvent) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _activateNativeTextInput();
|
||||
});
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
return widget.input._handleKey(
|
||||
context,
|
||||
node,
|
||||
event,
|
||||
_openTvKeyboard,
|
||||
activateNativeTextInput: activateNativeTextInput,
|
||||
activateNativeTextInputCallback: _activateNativeTextInput,
|
||||
);
|
||||
}
|
||||
|
||||
void _installKeyHandler(FocusNode node) {
|
||||
@@ -946,6 +1103,8 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
void _restoreInstalledHandler() {
|
||||
_logTvTextInput('Host.restoreInstalledHandler node=${_installedFocusNode?.debugLabel}');
|
||||
_setNativeTextInputFocused(false);
|
||||
_nativeTextInputActivated = false;
|
||||
_suppressNativeTextInputForCurrentFocus = false;
|
||||
final node = _installedFocusNode;
|
||||
if (node != null) {
|
||||
node.removeListener(_focusListener);
|
||||
@@ -961,8 +1120,19 @@ class _FocusableTextInputHostState extends State<_FocusableTextInputHost> {
|
||||
Widget build(BuildContext context) {
|
||||
final focusNode = _effectiveFocusNode;
|
||||
_installKeyHandler(focusNode);
|
||||
_handleFocusChanged();
|
||||
return widget.builder(widget.input._hasTvKeyboard, focusNode, _openTvKeyboard);
|
||||
_syncNativeTextInputFocus();
|
||||
_syncTvKeyboardAutoOpen();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _handleFocusChanged();
|
||||
});
|
||||
final usesTvKeyboard = widget.input._hasTvKeyboard;
|
||||
return widget.builder(
|
||||
usesTvKeyboard: usesTvKeyboard,
|
||||
nativeTextInputReadOnly: widget.input._usesNativeTvKeyboard && !_nativeTextInputActivated,
|
||||
focusNode: focusNode,
|
||||
openKeyboard: _openTvKeyboard,
|
||||
activateNativeTextInput: _activateNativeTextInput,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,9 +1159,9 @@ class FocusableTextField extends _FocusableTextInputBase {
|
||||
super.onBack,
|
||||
super.autofocus,
|
||||
super.enabled,
|
||||
super.enableTvKeyboard,
|
||||
super.tvKeyboardAutoOpenBehavior,
|
||||
super.tvKeyboardController,
|
||||
super.tvTextInputPresentation,
|
||||
super.tvTextInputAutoOpenBehavior,
|
||||
super.tvTextInputController,
|
||||
super.obscureText,
|
||||
super.autocorrect,
|
||||
super.enableSuggestions,
|
||||
@@ -1010,11 +1180,22 @@ class FocusableTextField extends _FocusableTextInputBase {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return buildFocusableInput((usesTvKeyboard, effectiveFocusNode, openKeyboard) {
|
||||
final tvInput = _tvInputConfiguration(usesTvKeyboard, openKeyboard);
|
||||
return buildFocusableInput(({
|
||||
required bool usesTvKeyboard,
|
||||
required bool nativeTextInputReadOnly,
|
||||
required FocusNode focusNode,
|
||||
required VoidCallback openKeyboard,
|
||||
required VoidCallback activateNativeTextInput,
|
||||
}) {
|
||||
final tvInput = _tvInputConfiguration(
|
||||
usesTvKeyboard: usesTvKeyboard,
|
||||
nativeTextInputReadOnly: nativeTextInputReadOnly,
|
||||
openKeyboard: openKeyboard,
|
||||
activateNativeTextInput: activateNativeTextInput,
|
||||
);
|
||||
return TextField(
|
||||
controller: controller,
|
||||
focusNode: effectiveFocusNode,
|
||||
focusNode: focusNode,
|
||||
enabled: enabled,
|
||||
decoration: decoration,
|
||||
keyboardType: tvInput.keyboardType,
|
||||
@@ -1066,9 +1247,9 @@ class FocusableTextFormField extends _FocusableTextInputBase {
|
||||
this.onSaved,
|
||||
super.autofocus,
|
||||
super.enabled,
|
||||
super.enableTvKeyboard,
|
||||
super.tvKeyboardAutoOpenBehavior,
|
||||
super.tvKeyboardController,
|
||||
super.tvTextInputPresentation,
|
||||
super.tvTextInputAutoOpenBehavior,
|
||||
super.tvTextInputController,
|
||||
super.obscureText,
|
||||
super.autocorrect,
|
||||
super.enableSuggestions,
|
||||
@@ -1087,11 +1268,22 @@ class FocusableTextFormField extends _FocusableTextInputBase {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return buildFocusableInput((usesTvKeyboard, effectiveFocusNode, openKeyboard) {
|
||||
final tvInput = _tvInputConfiguration(usesTvKeyboard, openKeyboard);
|
||||
return buildFocusableInput(({
|
||||
required bool usesTvKeyboard,
|
||||
required bool nativeTextInputReadOnly,
|
||||
required FocusNode focusNode,
|
||||
required VoidCallback openKeyboard,
|
||||
required VoidCallback activateNativeTextInput,
|
||||
}) {
|
||||
final tvInput = _tvInputConfiguration(
|
||||
usesTvKeyboard: usesTvKeyboard,
|
||||
nativeTextInputReadOnly: nativeTextInputReadOnly,
|
||||
openKeyboard: openKeyboard,
|
||||
activateNativeTextInput: activateNativeTextInput,
|
||||
);
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
focusNode: effectiveFocusNode,
|
||||
focusNode: focusNode,
|
||||
enabled: enabled,
|
||||
decoration: decoration,
|
||||
keyboardType: tvInput.keyboardType,
|
||||
|
||||
Reference in New Issue
Block a user