diff --git a/packages/flutter/lib/src/material/magnifier.dart b/packages/flutter/lib/src/material/magnifier.dart index 3d9c2a1669..3ebd41187f 100644 --- a/packages/flutter/lib/src/material/magnifier.dart +++ b/packages/flutter/lib/src/material/magnifier.dart @@ -289,7 +289,7 @@ class Magnifier extends StatelessWidget { /// The [kStandardVerticalFocalPointShift] value is a constant so that /// positioning of this [Magnifier] can be done with a guaranteed size, as /// opposed to an estimate. - static const double kStandardVerticalFocalPointShift = 22; + static const double kStandardVerticalFocalPointShift = 22.0; static const double _borderRadius = 40; static const double _magnification = 1.25; diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index ae2a1e0b62..ba386545a6 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1768,8 +1768,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, /// for a [TextPainter] object. TextPosition getPositionForPoint(Offset globalPosition) { _computeTextMetricsIfNeeded(); - globalPosition += -_paintOffset; - return _textPainter.getPositionForOffset(globalToLocal(globalPosition)); + return _textPainter.getPositionForOffset(globalToLocal(globalPosition) - _paintOffset); } /// Returns the [Rect] in local coordinates for the caret at the given text @@ -2070,10 +2069,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, /// to the [TextSelection.extentOffset]. void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) { _computeTextMetricsIfNeeded(); - final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset)); + final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from) - _paintOffset); final TextPosition? toPosition = to == null ? null - : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)); + : _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset); final int baseOffset = fromPosition.offset; final int extentOffset = toPosition?.offset ?? fromPosition.offset; @@ -2107,9 +2106,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, /// {@macro flutter.rendering.RenderEditable.selectPosition} void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) { _computeTextMetricsIfNeeded(); - final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset)); + final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from) - _paintOffset); final TextSelection fromWord = getWordAtOffset(fromPosition); - final TextPosition toPosition = to == null ? fromPosition : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)); + final TextPosition toPosition = to == null ? fromPosition : _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset); final TextSelection toWord = toPosition == fromPosition ? fromWord : getWordAtOffset(toPosition); final bool isFromWordBeforeToWord = fromWord.start < toWord.end; @@ -2129,7 +2128,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, void selectWordEdge({ required SelectionChangedCause cause }) { _computeTextMetricsIfNeeded(); assert(_lastTapDownPosition != null); - final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition! - _paintOffset)); + final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition!) - _paintOffset); final TextRange word = _textPainter.getWordBoundary(position); late TextSelection newSelection; if (position.offset <= word.start) { diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 5398787540..9c7e16e644 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -646,9 +646,6 @@ class TextSelectionOverlay { required Offset globalGesturePosition, required TextPosition currentTextPosition, }) { - final Offset globalRenderEditableTopLeft = renderEditable.localToGlobal(Offset.zero); - final Rect localCaretRect = renderEditable.getLocalRectForCaret(currentTextPosition); - final TextSelection lineAtOffset = renderEditable.getLineAtOffset(currentTextPosition); final TextPosition positionAtEndOfLine = TextPosition( offset: lineAtOffset.extentOffset, @@ -660,43 +657,64 @@ class TextSelectionOverlay { offset: lineAtOffset.baseOffset, ); - final Rect lineBoundaries = Rect.fromPoints( + final Rect localLineBoundaries = Rect.fromPoints( renderEditable.getLocalRectForCaret(positionAtBeginningOfLine).topCenter, renderEditable.getLocalRectForCaret(positionAtEndOfLine).bottomCenter, ); + final RenderBox? overlay = Overlay.of(context, rootOverlay: true).context.findRenderObject() as RenderBox?; + final Matrix4 transformToOverlay = renderEditable.getTransformTo(overlay); + final Rect overlayLineBoundaries = MatrixUtils.transformRect( + transformToOverlay, + localLineBoundaries, + ); + + final Rect localCaretRect = renderEditable.getLocalRectForCaret(currentTextPosition); + final Rect overlayCaretRect = MatrixUtils.transformRect( + transformToOverlay, + localCaretRect, + ); + + final Offset overlayGesturePosition = overlay?.globalToLocal(globalGesturePosition) ?? globalGesturePosition; return MagnifierInfo( - fieldBounds: globalRenderEditableTopLeft & renderEditable.size, - globalGesturePosition: globalGesturePosition, - caretRect: localCaretRect.shift(globalRenderEditableTopLeft), - currentLineBoundaries: lineBoundaries.shift(globalRenderEditableTopLeft), + fieldBounds: MatrixUtils.transformRect(transformToOverlay, renderEditable.paintBounds), + globalGesturePosition: overlayGesturePosition, + caretRect: overlayCaretRect, + currentLineBoundaries: overlayLineBoundaries, ); } - // The contact position of the gesture at the current end handle location. - // Updated when the handle moves. + // The contact position of the gesture at the current end handle location, in + // global coordinates. Updated when the handle moves. late double _endHandleDragPosition; // The distance from _endHandleDragPosition to the center of the line that it - // corresponds to. - late double _endHandleDragPositionToCenterOfLine; + // corresponds to, in global coordinates. + late double _endHandleDragTarget; void _handleSelectionEndHandleDragStart(DragStartDetails details) { if (!renderObject.attached) { return; } - // This adjusts for the fact that the selection handles may not - // perfectly cover the TextPosition that they correspond to. _endHandleDragPosition = details.globalPosition.dy; - final Offset endPoint = - renderObject.localToGlobal(_selectionOverlay.selectionEndpoints.last.point); - final double centerOfLine = endPoint.dy - renderObject.preferredLineHeight / 2; - _endHandleDragPositionToCenterOfLine = centerOfLine - _endHandleDragPosition; + + // Use local coordinates when dealing with line height. because in case of a + // scale transformation, the line height will also be scaled. + final double centerOfLineLocal = _selectionOverlay.selectionEndpoints.last.point.dy + - renderObject.preferredLineHeight / 2; + final double centerOfLineGlobal = renderObject.localToGlobal( + Offset(0.0, centerOfLineLocal), + ).dy; + _endHandleDragTarget = centerOfLineGlobal - details.globalPosition.dy; + // Instead of finding the TextPosition at the handle's location directly, + // use the vertical center of the line that it points to. This is because + // selection handles typically hang above or below the line that they point + // to. final TextPosition position = renderObject.getPositionForPoint( Offset( details.globalPosition.dx, - centerOfLine, + centerOfLineGlobal, ), ); @@ -715,7 +733,17 @@ class TextSelectionOverlay { /// The handle jumps instantly between lines when the drag reaches a full /// line's height away from the original handle position. In other words, the /// line jump happens when the contact point would be located at the same - /// place on the handle at the new line as when the gesture started. + /// place on the handle at the new line as when the gesture started, for both + /// directions. + /// + /// This is not the same as just maintaining an offset from the target and the + /// contact point. There is no point at which moving the drag up and down a + /// small sub-line-height distance will cause the cursor to jump up and down + /// between lines. The drag distance must be a full line height for the cursor + /// to change lines, for both directions. + /// + /// Both parameters must be in local coordinates because the untransformed + /// line height is used, and the return value is in local coordinates as well. double _getHandleDy(double dragDy, double handleDy) { final double distanceDragged = dragDy - handleDy; final int dragDirection = distanceDragged < 0.0 ? -1 : 1; @@ -729,13 +757,24 @@ class TextSelectionOverlay { return; } - _endHandleDragPosition = _getHandleDy(details.globalPosition.dy, _endHandleDragPosition); - final Offset adjustedOffset = Offset( + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = renderObject.globalToLocal(details.globalPosition); + + final double nextEndHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _endHandleDragPosition)).dy, + ); + _endHandleDragPosition = renderObject.localToGlobal( + Offset(0.0, nextEndHandleDragPositionLocal), + ).dy; + + final Offset handleTargetGlobal = Offset( details.globalPosition.dx, - _endHandleDragPosition + _endHandleDragPositionToCenterOfLine, + _endHandleDragPosition + _endHandleDragTarget, ); - final TextPosition position = renderObject.getPositionForPoint(adjustedOffset); + final TextPosition position = renderObject.getPositionForPoint(handleTargetGlobal); if (_selection.isCollapsed) { _selectionOverlay.updateMagnifier(_buildMagnifier( @@ -783,30 +822,37 @@ class TextSelectionOverlay { )); } - // The contact position of the gesture at the current start handle location. - // Updated when the handle moves. + // The contact position of the gesture at the current start handle location, + // in global coordinates. Updated when the handle moves. late double _startHandleDragPosition; // The distance from _startHandleDragPosition to the center of the line that - // it corresponds to. - late double _startHandleDragPositionToCenterOfLine; + // it corresponds to, in global coordinates. + late double _startHandleDragTarget; void _handleSelectionStartHandleDragStart(DragStartDetails details) { if (!renderObject.attached) { return; } - // This adjusts for the fact that the selection handles may not - // perfectly cover the TextPosition that they correspond to. _startHandleDragPosition = details.globalPosition.dy; - final Offset startPoint = - renderObject.localToGlobal(_selectionOverlay.selectionEndpoints.first.point); - final double centerOfLine = startPoint.dy - renderObject.preferredLineHeight / 2; - _startHandleDragPositionToCenterOfLine = centerOfLine - _startHandleDragPosition; + + // Use local coordinates when dealing with line height. because in case of a + // scale transformation, the line height will also be scaled. + final double centerOfLineLocal = _selectionOverlay.selectionEndpoints.first.point.dy + - renderObject.preferredLineHeight / 2; + final double centerOfLineGlobal = renderObject.localToGlobal( + Offset(0.0, centerOfLineLocal), + ).dy; + _startHandleDragTarget = centerOfLineGlobal - details.globalPosition.dy; + // Instead of finding the TextPosition at the handle's location directly, + // use the vertical center of the line that it points to. This is because + // selection handles typically hang above or below the line that they point + // to. final TextPosition position = renderObject.getPositionForPoint( Offset( details.globalPosition.dx, - centerOfLine, + centerOfLineGlobal, ), ); @@ -824,12 +870,21 @@ class TextSelectionOverlay { return; } - _startHandleDragPosition = _getHandleDy(details.globalPosition.dy, _startHandleDragPosition); - final Offset adjustedOffset = Offset( - details.globalPosition.dx, - _startHandleDragPosition + _startHandleDragPositionToCenterOfLine, + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = renderObject.globalToLocal(details.globalPosition); + final double nextStartHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _startHandleDragPosition)).dy, ); - final TextPosition position = renderObject.getPositionForPoint(adjustedOffset); + _startHandleDragPosition = renderObject.localToGlobal( + Offset(0.0, nextStartHandleDragPositionLocal), + ).dy; + final Offset handleTargetGlobal = Offset( + details.globalPosition.dx, + _startHandleDragPosition + _startHandleDragTarget, + ); + final TextPosition position = renderObject.getPositionForPoint(handleTargetGlobal); if (_selection.isCollapsed) { _selectionOverlay.updateMagnifier(_buildMagnifier( diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 3c2bb0d216..8c031c4a17 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -16287,6 +16287,112 @@ void main() { isNull, ); }); + + testWidgets('magnifier is in correct position when EditableText is scaled', (WidgetTester tester) async { + controller.text = 'hello \n world \n this \n is \n text'; + final GlobalKey magnifierKey = GlobalKey(); + const double scale = 0.5; + await tester.pumpWidget(MaterialApp( + home: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform.scale( + scale: scale, + child: EditableText( + controller: controller, + maxLines: null, + showSelectionHandles: true, + autofocus: true, + focusNode: focusNode, + style: Typography.material2018().black.titleMedium!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + selectionControls: materialTextSelectionControls, + keyboardType: TextInputType.text, + textAlign: TextAlign.right, + magnifierConfiguration: TextMagnifierConfiguration( + shouldDisplayHandlesInMagnifier: false, + magnifierBuilder: (BuildContext context, MagnifierController controller, ValueNotifier? notifier) { + return TextMagnifier( + key: magnifierKey, + magnifierInfo: notifier!, + ); + }, + ), + ), + ), + ], + ), + )); + + await tester.tapAt(textOffsetToPosition(tester, 3)); + await tester.pumpAndSettle(); + final List handles = List.from( + tester.renderObjectList( + find.descendant( + of: find.byType(CompositedTransformFollower), + matching: find.byType(Padding), + ), + ), + ); + expect(handles, hasLength(1)); + final RenderBox handle = handles.first; + expect(find.byKey(magnifierKey), findsNothing); + + final TestGesture gesture = await tester.startGesture(handle.localToGlobal(Offset( + handle.size.width / 2, + handle.size.height / 2, + ), + )); + await tester.pump(const Duration(milliseconds: 200)); + expect(find.byKey(magnifierKey), findsOneWidget); + final Offset magnifierStart = tester.getTopLeft(find.byKey(magnifierKey)); + + // Dragging by a quarter of a line height does not move the magnifier. + // Typically, when not scaled, you need to drag by a full line height to + // get the magnifier to move vertically. + final double lineHeight = findRenderEditable(tester).preferredLineHeight; + await gesture.moveBy(Offset(0.0, lineHeight / 4)); + await tester.pump(const Duration(milliseconds: 20)); + await tester.pumpAndSettle(); + expect(find.byKey(magnifierKey), findsOneWidget); + expect(tester.getTopLeft(find.byKey(magnifierKey)), magnifierStart); + + // Dragging by another quarter line height (total half a line height) does + // move the magnifier, because the text is scaled down by half. + await gesture.moveBy(Offset(0.0, lineHeight / 4)); + await tester.pump(const Duration(milliseconds: 20)); + await tester.pumpAndSettle(); + expect(find.byKey(magnifierKey), findsOneWidget); + expect( + tester.getTopLeft(find.byKey(magnifierKey)).dy, + magnifierStart.dy + lineHeight / 2, + ); + + // Drag back up by a quarter line height, cursor doesn't move. + await gesture.moveBy(Offset(0.0, -lineHeight / 4)); + await tester.pump(const Duration(milliseconds: 20)); + await tester.pumpAndSettle(); + expect(find.byKey(magnifierKey), findsOneWidget); + expect( + tester.getTopLeft(find.byKey(magnifierKey)).dy, + magnifierStart.dy + lineHeight / 2, + ); + + // Continuing the drag up to a half line height (whole line height scaled) + // does move the cursor. + await gesture.moveBy(Offset(0.0, -lineHeight / 4)); + await tester.pump(const Duration(milliseconds: 20)); + await tester.pumpAndSettle(); + expect(find.byKey(magnifierKey), findsOneWidget); + expect(tester.getTopLeft(find.byKey(magnifierKey)), magnifierStart); + + await gesture.up(); + await tester.pump(const Duration(milliseconds: 20)); + expect(find.byKey(magnifierKey), findsNothing); + + await tester.pumpAndSettle(); + }); }); // Regression test for: https://github.com/flutter/flutter/issues/117418. @@ -17248,6 +17354,200 @@ void main() { await tester.pumpAndSettle(); expect(scrollController.offset, 75.0); }); + + testWidgets('getPositionForPoint is correct when EditableText is scaled', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + controller.text = 'Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7\nLine8'; + + await tester.pumpWidget( + MaterialApp( + home: Center( + child: Transform.scale( + scale: 0.5, + child: EditableText( + key: key, + cursorColor: cursorColor, + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + maxLines: 2, + minLines: 2, + style: textStyle, + ), + ), + ), + ), + ); + + // With no scroll, the top left is the first character. + final EditableTextState state = tester.state(find.byType(EditableText)); + final Offset topLeft = tester.getTopLeft(find.byType(EditableText)); + expect( + state.renderEditable.getPositionForPoint(topLeft), + const TextPosition(offset: 0), + ); + + // After scrolling to view the fourth line, the top left is the start of the + // third line. + state.bringIntoView(const TextPosition(offset: 18)); + await tester.pumpAndSettle(); + expect( + state.renderEditable.getPositionForPoint(topLeft), + const TextPosition(offset: 12), + ); + }, + skip: kIsWeb, // [intended] + ); + + testWidgets('selectPositionAt is correct when EditableText is scaled', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + controller.text = 'Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7\nLine8'; + + await tester.pumpWidget( + MaterialApp( + home: Center( + child: Transform.scale( + scale: 0.5, + child: EditableText( + key: key, + cursorColor: cursorColor, + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + maxLines: 2, + minLines: 2, + style: textStyle, + ), + ), + ), + ), + ); + + final EditableTextState state = tester.state(find.byType(EditableText)); + final Offset topLeft = tester.getTopLeft(find.byType(EditableText)); + expect( + state.textEditingValue.selection, + const TextSelection.collapsed(offset: -1), + ); + + // Scroll to the fourth line and select the full line above that. + state.bringIntoView(const TextPosition(offset: 18)); + await tester.pumpAndSettle(); + state.renderEditable.selectPositionAt( + from: topLeft, + to: topLeft + const Offset(100.0, 0.0), + cause: SelectionChangedCause.drag, + ); + await tester.pumpAndSettle(); + expect( + state.textEditingValue.selection, + const TextSelection(baseOffset: 12, extentOffset: 17), + ); + }, + skip: kIsWeb, // [intended] + ); + + testWidgets('selectWordsInRange is correct when EditableText is scaled', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + controller.text = 'Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7\nLine8'; + + await tester.pumpWidget( + MaterialApp( + home: Center( + child: Transform.scale( + scale: 0.5, + child: EditableText( + key: key, + cursorColor: cursorColor, + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + maxLines: 2, + minLines: 2, + style: textStyle, + ), + ), + ), + ), + ); + + final EditableTextState state = tester.state(find.byType(EditableText)); + final Offset topLeft = tester.getTopLeft(find.byType(EditableText)); + expect( + state.textEditingValue.selection, + const TextSelection.collapsed(offset: -1), + ); + + // Scroll to the fourth line and select the full line above that. + state.bringIntoView(const TextPosition(offset: 18)); + await tester.pumpAndSettle(); + state.renderEditable.selectWordsInRange( + from: topLeft, + to: topLeft + const Offset(100.0, 0.0), + cause: SelectionChangedCause.drag, + ); + await tester.pumpAndSettle(); + expect( + state.textEditingValue.selection, + const TextSelection(baseOffset: 12, extentOffset: 17), + ); + }, + skip: kIsWeb, // [intended] + ); + + testWidgets('selectWordEdge is correct when EditableText is scaled', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + controller.text = 'Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7\nLine8'; + + await tester.pumpWidget( + MaterialApp( + home: Center( + child: Transform.scale( + scale: 0.5, + child: EditableText( + key: key, + cursorColor: cursorColor, + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + maxLines: 2, + minLines: 2, + style: textStyle, + ), + ), + ), + ), + ); + + final EditableTextState state = tester.state(find.byType(EditableText)); + //final Offset topLeft = tester.getTopLeft(find.byType(EditableText)); + expect( + state.textEditingValue.selection, + const TextSelection.collapsed(offset: -1), + ); + + // Scroll to the fourth line. + state.bringIntoView(const TextPosition(offset: 18)); + await tester.pumpAndSettle(); + + // Secondary tap inside of the 3rd line. + state.renderEditable.handleSecondaryTapDown(TapDownDetails( + globalPosition: textOffsetToPosition(tester, 13), + )); + expect( + state.textEditingValue.selection, + const TextSelection.collapsed(offset: -1), + ); + + // selectWordEdge moves the selection to the end of the 3rd line. + state.renderEditable.selectWordEdge( + cause: SelectionChangedCause.tap, + ); + expect( + state.textEditingValue.selection, + const TextSelection.collapsed(offset: 17, affinity: TextAffinity.upstream), + ); + }); } class UnsettableController extends TextEditingController { diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 9e21a0133d..fee2a0c576 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -1474,11 +1474,15 @@ void main() { testWidgets('can show magnifier when no handles exist', (WidgetTester tester) async { final GlobalKey magnifierKey = GlobalKey(); + Offset? builtGlobalGesturePosition; + Rect? builtFieldBounds; final SelectionOverlay selectionOverlay = await pumpApp( tester, magnifierConfiguration: TextMagnifierConfiguration( shouldDisplayHandlesInMagnifier: false, magnifierBuilder: (BuildContext context, MagnifierController controller, ValueNotifier? notifier) { + builtGlobalGesturePosition = notifier?.value.globalGesturePosition; + builtFieldBounds = notifier?.value.fieldBounds; return SizedBox.shrink( key: magnifierKey, ); @@ -1488,10 +1492,12 @@ void main() { expect(find.byKey(magnifierKey), findsNothing); + const Offset globalGesturePosition = Offset(10.0, 10.0); + final Rect fieldBounds = Offset.zero & const Size(200.0, 50.0); final MagnifierInfo info = MagnifierInfo( - globalGesturePosition: Offset.zero, + globalGesturePosition: globalGesturePosition, caretRect: Offset.zero & const Size(5.0, 20.0), - fieldBounds: Offset.zero & const Size(200.0, 50.0), + fieldBounds: fieldBounds, currentLineBoundaries: Offset.zero & const Size(200.0, 50.0), ); selectionOverlay.showMagnifier(info); @@ -1499,6 +1505,8 @@ void main() { expect(tester.takeException(), isNull); expect(find.byKey(magnifierKey), findsOneWidget); + expect(builtFieldBounds, fieldBounds); + expect(builtGlobalGesturePosition, globalGesturePosition); selectionOverlay.dispose(); await tester.pumpAndSettle(); @@ -1724,7 +1732,7 @@ void main() { final LayerLink endHandleLayerLink = LayerLink(); final LayerLink toolbarLayerLink = LayerLink(); - final UniqueKey editableText = UniqueKey(); + final UniqueKey editableTextKey = UniqueKey(); final TextEditingController controller = TextEditingController(); addTearDown(controller.dispose); final FocusNode focusNode = FocusNode(); @@ -1735,7 +1743,7 @@ void main() { key: column, children: [ FakeEditableText( - key: editableText, + key: editableTextKey, controller: controller, focusNode: focusNode, ), @@ -1757,7 +1765,7 @@ void main() { return TextSelectionOverlay( value: TextEditingValue.empty, - renderObject: tester.state(find.byKey(editableText)).renderEditable, + renderObject: tester.state(find.byKey(editableTextKey)).renderEditable, context: tester.element(find.byKey(column)), onSelectionHandleTapped: () {}, startHandleLayerLink: startHandleLayerLink,