Unverified Commit 9064bf67 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Revert "[text_input] introduce TextInputControl (#76072)" (#113724)

This reverts commit 2fdfa29e.
parent 38ef9410
......@@ -77,7 +77,6 @@ Hidenori Matsubayashi <Hidenori.Matsubayashi@sony.com>
Perqin Xie <perqinxie@gmail.com>
Seongyun Kim <helloworld@cau.ac.kr>
Ludwik Trammer <ludwik@gmail.com>
J-P Nurmi <jpnurmi@gmail.com>
Marian Triebe <m.triebe@live.de>
Alexis Rouillard <contact@arouillard.fr>
Mirko Mucaria <skogsfrae@gmail.com>
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for TextInputControl
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
MyStatefulWidgetState createState() => MyStatefulWidgetState();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
void dispose() {
super.dispose();
_controller.dispose();
_focusNode.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextField(
autofocus: true,
controller: _controller,
focusNode: _focusNode,
decoration: InputDecoration(
suffix: IconButton(
icon: const Icon(Icons.clear),
tooltip: 'Clear and unfocus',
onPressed: () {
_controller.clear();
_focusNode.unfocus();
},
),
),
),
),
bottomSheet: const MyVirtualKeyboard(),
);
}
}
class MyVirtualKeyboard extends StatefulWidget {
const MyVirtualKeyboard({super.key});
@override
MyVirtualKeyboardState createState() => MyVirtualKeyboardState();
}
class MyVirtualKeyboardState extends State<MyVirtualKeyboard> {
final MyTextInputControl _inputControl = MyTextInputControl();
@override
void initState() {
super.initState();
_inputControl.register();
}
@override
void dispose() {
super.dispose();
_inputControl.unregister();
}
void _handleKeyPress(String key) {
_inputControl.processUserInput(key);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _inputControl.visible,
builder: (_, bool visible, __) {
return Visibility(
visible: visible,
child: FocusScope(
canRequestFocus: false,
child: TextFieldTapRegion(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (final String key in <String>['A', 'B', 'C'])
ElevatedButton(
child: Text(key),
onPressed: () => _handleKeyPress(key),
),
],
),
),
),
);
},
);
}
}
class MyTextInputControl with TextInputControl {
TextEditingValue _editingState = TextEditingValue.empty;
final ValueNotifier<bool> _visible = ValueNotifier<bool>(false);
/// The input control's visibility state for updating the visual presentation.
ValueListenable<bool> get visible => _visible;
/// Register the input control.
void register() => TextInput.setInputControl(this);
/// Restore the original platform input control.
void unregister() => TextInput.restorePlatformInputControl();
@override
void show() => _visible.value = true;
@override
void hide() => _visible.value = false;
@override
void setEditingState(TextEditingValue value) => _editingState = value;
/// Process user input.
///
/// Updates the internal editing state by inserting the input text,
/// and by replacing the current selection if any.
void processUserInput(String input) {
_editingState = _editingState.copyWith(
text: _insertText(input),
selection: _replaceSelection(input),
);
// Request the attached client to update accordingly.
TextInput.updateEditingValue(_editingState);
}
String _insertText(String input) {
final String text = _editingState.text;
final TextSelection selection = _editingState.selection;
return text.replaceRange(selection.start, selection.end, input);
}
TextSelection _replaceSelection(String input) {
final TextSelection selection = _editingState.selection;
return TextSelection.collapsed(offset: selection.start + input.length);
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_api_samples/services/text_input/text_input_control.0.dart'
as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Enter text using the VKB', (WidgetTester tester) async {
await tester.pumpWidget(const example.MyApp());
await tester.pumpAndSettle();
await tester.tap(find.descendant(
of: find.byType(example.MyVirtualKeyboard),
matching: find.widgetWithText(ElevatedButton, 'A'),
));
await tester.pumpAndSettle();
expect(find.widgetWithText(TextField, 'A'), findsOneWidget);
await tester.tap(find.descendant(
of: find.byType(example.MyVirtualKeyboard),
matching: find.widgetWithText(ElevatedButton, 'B'),
));
await tester.pumpAndSettle();
expect(find.widgetWithText(TextField, 'AB'), findsOneWidget);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
await tester.tap(find.descendant(
of: find.byType(example.MyVirtualKeyboard),
matching: find.widgetWithText(ElevatedButton, 'C'),
));
await tester.pumpAndSettle();
expect(find.widgetWithText(TextField, 'ACB'), findsOneWidget);
});
}
......@@ -2675,14 +2675,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
@override
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {
if (_hasFocus && _hasInputConnection) {
oldControl?.hide();
newControl?.show();
}
}
@override
void connectionClosed() {
if (_hasInputConnection) {
......
......@@ -139,11 +139,6 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
latestMethodCall = 'showAutocorrectionPromptRect';
}
@override
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {
latestMethodCall = 'didChangeInputControl';
}
@override
void autofill(TextEditingValue newEditingValue) => updateEditingValue(newEditingValue);
......
......@@ -292,9 +292,4 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
}
TextInputConfiguration get configuration => const TextInputConfiguration(enableDeltaModel: true);
@override
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {
latestMethodCall = 'didChangeInputControl';
}
}
......@@ -780,178 +780,6 @@ void main() {
isTrue,
);
});
group('TextInputControl', () {
late FakeTextChannel fakeTextChannel;
setUp(() {
fakeTextChannel = FakeTextChannel((MethodCall call) async {});
TextInput.setChannel(fakeTextChannel);
});
tearDown(() {
TextInput.restorePlatformInputControl();
TextInputConnection.debugResetId();
TextInput.setChannel(SystemChannels.textInput);
});
test('gets attached and detached', () {
final FakeTextInputControl control = FakeTextInputControl();
TextInput.setInputControl(control);
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
final TextInputConnection connection = TextInput.attach(client, const TextInputConfiguration());
final List<String> expectedMethodCalls = <String>['attach'];
expect(control.methodCalls, expectedMethodCalls);
connection.close();
expectedMethodCalls.add('detach');
expect(control.methodCalls, expectedMethodCalls);
});
test('receives text input state changes', () {
final FakeTextInputControl control = FakeTextInputControl();
TextInput.setInputControl(control);
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
final TextInputConnection connection = TextInput.attach(client, const TextInputConfiguration());
control.methodCalls.clear();
final List<String> expectedMethodCalls = <String>[];
connection.updateConfig(const TextInputConfiguration());
expectedMethodCalls.add('updateConfig');
expect(control.methodCalls, expectedMethodCalls);
connection.setEditingState(TextEditingValue.empty);
expectedMethodCalls.add('setEditingState');
expect(control.methodCalls, expectedMethodCalls);
connection.close();
expectedMethodCalls.add('detach');
expect(control.methodCalls, expectedMethodCalls);
});
test('does not interfere with platform text input', () {
final FakeTextInputControl control = FakeTextInputControl();
TextInput.setInputControl(control);
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
TextInput.attach(client, const TextInputConfiguration());
fakeTextChannel.outgoingCalls.clear();
fakeTextChannel.incoming!(MethodCall('TextInputClient.updateEditingState', <dynamic>[1, TextEditingValue.empty.toJSON()]));
expect(client.latestMethodCall, 'updateEditingValue');
expect(control.methodCalls, <String>['attach', 'setEditingState']);
expect(fakeTextChannel.outgoingCalls, isEmpty);
});
test('both input controls receive requests', () async {
final FakeTextInputControl control = FakeTextInputControl();
TextInput.setInputControl(control);
const TextInputConfiguration textConfig = TextInputConfiguration();
const TextInputConfiguration numberConfig = TextInputConfiguration(inputType: TextInputType.number);
const TextInputConfiguration noneConfig = TextInputConfiguration(inputType: TextInputType.none);
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
final TextInputConnection connection = TextInput.attach(client, textConfig);
final List<String> expectedMethodCalls = <String>['attach'];
expect(control.methodCalls, expectedMethodCalls);
expect(control.inputType, TextInputType.text);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall('TextInput.setClient', <dynamic>[1, noneConfig.toJson()]),
]);
connection.show();
expectedMethodCalls.add('show');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 2);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.show');
connection.updateConfig(numberConfig);
expectedMethodCalls.add('updateConfig');
expect(control.methodCalls, expectedMethodCalls);
expect(control.inputType, TextInputType.number);
expect(fakeTextChannel.outgoingCalls.length, 3);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall('TextInput.setClient', <dynamic>[1, noneConfig.toJson()]),
const MethodCall('TextInput.show'),
MethodCall('TextInput.updateConfig', noneConfig.toJson()),
]);
connection.setComposingRect(Rect.zero);
expectedMethodCalls.add('setComposingRect');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 4);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setMarkedTextRect');
connection.setCaretRect(Rect.zero);
expectedMethodCalls.add('setCaretRect');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 5);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setCaretRect');
connection.setEditableSizeAndTransform(Size.zero, Matrix4.identity());
expectedMethodCalls.add('setEditableSizeAndTransform');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 6);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setEditableSizeAndTransform');
connection.setSelectionRects(const <SelectionRect>[SelectionRect(position: 0, bounds: Rect.zero)]);
expectedMethodCalls.add('setSelectionRects');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 7);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setSelectionRects');
connection.setStyle(
fontFamily: null,
fontSize: null,
fontWeight: null,
textDirection: TextDirection.ltr,
textAlign: TextAlign.left,
);
expectedMethodCalls.add('setStyle');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 8);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setStyle');
connection.close();
expectedMethodCalls.add('detach');
expect(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 9);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.clearClient');
expectedMethodCalls.add('hide');
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
await binding.runAsync(() async {});
await expectLater(control.methodCalls, expectedMethodCalls);
expect(fakeTextChannel.outgoingCalls.length, 10);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.hide');
});
test('notifies changes to the attached client', () async {
final FakeTextInputControl control = FakeTextInputControl();
TextInput.setInputControl(control);
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
final TextInputConnection connection = TextInput.attach(client, const TextInputConfiguration());
TextInput.setInputControl(null);
expect(client.latestMethodCall, 'didChangeInputControl');
connection.show();
expect(client.latestMethodCall, 'didChangeInputControl');
});
});
}
class FakeTextInputClient with TextInputClient {
......@@ -1005,11 +833,6 @@ class FakeTextInputClient with TextInputClient {
TextInputConfiguration get configuration => const TextInputConfiguration();
@override
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {
latestMethodCall = 'didChangeInputControl';
}
@override
void insertTextPlaceholder(Size size) {
latestMethodCall = 'insertTextPlaceholder';
......@@ -1026,81 +849,3 @@ class FakeTextInputClient with TextInputClient {
performedSelectors.add(selectorName);
}
}
class FakeTextInputControl with TextInputControl {
final List<String> methodCalls = <String>[];
late TextInputType inputType;
@override
void attach(TextInputClient client, TextInputConfiguration configuration) {
methodCalls.add('attach');
inputType = configuration.inputType;
}
@override
void detach(TextInputClient client) {
methodCalls.add('detach');
}
@override
void setEditingState(TextEditingValue value) {
methodCalls.add('setEditingState');
}
@override
void updateConfig(TextInputConfiguration configuration) {
methodCalls.add('updateConfig');
inputType = configuration.inputType;
}
@override
void show() {
methodCalls.add('show');
}
@override
void hide() {
methodCalls.add('hide');
}
@override
void setComposingRect(Rect rect) {
methodCalls.add('setComposingRect');
}
@override
void setCaretRect(Rect rect) {
methodCalls.add('setCaretRect');
}
@override
void setEditableSizeAndTransform(Size editableBoxSize, Matrix4 transform) {
methodCalls.add('setEditableSizeAndTransform');
}
@override
void setSelectionRects(List<SelectionRect> selectionRects) {
methodCalls.add('setSelectionRects');
}
@override
void setStyle({
required String? fontFamily,
required double? fontSize,
required FontWeight? fontWeight,
required TextDirection textDirection,
required TextAlign textAlign,
}) {
methodCalls.add('setStyle');
}
@override
void finishAutofillContext({bool shouldSave = true}) {
methodCalls.add('finishAutofillContext');
}
@override
void requestAutofill() {
methodCalls.add('requestAutofill');
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment