Unverified Commit ef11bff4 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Check for mount after cut postframecallback (#100776)

Fixes a crash that can happen when removing an input field immediately after cut.
parent 568eef34
...@@ -1701,7 +1701,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1701,7 +1701,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (cause == SelectionChangedCause.toolbar) { if (cause == SelectionChangedCause.toolbar) {
// Schedule a call to bringIntoView() after renderEditable updates. // Schedule a call to bringIntoView() after renderEditable updates.
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
bringIntoView(textEditingValue.selection.extent); if (mounted) {
bringIntoView(textEditingValue.selection.extent);
}
}); });
hideToolbar(); hideToolbar();
} }
......
...@@ -11804,7 +11804,7 @@ void main() { ...@@ -11804,7 +11804,7 @@ void main() {
final TextEditingController controller = TextEditingController(text: 'text'); final TextEditingController controller = TextEditingController(text: 'text');
late StateSetter setState; late StateSetter setState;
bool showField = true; bool showField = true;
final _PasteTextSelectionControls controls = _PasteTextSelectionControls( final _CustomTextSelectionControls controls = _CustomTextSelectionControls(
onPaste: () { onPaste: () {
setState(() { setState(() {
showField = false; showField = false;
...@@ -11848,6 +11848,57 @@ void main() { ...@@ -11848,6 +11848,57 @@ void main() {
expect(tester.takeException(), null); expect(tester.takeException(), null);
// On web, the text selection toolbar paste button is handled by the browser. // On web, the text selection toolbar paste button is handled by the browser.
}, skip: kIsWeb); // [intended] }, skip: kIsWeb); // [intended]
// Regression test for https://github.com/flutter/flutter/issues/100585.
testWidgets('can cut and remove field', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'text');
late StateSetter setState;
bool showField = true;
final _CustomTextSelectionControls controls = _CustomTextSelectionControls(
onCut: () {
setState(() {
showField = false;
});
},
);
await tester.pumpWidget(MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter stateSetter) {
setState = stateSetter;
if (!showField) {
return const Placeholder();
}
return EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: controls,
);
},
),
));
await tester.tap(find.byType(EditableText));
await tester.pump();
final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));
await tester.tapAt(textOffsetToPosition(tester, 2));
state.renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
expect(state.showToolbar(), isTrue);
await tester.pumpAndSettle();
expect(find.text('Cut'), findsOneWidget);
await tester.tap(find.text('Cut'));
await tester.pumpAndSettle();
expect(tester.takeException(), null);
// On web, the text selection toolbar cut button is handled by the browser.
}, skip: kIsWeb); // [intended]
} }
class UnsettableController extends TextEditingController { class UnsettableController extends TextEditingController {
...@@ -11962,15 +12013,17 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls { ...@@ -11962,15 +12013,17 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls {
} }
// Fake text selection controls that call a callback when paste happens. // Fake text selection controls that call a callback when paste happens.
class _PasteTextSelectionControls extends TextSelectionControls { class _CustomTextSelectionControls extends TextSelectionControls {
_PasteTextSelectionControls({ _CustomTextSelectionControls({
required this.onPaste, this.onPaste,
this.onCut,
}); });
static const double _kToolbarContentDistanceBelow = 20.0; static const double _kToolbarContentDistanceBelow = 20.0;
static const double _kToolbarContentDistance = 8.0; static const double _kToolbarContentDistance = 8.0;
final VoidCallback onPaste; final VoidCallback? onPaste;
final VoidCallback? onCut;
@override @override
Widget buildToolbar(BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset position, List<TextSelectionPoint> endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier? clipboardStatus, Offset? lastSecondaryTapDownPosition) { Widget buildToolbar(BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset position, List<TextSelectionPoint> endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier? clipboardStatus, Offset? lastSecondaryTapDownPosition) {
...@@ -11987,10 +12040,11 @@ class _PasteTextSelectionControls extends TextSelectionControls { ...@@ -11987,10 +12040,11 @@ class _PasteTextSelectionControls extends TextSelectionControls {
globalEditableRegion.left + selectionMidpoint.dx, globalEditableRegion.left + selectionMidpoint.dx,
globalEditableRegion.top + endTextSelectionPoint.point.dy + _kToolbarContentDistanceBelow, globalEditableRegion.top + endTextSelectionPoint.point.dy + _kToolbarContentDistanceBelow,
); );
return _PasteTextSelectionToolbar( return _CustomTextSelectionToolbar(
anchorAbove: anchorAbove, anchorAbove: anchorAbove,
anchorBelow: anchorBelow, anchorBelow: anchorBelow,
handlePaste: () => handlePaste(delegate), handlePaste: () => handlePaste(delegate),
handleCut: () => handleCut(delegate),
); );
} }
...@@ -12009,6 +12063,11 @@ class _PasteTextSelectionControls extends TextSelectionControls { ...@@ -12009,6 +12063,11 @@ class _PasteTextSelectionControls extends TextSelectionControls {
return Offset.zero; return Offset.zero;
} }
@override
bool canCut(TextSelectionDelegate delegate) {
return true;
}
@override @override
bool canPaste(TextSelectionDelegate delegate) { bool canPaste(TextSelectionDelegate delegate) {
return true; return true;
...@@ -12016,29 +12075,37 @@ class _PasteTextSelectionControls extends TextSelectionControls { ...@@ -12016,29 +12075,37 @@ class _PasteTextSelectionControls extends TextSelectionControls {
@override @override
Future<void> handlePaste(TextSelectionDelegate delegate) { Future<void> handlePaste(TextSelectionDelegate delegate) {
onPaste(); onPaste?.call();
return super.handlePaste(delegate); return super.handlePaste(delegate);
} }
@override
void handleCut(TextSelectionDelegate delegate, [ClipboardStatusNotifier? clipboardStatus]) {
onCut?.call();
return super.handleCut(delegate, clipboardStatus);
}
} }
// A fake text selection toolbar with only a paste button. // A fake text selection toolbar with only a paste button.
class _PasteTextSelectionToolbar extends StatefulWidget { class _CustomTextSelectionToolbar extends StatefulWidget {
const _PasteTextSelectionToolbar({ const _CustomTextSelectionToolbar({
Key? key, Key? key,
required this.anchorAbove, required this.anchorAbove,
required this.anchorBelow, required this.anchorBelow,
this.handlePaste, this.handlePaste,
this.handleCut,
}) : super(key: key); }) : super(key: key);
final Offset anchorAbove; final Offset anchorAbove;
final Offset anchorBelow; final Offset anchorBelow;
final VoidCallback? handlePaste; final VoidCallback? handlePaste;
final VoidCallback? handleCut;
@override @override
_PasteTextSelectionToolbarState createState() => _PasteTextSelectionToolbarState(); _CustomTextSelectionToolbarState createState() => _CustomTextSelectionToolbarState();
} }
class _PasteTextSelectionToolbarState extends State<_PasteTextSelectionToolbar> { class _CustomTextSelectionToolbarState extends State<_CustomTextSelectionToolbar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextSelectionToolbar( return TextSelectionToolbar(
...@@ -12052,7 +12119,12 @@ class _PasteTextSelectionToolbarState extends State<_PasteTextSelectionToolbar> ...@@ -12052,7 +12119,12 @@ class _PasteTextSelectionToolbarState extends State<_PasteTextSelectionToolbar>
}, },
children: <Widget>[ children: <Widget>[
TextSelectionToolbarTextButton( TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1), padding: TextSelectionToolbarTextButton.getPadding(0, 2),
onPressed: widget.handleCut,
child: const Text('Cut'),
),
TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(1, 2),
onPressed: widget.handlePaste, onPressed: widget.handlePaste,
child: const Text('Paste'), child: const Text('Paste'),
), ),
......
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