Unverified Commit 1f3ed602 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Fix crash after paste and unmount (#100589)

parent 250c3407
...@@ -1740,7 +1740,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1740,7 +1740,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();
} }
......
...@@ -11798,6 +11798,56 @@ void main() { ...@@ -11798,6 +11798,56 @@ void main() {
await tester.pump(); await tester.pump();
expect(receivedIntent, isFalse); expect(receivedIntent, isFalse);
}); });
// Regression test for https://github.com/flutter/flutter/issues/100585.
testWidgets('can paste and remove field', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'text');
late StateSetter setState;
bool showField = true;
final _PasteTextSelectionControls controls = _PasteTextSelectionControls(
onPaste: () {
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.longPress(find.byType(EditableText));
await tester.pump();
expect(state.showToolbar(), isTrue);
await tester.pumpAndSettle();
expect(find.text('Paste'), findsOneWidget);
await tester.tap(find.text('Paste'));
await tester.pumpAndSettle();
expect(tester.takeException(), null);
// On web, the text selection toolbar paste button is handled by the browser.
}, skip: kIsWeb); // [intended]
} }
class UnsettableController extends TextEditingController { class UnsettableController extends TextEditingController {
...@@ -11911,6 +11961,106 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls { ...@@ -11911,6 +11961,106 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls {
} }
} }
// Fake text selection controls that call a callback when paste happens.
class _PasteTextSelectionControls extends TextSelectionControls {
_PasteTextSelectionControls({
required this.onPaste,
});
static const double _kToolbarContentDistanceBelow = 20.0;
static const double _kToolbarContentDistance = 8.0;
final VoidCallback onPaste;
@override
Widget buildToolbar(BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset position, List<TextSelectionPoint> endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier? clipboardStatus, Offset? lastSecondaryTapDownPosition) {
final Offset selectionMidpoint = position;
final TextSelectionPoint startTextSelectionPoint = endpoints[0];
final TextSelectionPoint endTextSelectionPoint = endpoints.length > 1
? endpoints[1]
: endpoints[0];
final Offset anchorAbove = Offset(
globalEditableRegion.left + selectionMidpoint.dx,
globalEditableRegion.top + startTextSelectionPoint.point.dy - textLineHeight - _kToolbarContentDistance
);
final Offset anchorBelow = Offset(
globalEditableRegion.left + selectionMidpoint.dx,
globalEditableRegion.top + endTextSelectionPoint.point.dy + _kToolbarContentDistanceBelow,
);
return _PasteTextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
handlePaste: () => handlePaste(delegate),
);
}
@override
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight, [VoidCallback? onTap]) {
return Container();
}
@override
Size getHandleSize(double textLineHeight) {
return Size.zero;
}
@override
Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
return Offset.zero;
}
@override
bool canPaste(TextSelectionDelegate delegate) {
return true;
}
@override
Future<void> handlePaste(TextSelectionDelegate delegate) {
onPaste();
return super.handlePaste(delegate);
}
}
// A fake text selection toolbar with only a paste button.
class _PasteTextSelectionToolbar extends StatefulWidget {
const _PasteTextSelectionToolbar({
Key? key,
required this.anchorAbove,
required this.anchorBelow,
this.handlePaste,
}) : super(key: key);
final Offset anchorAbove;
final Offset anchorBelow;
final VoidCallback? handlePaste;
@override
_PasteTextSelectionToolbarState createState() => _PasteTextSelectionToolbarState();
}
class _PasteTextSelectionToolbarState extends State<_PasteTextSelectionToolbar> {
@override
Widget build(BuildContext context) {
return TextSelectionToolbar(
anchorAbove: widget.anchorAbove,
anchorBelow: widget.anchorBelow,
toolbarBuilder: (BuildContext context, Widget child) {
return Container(
color: Colors.pink,
child: child,
);
},
children: <Widget>[
TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1),
onPressed: widget.handlePaste,
child: const Text('Paste'),
),
],
);
}
}
class CustomStyleEditableText extends EditableText { class CustomStyleEditableText extends EditableText {
CustomStyleEditableText({ CustomStyleEditableText({
Key? key, Key? key,
......
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