Skip to content

Commit

Permalink
Reland "[text_input] introduce TextInputControl" (flutter#113758)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpnurmi committed Oct 24, 2022
1 parent a25c86c commit 28e0f08
Show file tree
Hide file tree
Showing 8 changed files with 902 additions and 83 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ 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>
Expand Down
167 changes: 167 additions & 0 deletions examples/api/lib/services/text_input/text_input_control.0.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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);
});
}

0 comments on commit 28e0f08

Please sign in to comment.