Skip to content

Commit 3856aef

Browse files
committed
scroll: Handle starting from overscroll in "scroll to end"
1 parent 40abc88 commit 3856aef

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

lib/widgets/scrolling.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ class MessageListScrollPosition extends ScrollPositionWithSingleContext {
393393
return;
394394
}
395395

396+
if (pixels > maxScrollExtent) {
397+
// The position is already scrolled past the end. Let overscroll handle it.
398+
// (This situation shouldn't even arise; the UI only offers this option
399+
// when `pixels < maxScrollExtent`.)
400+
goBallistic(0.0);
401+
return;
402+
}
403+
396404
/// The top speed to move at, in logical pixels per second.
397405
///
398406
/// This will be the speed whenever the distance to be traveled

test/flutter_checks.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ extension TextEditingControllerChecks on Subject<TextEditingController> {
142142
Subject<String?> get text => has((t) => t.text, 'text');
143143
}
144144

145+
extension ScrollActivityChecks on Subject<ScrollActivity> {
146+
Subject<double> get velocity => has((x) => x.velocity, 'velocity');
147+
}
148+
145149
extension IconChecks on Subject<Icon> {
146150
Subject<IconData?> get icon => has((i) => i.icon, 'icon');
147151
Subject<Color?> get color => has((i) => i.color, 'color');

test/widgets/scrolling_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:checks/checks.dart';
2+
import 'package:flutter/foundation.dart';
23
// ignore: undefined_hidden_name // anticipates https://github.com/flutter/flutter/pull/164818
34
import 'package:flutter/rendering.dart' hide SliverPaintOrder;
45
// ignore: undefined_hidden_name // anticipates https://github.com/flutter/flutter/pull/164818
@@ -376,6 +377,46 @@ void main() {
376377
// … without moving any farther.
377378
check(position.extentAfter).equals(0);
378379
});
380+
381+
testWidgets('starting from overscroll, just drift', (tester) async {
382+
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
383+
await prepare(tester, topHeight: 400, bottomHeight: 400);
384+
385+
// Drag into overscroll.
386+
await tester.drag(findBottom, Offset(0, -100));
387+
await tester.pump();
388+
final offset1 = position.pixels - position.maxScrollExtent;
389+
check(offset1).isGreaterThan(100 / 2);
390+
check(position.activity).isA<BallisticScrollActivity>();
391+
392+
// Start drifting back into range.
393+
await tester.pump(Duration(milliseconds: 10));
394+
final offset2 = position.pixels - position.maxScrollExtent;
395+
check(offset2)..isGreaterThan(0.0)..isLessThan(offset1);
396+
check(position.activity).isA<BallisticScrollActivity>()
397+
.velocity.isLessThan(0);
398+
399+
// Invoke `scrollToEnd`. The motion should stop…
400+
position.scrollToEnd();
401+
await tester.pump();
402+
check(position.pixels - position.maxScrollExtent).equals(offset2);
403+
check(position.activity).isA<BallisticScrollActivity>()
404+
.velocity.equals(0);
405+
406+
// … and resume drifting from there…
407+
await tester.pump(Duration(milliseconds: 10));
408+
final offset3 = position.pixels - position.maxScrollExtent;
409+
check(offset3)..isGreaterThan(0.0)..isLessThan(offset2);
410+
check(position.activity).isA<BallisticScrollActivity>()
411+
.velocity.isLessThan(0);
412+
413+
// … to eventually return to being in range.
414+
await tester.pump(Duration(seconds: 1));
415+
check(position.pixels - position.maxScrollExtent).equals(0);
416+
check(position.activity).isA<IdleScrollActivity>();
417+
418+
debugDefaultTargetPlatformOverride = null;
419+
});
379420
});
380421
});
381422
}

0 commit comments

Comments
 (0)