Unverified Commit bdb74e16 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Fix `Action.overridable` example (#110824)

parent 3c5a0747
......@@ -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,
});
const CopyTextIntent copyTextIntent = CopyTextIntent._();
class CopyTextIntent extends Intent {
const CopyTextIntent._();
}
final int? maxLength;
final TextEditingController controller;
final TextInputAction textInputAction;
final FocusNode focusNode;
class CopyableText extends StatelessWidget {
const CopyableText({ super.key, required this.text });
@override
DigitInputState createState() => DigitInputState();
}
final String text;
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;
},
);
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});
@override
State<SimpleUSPhoneNumberEntry> createState() =>
_SimpleUSPhoneNumberEntryState();
}
class _DeleteDigit extends Action<DeleteCharacterIntent> {
_DeleteDigit(this.state);
class VerificationCodeGenerator extends StatelessWidget {
const VerificationCodeGenerator({ super.key });
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'),
],
),
],
),
......
// 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}
......
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