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

Handles hidden by keyboard (#32838)

Extra space when scrolling to selected input so that the selection caret is visible.
parent 04015b98
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -1120,10 +1121,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1120,10 +1121,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
double scrollOffset = _scrollController.offset; double scrollOffset = _scrollController.offset;
final double viewportExtent = _scrollController.position.viewportDimension; final double viewportExtent = _scrollController.position.viewportDimension;
if (caretStart < 0.0) // cursor before start of bounds if (caretStart < 0.0) { // cursor before start of bounds
scrollOffset += caretStart; scrollOffset += caretStart;
else if (caretEnd >= viewportExtent) // cursor after end of bounds } else if (caretEnd >= viewportExtent) { // cursor after end of bounds
scrollOffset += caretEnd - viewportExtent; scrollOffset += caretEnd - viewportExtent;
}
return scrollOffset; return scrollOffset;
} }
...@@ -1271,12 +1273,32 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1271,12 +1273,32 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
curve: _caretAnimationCurve, curve: _caretAnimationCurve,
); );
final Rect newCaretRect = _getCaretRectAtScrollOffset(_currentCaretRect, scrollOffsetForCaret); final Rect newCaretRect = _getCaretRectAtScrollOffset(_currentCaretRect, scrollOffsetForCaret);
// Enlarge newCaretRect by scrollPadding to ensure that caret is not positioned directly at the edge after scrolling. // Enlarge newCaretRect by scrollPadding to ensure that caret is not
// positioned directly at the edge after scrolling.
double bottomSpacing = widget.scrollPadding.bottom;
if (_selectionOverlay?.selectionControls != null) {
final double handleHeight = _selectionOverlay.selectionControls
.getHandleSize(renderEditable.preferredLineHeight).height;
final double interactiveHandleHeight = math.max(
handleHeight,
kMinInteractiveSize,
);
final Offset anchor = _selectionOverlay.selectionControls
.getHandleAnchor(
TextSelectionHandleType.collapsed,
renderEditable.preferredLineHeight,
);
final double handleCenter = handleHeight / 2 - anchor.dy;
bottomSpacing = math.max(
handleCenter + interactiveHandleHeight / 2,
bottomSpacing,
);
}
final Rect inflatedRect = Rect.fromLTRB( final Rect inflatedRect = Rect.fromLTRB(
newCaretRect.left - widget.scrollPadding.left, newCaretRect.left - widget.scrollPadding.left,
newCaretRect.top - widget.scrollPadding.top, newCaretRect.top - widget.scrollPadding.top,
newCaretRect.right + widget.scrollPadding.right, newCaretRect.right + widget.scrollPadding.right,
newCaretRect.bottom + widget.scrollPadding.bottom, newCaretRect.bottom + bottomSpacing,
); );
_editableKey.currentContext.findRenderObject().showOnScreen( _editableKey.currentContext.findRenderObject().showOnScreen(
rect: inflatedRect, rect: inflatedRect,
......
...@@ -2626,6 +2626,34 @@ void main() { ...@@ -2626,6 +2626,34 @@ void main() {
debugDefaultTargetPlatformOverride = null; debugDefaultTargetPlatformOverride = null;
}); });
testWidgets('when CupertinoTextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(CupertinoApp(
theme: const CupertinoThemeData(),
home: Center(
child: ListView(
controller: scrollController,
children: <Widget>[
Container(height: 585), // Push field almost off screen.
CupertinoTextField(controller: controller),
Container(height: 1000),
],
),
),
));
// Tap the TextField to put the cursor into it and bring it into view.
expect(scrollController.offset, 0.0);
await tester.tap(find.byType(CupertinoTextField));
await tester.pumpAndSettle();
// The ListView has scrolled to keep the TextField and cursor handle
// visible.
expect(scrollController.offset, 26.0);
});
testWidgets('disabled state golden', (WidgetTester tester) async { testWidgets('disabled state golden', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( CupertinoApp(
......
...@@ -6581,4 +6581,34 @@ void main() { ...@@ -6581,4 +6581,34 @@ void main() {
await tester.tapAt(handlePos, pointer: 7); await tester.tapAt(handlePos, pointer: 7);
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse); expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
}); });
testWidgets('when TextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: Center(
child: ListView(
controller: scrollController,
children: <Widget>[
Container(height: 579), // Push field almost off screen.
TextField(controller: controller),
Container(height: 1000),
],
),
),
),
));
// Tap the TextField to put the cursor into it and bring it into view.
expect(scrollController.offset, 0.0);
await tester.tap(find.byType(TextField));
await tester.pumpAndSettle();
// The ListView has scrolled to keep the TextField and cursor handle
// visible.
expect(scrollController.offset, 44.0);
});
} }
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