Fix: Magnifier appears and won't dismiss (#128545)
Fixes a bug when tapping near certain TextFields.
This commit is contained in:
parent
541fdd60d3
commit
40b4bc996c
@ -1088,10 +1088,26 @@ class SelectionOverlay {
|
|||||||
|
|
||||||
void _handleStartHandleDragStart(DragStartDetails details) {
|
void _handleStartHandleDragStart(DragStartDetails details) {
|
||||||
assert(!_isDraggingStartHandle);
|
assert(!_isDraggingStartHandle);
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
_isDraggingStartHandle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_isDraggingStartHandle = details.kind == PointerDeviceKind.touch;
|
_isDraggingStartHandle = details.kind == PointerDeviceKind.touch;
|
||||||
onStartHandleDragStart?.call(details);
|
onStartHandleDragStart?.call(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleStartHandleDragUpdate(DragUpdateDetails details) {
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
_isDraggingStartHandle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onStartHandleDragUpdate?.call(details);
|
||||||
|
}
|
||||||
|
|
||||||
/// Called when the users drag the start selection handles to new locations.
|
/// Called when the users drag the start selection handles to new locations.
|
||||||
final ValueChanged<DragUpdateDetails>? onStartHandleDragUpdate;
|
final ValueChanged<DragUpdateDetails>? onStartHandleDragUpdate;
|
||||||
|
|
||||||
@ -1101,6 +1117,11 @@ class SelectionOverlay {
|
|||||||
|
|
||||||
void _handleStartHandleDragEnd(DragEndDetails details) {
|
void _handleStartHandleDragEnd(DragEndDetails details) {
|
||||||
_isDraggingStartHandle = false;
|
_isDraggingStartHandle = false;
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onStartHandleDragEnd?.call(details);
|
onStartHandleDragEnd?.call(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1147,10 +1168,26 @@ class SelectionOverlay {
|
|||||||
|
|
||||||
void _handleEndHandleDragStart(DragStartDetails details) {
|
void _handleEndHandleDragStart(DragStartDetails details) {
|
||||||
assert(!_isDraggingEndHandle);
|
assert(!_isDraggingEndHandle);
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
_isDraggingEndHandle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_isDraggingEndHandle = details.kind == PointerDeviceKind.touch;
|
_isDraggingEndHandle = details.kind == PointerDeviceKind.touch;
|
||||||
onEndHandleDragStart?.call(details);
|
onEndHandleDragStart?.call(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleEndHandleDragUpdate(DragUpdateDetails details) {
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
_isDraggingEndHandle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onEndHandleDragUpdate?.call(details);
|
||||||
|
}
|
||||||
|
|
||||||
/// Called when the users drag the end selection handles to new locations.
|
/// Called when the users drag the end selection handles to new locations.
|
||||||
final ValueChanged<DragUpdateDetails>? onEndHandleDragUpdate;
|
final ValueChanged<DragUpdateDetails>? onEndHandleDragUpdate;
|
||||||
|
|
||||||
@ -1160,6 +1197,11 @@ class SelectionOverlay {
|
|||||||
|
|
||||||
void _handleEndHandleDragEnd(DragEndDetails details) {
|
void _handleEndHandleDragEnd(DragEndDetails details) {
|
||||||
_isDraggingEndHandle = false;
|
_isDraggingEndHandle = false;
|
||||||
|
// Calling OverlayEntry.remove may not happen until the following frame, so
|
||||||
|
// it's possible for the handles to receive a gesture after calling remove.
|
||||||
|
if (_handles == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onEndHandleDragEnd?.call(details);
|
onEndHandleDragEnd?.call(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1472,7 +1514,7 @@ class SelectionOverlay {
|
|||||||
handleLayerLink: startHandleLayerLink,
|
handleLayerLink: startHandleLayerLink,
|
||||||
onSelectionHandleTapped: onSelectionHandleTapped,
|
onSelectionHandleTapped: onSelectionHandleTapped,
|
||||||
onSelectionHandleDragStart: _handleStartHandleDragStart,
|
onSelectionHandleDragStart: _handleStartHandleDragStart,
|
||||||
onSelectionHandleDragUpdate: onStartHandleDragUpdate,
|
onSelectionHandleDragUpdate: _handleStartHandleDragUpdate,
|
||||||
onSelectionHandleDragEnd: _handleStartHandleDragEnd,
|
onSelectionHandleDragEnd: _handleStartHandleDragEnd,
|
||||||
selectionControls: selectionControls,
|
selectionControls: selectionControls,
|
||||||
visibility: startHandlesVisible,
|
visibility: startHandlesVisible,
|
||||||
@ -1499,7 +1541,7 @@ class SelectionOverlay {
|
|||||||
handleLayerLink: endHandleLayerLink,
|
handleLayerLink: endHandleLayerLink,
|
||||||
onSelectionHandleTapped: onSelectionHandleTapped,
|
onSelectionHandleTapped: onSelectionHandleTapped,
|
||||||
onSelectionHandleDragStart: _handleEndHandleDragStart,
|
onSelectionHandleDragStart: _handleEndHandleDragStart,
|
||||||
onSelectionHandleDragUpdate: onEndHandleDragUpdate,
|
onSelectionHandleDragUpdate: _handleEndHandleDragUpdate,
|
||||||
onSelectionHandleDragEnd: _handleEndHandleDragEnd,
|
onSelectionHandleDragEnd: _handleEndHandleDragEnd,
|
||||||
selectionControls: selectionControls,
|
selectionControls: selectionControls,
|
||||||
visibility: endHandlesVisible,
|
visibility: endHandlesVisible,
|
||||||
|
@ -15803,7 +15803,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
final TextField textField = TextField(
|
final TextField textField = TextField(
|
||||||
magnifierConfiguration: TextMagnifierConfiguration(
|
magnifierConfiguration: TextMagnifierConfiguration(
|
||||||
magnifierBuilder: (_, __, ___) => customMagnifier,
|
magnifierBuilder: (BuildContext context, MagnifierController controller, ValueNotifier<MagnifierInfo>? info) => customMagnifier,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -15901,7 +15901,7 @@ void main() {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
magnifierConfiguration: TextMagnifierConfiguration(
|
magnifierConfiguration: TextMagnifierConfiguration(
|
||||||
magnifierBuilder: (
|
magnifierBuilder: (
|
||||||
_,
|
BuildContext context,
|
||||||
MagnifierController controller,
|
MagnifierController controller,
|
||||||
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
||||||
) {
|
) {
|
||||||
@ -15965,7 +15965,7 @@ void main() {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
magnifierConfiguration: TextMagnifierConfiguration(
|
magnifierConfiguration: TextMagnifierConfiguration(
|
||||||
magnifierBuilder: (
|
magnifierBuilder: (
|
||||||
_,
|
BuildContext context,
|
||||||
MagnifierController controller,
|
MagnifierController controller,
|
||||||
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
||||||
) {
|
) {
|
||||||
@ -16067,7 +16067,7 @@ void main() {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
magnifierConfiguration: TextMagnifierConfiguration(
|
magnifierConfiguration: TextMagnifierConfiguration(
|
||||||
magnifierBuilder: (
|
magnifierBuilder: (
|
||||||
_,
|
BuildContext context,
|
||||||
MagnifierController controller,
|
MagnifierController controller,
|
||||||
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
||||||
) {
|
) {
|
||||||
@ -16118,6 +16118,54 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.iOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.iOS }));
|
||||||
|
|
||||||
|
testWidgets('magnifier does not show when tapping outside field', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/128321
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: TextField(
|
||||||
|
magnifierConfiguration: TextMagnifierConfiguration(
|
||||||
|
magnifierBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
MagnifierController controller,
|
||||||
|
ValueNotifier<MagnifierInfo> localMagnifierInfo
|
||||||
|
) {
|
||||||
|
magnifierInfo = localMagnifierInfo;
|
||||||
|
return fakeMagnifier;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTapOutside: (PointerDownEvent event) {
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tapAt(
|
||||||
|
tester.getCenter(find.byType(TextField)),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.startGesture(
|
||||||
|
tester.getBottomLeft(find.byType(TextField)) - const Offset(10.0, 20.0),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
|
},
|
||||||
|
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
|
||||||
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('TapRegion integration', () {
|
group('TapRegion integration', () {
|
||||||
@ -16513,7 +16561,7 @@ class _ObscureTextTestWidgetState extends State<_ObscureTextTestWidget> {
|
|||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (_) {
|
builder: (BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextField(
|
TextField(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user