Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a51735d

Browse files
committed
Shift+page up/down works with single line fields and tests pass
1 parent a47a9f3 commit a51735d

File tree

3 files changed

+202
-24
lines changed

3 files changed

+202
-24
lines changed

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,7 +3309,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
33093309

33103310
final ScrollableState? state = _scrollableKey.currentState as ScrollableState?;
33113311
final double increment = ScrollAction.getIncrement(state!, intent);
3312-
final double destination = (position.pixels + increment).clamp(
3312+
final double destination = clampDouble(
3313+
position.pixels + increment,
33133314
position.minScrollExtent,
33143315
position.maxScrollExtent,
33153316
);
@@ -3322,19 +3323,28 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
33223323
/// Extend the selection down by page if the `forward` parameter is true, or
33233324
/// up by page otherwise.
33243325
void _extendSelectionByPage(ExtendSelectionByPageIntent intent) {
3325-
final ScrollPosition position = _scrollController.position;
3326+
if (widget.maxLines == 1) {
3327+
return;
3328+
}
3329+
33263330
final TextSelection nextSelection;
33273331
final Rect extentRect = renderEditable.getLocalRectForCaret(
33283332
_value.selection.extent,
33293333
);
3334+
final ScrollableState? state = _scrollableKey.currentState as ScrollableState?;
3335+
final double increment = ScrollAction.calculateScrollIncrement(
3336+
state!,
3337+
type: ScrollIncrementType.page,
3338+
);
3339+
final ScrollPosition position = _scrollController.position;
33303340
if (intent.forward) {
33313341
if (_value.selection.extentOffset >= _value.text.length) {
33323342
return;
33333343
}
33343344
final Offset nextExtentOffset =
3335-
Offset(extentRect.left, extentRect.top + position.viewportDimension);
3336-
final double height = _scrollController.position.maxScrollExtent + renderEditable.size.height;
3337-
final TextPosition nextExtent = nextExtentOffset.dy + _scrollController.position.pixels >= height
3345+
Offset(extentRect.left, extentRect.top + increment);
3346+
final double height = position.maxScrollExtent + renderEditable.size.height;
3347+
final TextPosition nextExtent = nextExtentOffset.dy + position.pixels >= height
33383348
? TextPosition(offset: _value.text.length)
33393349
: renderEditable.getPositionForPoint(
33403350
renderEditable.localToGlobal(nextExtentOffset),
@@ -3347,8 +3357,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
33473357
return;
33483358
}
33493359
final Offset nextExtentOffset =
3350-
Offset(extentRect.left, extentRect.top - position.viewportDimension);
3351-
final TextPosition nextExtent = nextExtentOffset.dy + _scrollController.position.pixels <= 0
3360+
Offset(extentRect.left, extentRect.top - increment);
3361+
final TextPosition nextExtent = nextExtentOffset.dy + position.pixels <= 0
33523362
? const TextPosition(offset: 0)
33533363
: renderEditable.getPositionForPoint(
33543364
renderEditable.localToGlobal(nextExtentOffset),

packages/flutter/lib/src/widgets/scrollable.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,14 +1597,14 @@ class ScrollAction extends Action<ScrollIntent> {
15971597
return false;
15981598
}
15991599

1600-
// Returns the scroll increment for a single scroll request, for use when
1601-
// scrolling using a hardware keyboard.
1602-
//
1603-
// Must not be called when the position is null, or when any of the position
1604-
// metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
1605-
// null. The type and state arguments must not be null, and the widget must
1606-
// have already been laid out so that the position fields are valid.
1607-
static double _calculateScrollIncrement(ScrollableState state, { ScrollIncrementType type = ScrollIncrementType.line }) {
1600+
/// Returns the scroll increment for a single scroll request, for use when
1601+
/// scrolling using a hardware keyboard.
1602+
///
1603+
/// Must not be called when the position is null, or when any of the position
1604+
/// metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
1605+
/// null. The type and state arguments must not be null, and the widget must
1606+
/// have already been laid out so that the position fields are valid.
1607+
static double calculateScrollIncrement(ScrollableState state, { ScrollIncrementType type = ScrollIncrementType.line }) {
16081608
assert(type != null);
16091609
assert(state.position != null);
16101610
assert(state.position.hasPixels);
@@ -1631,7 +1631,7 @@ class ScrollAction extends Action<ScrollIntent> {
16311631
/// Find out how much of an increment to move by, taking the different
16321632
/// directions into account.
16331633
static double getIncrement(ScrollableState state, ScrollIntent intent) {
1634-
final double increment = _calculateScrollIncrement(state, type: intent.type);
1634+
final double increment = calculateScrollIncrement(state, type: intent.type);
16351635
switch (intent.direction) {
16361636
case AxisDirection.down:
16371637
switch (state.axisDirection) {

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 176 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7846,17 +7846,17 @@ void main() {
78467846
);
78477847
expect(scrollController.position.pixels, 0.0);
78487848

7849-
// Page Down scrolls by the height of the viewport.
7849+
// Page Down scrolls proportionally to the height of the viewport.
78507850
await sendKeys(
78517851
tester,
78527852
<LogicalKeyboardKey>[
78537853
LogicalKeyboardKey.pageDown,
78547854
],
78557855
targetPlatform: defaultTargetPlatform,
78567856
);
7857-
expect(scrollController.position.pixels, lineHeight * lines);
7857+
expect(scrollController.position.pixels, lineHeight * lines * 0.8);
78587858

7859-
// Another Page Down does nothing, since it's now at the bottom.
7859+
// Another Page Down reaches the bottom.
78607860
await sendKeys(
78617861
tester,
78627862
<LogicalKeyboardKey>[
@@ -7866,7 +7866,17 @@ void main() {
78667866
);
78677867
expect(scrollController.position.pixels, lineHeight * lines);
78687868

7869-
// Page Up now scrolls back up.
7869+
// Page Up now scrolls back up proportionally to the height of the viewport.
7870+
await sendKeys(
7871+
tester,
7872+
<LogicalKeyboardKey>[
7873+
LogicalKeyboardKey.pageUp,
7874+
],
7875+
targetPlatform: defaultTargetPlatform,
7876+
);
7877+
expect(scrollController.position.pixels, lineHeight * lines - lineHeight * lines * 0.8);
7878+
7879+
// Another Page Up reaches the top.
78707880
await sendKeys(
78717881
tester,
78727882
<LogicalKeyboardKey>[
@@ -7880,6 +7890,84 @@ void main() {
78807890
variant: TargetPlatformVariant.all(),
78817891
);
78827892

7893+
testWidgets('pageup/pagedown keys in a one line field', (WidgetTester tester) async {
7894+
final TextEditingController controller = TextEditingController(text: testText);
7895+
controller.selection = const TextSelection(
7896+
baseOffset: 0,
7897+
extentOffset: 0,
7898+
affinity: TextAffinity.upstream,
7899+
);
7900+
final ScrollController scrollController = ScrollController();
7901+
await tester.pumpWidget(MaterialApp(
7902+
home: Align(
7903+
alignment: Alignment.topLeft,
7904+
child: SizedBox(
7905+
width: 400,
7906+
child: EditableText(
7907+
minLines: 1,
7908+
controller: controller,
7909+
scrollController: scrollController,
7910+
showSelectionHandles: true,
7911+
autofocus: true,
7912+
focusNode: FocusNode(),
7913+
style: Typography.material2018().black.subtitle1!,
7914+
cursorColor: Colors.blue,
7915+
backgroundCursorColor: Colors.grey,
7916+
selectionControls: materialTextSelectionControls,
7917+
keyboardType: TextInputType.text,
7918+
textAlign: TextAlign.right,
7919+
),
7920+
),
7921+
),
7922+
));
7923+
7924+
await tester.pump(); // Wait for autofocus to take effect.
7925+
7926+
expect(controller.value.selection.isCollapsed, isTrue);
7927+
expect(controller.value.selection.baseOffset, 0);
7928+
expect(scrollController.position.pixels, 0.0);
7929+
final double lineHeight = findRenderEditable(tester).preferredLineHeight;
7930+
7931+
// Page Up scrolls to the end.
7932+
await sendKeys(
7933+
tester,
7934+
<LogicalKeyboardKey>[
7935+
LogicalKeyboardKey.pageUp,
7936+
],
7937+
targetPlatform: defaultTargetPlatform,
7938+
);
7939+
expect(scrollController.position.pixels, scrollController.position.maxScrollExtent);
7940+
expect(controller.value.selection.isCollapsed, isTrue);
7941+
expect(controller.value.selection.baseOffset, 0);
7942+
7943+
// Return scroll to the start.
7944+
await sendKeys(
7945+
tester,
7946+
<LogicalKeyboardKey>[
7947+
LogicalKeyboardKey.home,
7948+
],
7949+
targetPlatform: defaultTargetPlatform,
7950+
);
7951+
expect(scrollController.position.pixels, 0.0);
7952+
expect(controller.value.selection.isCollapsed, isTrue);
7953+
expect(controller.value.selection.baseOffset, 0);
7954+
7955+
// Page Down also scrolls to the end.
7956+
await sendKeys(
7957+
tester,
7958+
<LogicalKeyboardKey>[
7959+
LogicalKeyboardKey.pageDown,
7960+
],
7961+
targetPlatform: defaultTargetPlatform,
7962+
);
7963+
expect(scrollController.position.pixels, scrollController.position.maxScrollExtent);
7964+
expect(controller.value.selection.isCollapsed, isTrue);
7965+
expect(controller.value.selection.baseOffset, 0);
7966+
},
7967+
skip: kIsWeb, // [intended] on web these keys are handled by the browser.
7968+
variant: TargetPlatformVariant.all(),
7969+
);
7970+
78837971
testWidgets('shift + pageup/pagedown keys', (WidgetTester tester) async {
78847972
final TextEditingController controller = TextEditingController(text: testText);
78857973
controller.selection = const TextSelection(
@@ -7943,12 +8031,26 @@ void main() {
79438031
shift: true,
79448032
targetPlatform: defaultTargetPlatform,
79458033
);
7946-
expect(scrollController.position.pixels, lineHeight * 1);
8034+
expect(scrollController.position.pixels, 0.0);
8035+
expect(controller.value.selection.isCollapsed, isFalse);
8036+
expect(controller.value.selection.baseOffset, 0);
8037+
expect(controller.value.selection.extentOffset, 20);
8038+
8039+
// Another Page Down selects further down and scrolls.
8040+
await sendKeys(
8041+
tester,
8042+
<LogicalKeyboardKey>[
8043+
LogicalKeyboardKey.pageDown,
8044+
],
8045+
shift: true,
8046+
targetPlatform: defaultTargetPlatform,
8047+
);
8048+
expect(scrollController.position.pixels, 14.0);
79478049
expect(controller.value.selection.isCollapsed, isFalse);
79488050
expect(controller.value.selection.baseOffset, 0);
7949-
expect(controller.value.selection.extentOffset, 36);
8051+
expect(controller.value.selection.extentOffset, 54);
79508052

7951-
// Another Page Down selects everything.
8053+
// Another Page Down selects everything and scrolls to the bottom.
79528054
await sendKeys(
79538055
tester,
79548056
<LogicalKeyboardKey>[
@@ -7962,7 +8064,6 @@ void main() {
79628064
expect(controller.value.selection.baseOffset, 0);
79638065
expect(controller.value.selection.extentOffset, controller.text.length);
79648066

7965-
79668067
// Another Page Down does nothing, since it's now at the bottom.
79678068
await sendKeys(
79688069
tester,
@@ -8008,6 +8109,73 @@ void main() {
80088109
variant: TargetPlatformVariant.all(),
80098110
);
80108111

8112+
testWidgets('shift + pageup/pagedown keys in a one line field', (WidgetTester tester) async {
8113+
final TextEditingController controller = TextEditingController(text: testText);
8114+
controller.selection = const TextSelection(
8115+
baseOffset: 0,
8116+
extentOffset: 0,
8117+
affinity: TextAffinity.upstream,
8118+
);
8119+
final ScrollController scrollController = ScrollController();
8120+
await tester.pumpWidget(MaterialApp(
8121+
home: Align(
8122+
alignment: Alignment.topLeft,
8123+
child: SizedBox(
8124+
width: 400,
8125+
child: EditableText(
8126+
minLines: 1,
8127+
controller: controller,
8128+
scrollController: scrollController,
8129+
showSelectionHandles: true,
8130+
autofocus: true,
8131+
focusNode: FocusNode(),
8132+
style: Typography.material2018().black.subtitle1!,
8133+
cursorColor: Colors.blue,
8134+
backgroundCursorColor: Colors.grey,
8135+
selectionControls: materialTextSelectionControls,
8136+
keyboardType: TextInputType.text,
8137+
textAlign: TextAlign.right,
8138+
),
8139+
),
8140+
),
8141+
));
8142+
8143+
await tester.pump(); // Wait for autofocus to take effect.
8144+
8145+
expect(controller.value.selection.isCollapsed, isTrue);
8146+
expect(controller.value.selection.baseOffset, 0);
8147+
expect(scrollController.position.pixels, 0.0);
8148+
8149+
// Shift + Page Up does nothing.
8150+
await sendKeys(
8151+
tester,
8152+
<LogicalKeyboardKey>[
8153+
LogicalKeyboardKey.pageUp,
8154+
],
8155+
shift: true,
8156+
targetPlatform: defaultTargetPlatform,
8157+
);
8158+
expect(scrollController.position.pixels, 0.0);
8159+
expect(controller.value.selection.isCollapsed, isTrue);
8160+
expect(controller.value.selection.baseOffset, 0);
8161+
8162+
// Shift + Page Down does nothing either.
8163+
await sendKeys(
8164+
tester,
8165+
<LogicalKeyboardKey>[
8166+
LogicalKeyboardKey.pageDown,
8167+
],
8168+
shift: true,
8169+
targetPlatform: defaultTargetPlatform,
8170+
);
8171+
expect(scrollController.position.pixels, 0.0);
8172+
expect(controller.value.selection.isCollapsed, isTrue);
8173+
expect(controller.value.selection.baseOffset, 0);
8174+
},
8175+
skip: kIsWeb, // [intended] on web these keys are handled by the browser.
8176+
variant: TargetPlatformVariant.all(),
8177+
);
8178+
80118179
// Regression test for https://github.com/flutter/flutter/issues/31287
80128180
testWidgets('text selection handle visibility', (WidgetTester tester) async {
80138181
// Text with two separate words to select.

0 commit comments

Comments
 (0)