Fix Action.overridable
example (#110824)
This commit is contained in:
parent
3c5a074784
commit
bdb74e16c6
@ -11,150 +11,73 @@ void main() {
|
||||
runApp(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(child: SimpleUSPhoneNumberEntry()),
|
||||
body: Center(child: VerificationCodeGenerator()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// This implements a custom phone number input field that handles the
|
||||
// [DeleteCharacterIntent] intent.
|
||||
class DigitInput extends StatefulWidget {
|
||||
const DigitInput({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.focusNode,
|
||||
this.maxLength,
|
||||
this.textInputAction = TextInputAction.next,
|
||||
});
|
||||
|
||||
final int? maxLength;
|
||||
final TextEditingController controller;
|
||||
final TextInputAction textInputAction;
|
||||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
DigitInputState createState() => DigitInputState();
|
||||
const CopyTextIntent copyTextIntent = CopyTextIntent._();
|
||||
class CopyTextIntent extends Intent {
|
||||
const CopyTextIntent._();
|
||||
}
|
||||
|
||||
class DigitInputState extends State<DigitInput> {
|
||||
late final Action<DeleteCharacterIntent> _deleteTextAction =
|
||||
CallbackAction<DeleteCharacterIntent>(
|
||||
onInvoke: (DeleteCharacterIntent intent) {
|
||||
// For simplicity we delete everything in the section.
|
||||
widget.controller.clear();
|
||||
return null;
|
||||
},
|
||||
);
|
||||
class CopyableText extends StatelessWidget {
|
||||
const CopyableText({ super.key, required this.text });
|
||||
|
||||
final String text;
|
||||
|
||||
void _copy(CopyTextIntent intent) => Clipboard.setData(ClipboardData(text: text));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Actions(
|
||||
actions: <Type, Action<Intent>>{
|
||||
// Make the default `DeleteCharacterIntent` handler overridable.
|
||||
DeleteCharacterIntent: Action<DeleteCharacterIntent>.overridable(
|
||||
defaultAction: _deleteTextAction, context: context),
|
||||
},
|
||||
child: TextField(
|
||||
controller: widget.controller,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.phone,
|
||||
focusNode: widget.focusNode,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
final Action<CopyTextIntent> defaultCopyAction = CallbackAction<CopyTextIntent>(onInvoke: _copy);
|
||||
return Shortcuts(
|
||||
shortcuts: const <ShortcutActivator, Intent> { SingleActivator(LogicalKeyboardKey.keyC, control: true) : copyTextIntent },
|
||||
child: Actions(
|
||||
actions: <Type, Action<Intent>> {
|
||||
/// The Action is made overridable so the VerificationCodeGenerator
|
||||
/// widget can override how copying is handled.
|
||||
CopyTextIntent: Action<CopyTextIntent>.overridable(defaultAction: defaultCopyAction, context: context),
|
||||
},
|
||||
child: Focus(
|
||||
autofocus: true,
|
||||
child: DefaultTextStyle.merge(
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
child: Text(text),
|
||||
),
|
||||
),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(widget.maxLength),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleUSPhoneNumberEntry extends StatefulWidget {
|
||||
const SimpleUSPhoneNumberEntry({super.key});
|
||||
class VerificationCodeGenerator extends StatelessWidget {
|
||||
const VerificationCodeGenerator({ super.key });
|
||||
|
||||
@override
|
||||
State<SimpleUSPhoneNumberEntry> createState() =>
|
||||
_SimpleUSPhoneNumberEntryState();
|
||||
}
|
||||
|
||||
class _DeleteDigit extends Action<DeleteCharacterIntent> {
|
||||
_DeleteDigit(this.state);
|
||||
|
||||
final _SimpleUSPhoneNumberEntryState state;
|
||||
@override
|
||||
void invoke(DeleteCharacterIntent intent) {
|
||||
assert(callingAction != null);
|
||||
callingAction?.invoke(intent);
|
||||
|
||||
if (state.lineNumberController.text.isEmpty &&
|
||||
state.lineNumberFocusNode.hasFocus) {
|
||||
state.prefixFocusNode.requestFocus();
|
||||
}
|
||||
|
||||
if (state.prefixController.text.isEmpty && state.prefixFocusNode.hasFocus) {
|
||||
state.areaCodeFocusNode.requestFocus();
|
||||
}
|
||||
void _copy(CopyTextIntent intent) {
|
||||
debugPrint('Content copied');
|
||||
Clipboard.setData(const ClipboardData(text: '111222333'));
|
||||
}
|
||||
|
||||
// This action is only enabled when the `callingAction` exists and is
|
||||
// enabled.
|
||||
@override
|
||||
bool get isActionEnabled => callingAction?.isActionEnabled ?? false;
|
||||
}
|
||||
|
||||
class _SimpleUSPhoneNumberEntryState extends State<SimpleUSPhoneNumberEntry> {
|
||||
final FocusNode areaCodeFocusNode = FocusNode();
|
||||
final TextEditingController areaCodeController = TextEditingController();
|
||||
final FocusNode prefixFocusNode = FocusNode();
|
||||
final TextEditingController prefixController = TextEditingController();
|
||||
final FocusNode lineNumberFocusNode = FocusNode();
|
||||
final TextEditingController lineNumberController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Actions(
|
||||
actions: <Type, Action<Intent>>{
|
||||
DeleteCharacterIntent: _DeleteDigit(this),
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
actions: <Type, Action<Intent>> { CopyTextIntent: CallbackAction<CopyTextIntent>(onInvoke: _copy) },
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Expanded(
|
||||
child: Text('(', textAlign: TextAlign.center),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: DigitInput(
|
||||
focusNode: areaCodeFocusNode,
|
||||
controller: areaCodeController,
|
||||
maxLength: 3,
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: Text(')', textAlign: TextAlign.center),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: DigitInput(
|
||||
focusNode: prefixFocusNode,
|
||||
controller: prefixController,
|
||||
maxLength: 3,
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: Text('-', textAlign: TextAlign.center),
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: DigitInput(
|
||||
focusNode: lineNumberFocusNode,
|
||||
controller: lineNumberController,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLength: 4,
|
||||
),
|
||||
const Text('Press Ctrl-C to Copy'),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const <Widget>[
|
||||
CopyableText(text: '111'),
|
||||
SizedBox(width: 5,),
|
||||
CopyableText(text: '222'),
|
||||
SizedBox(width: 5,),
|
||||
CopyableText(text: '333'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -0,0 +1,51 @@
|
||||
// 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/widgets/actions/action.action_overridable.0.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final _MockClipboard mockClipboard = _MockClipboard();
|
||||
|
||||
testWidgets('Copies text on Ctrl-C', (WidgetTester tester) async {
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
|
||||
await tester.pumpWidget(const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(child: example.VerificationCodeGenerator()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(primaryFocus, isNotNull);
|
||||
expect(mockClipboard.clipboardData, isNull);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.control);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.control);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
|
||||
|
||||
expect(mockClipboard.clipboardData?['text'], '111222333');
|
||||
});
|
||||
}
|
||||
|
||||
class _MockClipboard {
|
||||
_MockClipboard();
|
||||
|
||||
Map<String, dynamic>? clipboardData;
|
||||
|
||||
Future<Object?> handleMethodCall(MethodCall methodCall) async {
|
||||
switch (methodCall.method) {
|
||||
case 'Clipboard.setData':
|
||||
clipboardData = methodCall.arguments as Map<String, dynamic>;
|
||||
return null;
|
||||
}
|
||||
if (methodCall.method.startsWith('Clipboard')) {
|
||||
throw StateError('unrecognized method call: ${methodCall.method}');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -145,12 +145,16 @@ abstract class Action<T extends Intent> with Diagnosticable {
|
||||
/// parent widgets that also support this [Intent].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample implements a custom text input field that handles the
|
||||
/// [DeleteCharacterIntent] intent, as well as a US telephone number input
|
||||
/// widget that consists of multiple text fields for area code, prefix and line
|
||||
/// number. When the backspace key is pressed, the phone number input widget
|
||||
/// sends the focus to the preceding text field when the currently focused
|
||||
/// field becomes empty.
|
||||
/// This sample shows how to implement a rudimentary `CopyableText` widget
|
||||
/// that responds to Ctrl-C by copying its own content to the clipboard.
|
||||
///
|
||||
/// if `CopyableText` is to be provided in a package, developers using the
|
||||
/// widget may want to change how copying is handled. As the author of the
|
||||
/// package, you can enable that by making the corresponding [Action]
|
||||
/// overridable. In the second part of the code sample, three `CopyableText`
|
||||
/// widgets are used to build a verification code widget which overrides the
|
||||
/// "copy" action by copying the combined numbers from all three `CopyableText`
|
||||
/// widgets.
|
||||
///
|
||||
/// ** See code in examples/api/lib/widgets/actions/action.action_overridable.0.dart **
|
||||
/// {@end-tool}
|
||||
|
Loading…
x
Reference in New Issue
Block a user