zefyr

input.dart 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file
  2. // for details. All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'package:flutter/services.dart';
  5. import 'package:flutter/widgets.dart';
  6. import 'package:zefyr/util.dart';
  7. typedef RemoteValueChanged = Function(
  8. int start, String deleted, String inserted, TextSelection selection);
  9. class InputConnectionController implements TextInputClient {
  10. InputConnectionController(this.onValueChanged)
  11. : assert(onValueChanged != null);
  12. //
  13. // New public members
  14. //
  15. final RemoteValueChanged onValueChanged;
  16. /// Returns `true` if there is open input connection.
  17. bool get hasConnection =>
  18. _textInputConnection != null && _textInputConnection.attached;
  19. /// Opens or closes input connection based on the current state of
  20. /// [focusNode] and [value].
  21. void openOrCloseConnection(FocusNode focusNode, TextEditingValue value) {
  22. if (focusNode.hasFocus && focusNode.consumeKeyboardToken()) {
  23. openConnection(value);
  24. } else if (!focusNode.hasFocus) {
  25. closeConnection();
  26. }
  27. }
  28. void openConnection(TextEditingValue value) {
  29. if (!hasConnection) {
  30. _lastKnownRemoteTextEditingValue = value;
  31. _textInputConnection = TextInput.attach(
  32. this,
  33. new TextInputConfiguration(
  34. inputType: TextInputType.multiline,
  35. obscureText: false,
  36. autocorrect: true,
  37. inputAction: TextInputAction.newline,
  38. ),
  39. )..setEditingState(value);
  40. _sentRemoteValues.add(value);
  41. }
  42. _textInputConnection.show();
  43. }
  44. /// Closes input connection if it's currently open. Otherwise does nothing.
  45. void closeConnection() {
  46. if (hasConnection) {
  47. _textInputConnection.close();
  48. _textInputConnection = null;
  49. _lastKnownRemoteTextEditingValue = null;
  50. _sentRemoteValues.clear();
  51. }
  52. }
  53. /// Updates remote value based on current state of [document] and
  54. /// [selection].
  55. ///
  56. /// This method may not actually send an update to native side if it thinks
  57. /// remote value is up to date or identical.
  58. void updateRemoteValue(TextEditingValue value) {
  59. if (!hasConnection) return;
  60. // Since we don't keep track of composing range in value provided by
  61. // ZefyrController we need to add it here manually before comparing
  62. // with the last known remote value.
  63. // It is important to prevent excessive remote updates as it can cause
  64. // race conditions.
  65. final actualValue = value.copyWith(
  66. composing: _lastKnownRemoteTextEditingValue.composing,
  67. );
  68. if (actualValue == _lastKnownRemoteTextEditingValue) return;
  69. bool shouldRemember = value.text != _lastKnownRemoteTextEditingValue.text;
  70. _lastKnownRemoteTextEditingValue = actualValue;
  71. _textInputConnection.setEditingState(actualValue);
  72. if (shouldRemember) {
  73. // Only keep track if text changed (selection changes are not relevant)
  74. _sentRemoteValues.add(actualValue);
  75. }
  76. }
  77. //
  78. // Overridden members
  79. //
  80. @override
  81. void performAction(TextInputAction action) {
  82. // no-op
  83. }
  84. @override
  85. void updateEditingValue(TextEditingValue value) {
  86. if (_sentRemoteValues.contains(value)) {
  87. /// There is a race condition in Flutter text input plugin where sending
  88. /// updates to native side too often results in broken behavior.
  89. /// TextInputConnection.setEditingValue is an async call to native side.
  90. /// For each such call native side _always_ sends update which triggers
  91. /// this method (updateEditingValue) with the same value we've sent it.
  92. /// If multiple calls to setEditingValue happen too fast and we only
  93. /// track the last sent value then there is no way for us to filter out
  94. /// automatic callbacks from native side.
  95. /// Therefore we have to keep track of all values we send to the native
  96. /// side and when we see this same value appear here we skip it.
  97. /// This is fragile but it's probably the only available option.
  98. _sentRemoteValues.remove(value);
  99. return;
  100. }
  101. if (_lastKnownRemoteTextEditingValue == value) {
  102. // There is no difference between this value and the last known value.
  103. return;
  104. }
  105. // Note Flutter (unintentionally?) silences errors occurred during
  106. // text input update, so we have to report it ourselves.
  107. // For more details see https://github.com/flutter/flutter/issues/19191
  108. // TODO: remove try-catch when/if Flutter stops silencing these errors.
  109. try {
  110. final effectiveLastKnownValue = _lastKnownRemoteTextEditingValue;
  111. _lastKnownRemoteTextEditingValue = value;
  112. final oldText = effectiveLastKnownValue.text;
  113. final text = value.text;
  114. final cursorPosition = value.selection.extentOffset;
  115. final diff = fastDiff(oldText, text, cursorPosition);
  116. onValueChanged(diff.start, diff.deleted, diff.inserted, value.selection);
  117. } catch (e, trace) {
  118. FlutterError.reportError(new FlutterErrorDetails(
  119. exception: e,
  120. stack: trace,
  121. library: 'Zefyr',
  122. context: 'while updating editing value',
  123. ));
  124. rethrow;
  125. }
  126. }
  127. //
  128. // Private members
  129. //
  130. final List<TextEditingValue> _sentRemoteValues = [];
  131. TextInputConnection _textInputConnection;
  132. TextEditingValue _lastKnownRemoteTextEditingValue;
  133. }