Skip to content

Commit 7055b9c

Browse files
committed
scroll: Handle starting from overscroll in "scroll to end"
1 parent c72b99a commit 7055b9c

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
@@ -187,6 +187,14 @@ class MessageListScrollPosition extends ScrollPositionWithSingleContext {
187187
return;
188188
}
189189

190+
if (pixels > maxScrollExtent) {
191+
// The position is already scrolled past the end. Let overscroll handle it.
192+
// (This situation shouldn't even arise; the UI only offers this option
193+
// when `pixels < maxScrollExtent`.)
194+
goBallistic(0.0);
195+
return;
196+
}
197+
190198
/// The top speed to move at, in logical pixels per second.
191199
///
192200
/// 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
import 'package:flutter/widgets.dart';
34
import 'package:flutter_test/flutter_test.dart';
45
import 'package:zulip/widgets/scrolling.dart';
@@ -255,6 +256,46 @@ void main() {
255256
// … without moving any farther.
256257
check(position.extentAfter).equals(0);
257258
});
259+
260+
testWidgets('starting from overscroll, just drift', (tester) async {
261+
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
262+
await prepare(tester, topHeight: 400, bottomHeight: 400);
263+
264+
// Drag into overscroll.
265+
await tester.drag(findBottom, Offset(0, -100));
266+
await tester.pump();
267+
final offset1 = position.pixels - position.maxScrollExtent;
268+
check(offset1).isGreaterThan(100 / 2);
269+
check(position.activity).isA<BallisticScrollActivity>();
270+
271+
// Start drifting back into range.
272+
await tester.pump(Duration(milliseconds: 10));
273+
final offset2 = position.pixels - position.maxScrollExtent;
274+
check(offset2)..isGreaterThan(0.0)..isLessThan(offset1);
275+
check(position.activity).isA<BallisticScrollActivity>()
276+
.velocity.isLessThan(0);
277+
278+
// Invoke `scrollToEnd`. The motion should stop…
279+
position.scrollToEnd();
280+
await tester.pump();
281+
check(position.pixels - position.maxScrollExtent).equals(offset2);
282+
check(position.activity).isA<BallisticScrollActivity>()
283+
.velocity.equals(0);
284+
285+
// … and resume drifting from there…
286+
await tester.pump(Duration(milliseconds: 10));
287+
final offset3 = position.pixels - position.maxScrollExtent;
288+
check(offset3)..isGreaterThan(0.0)..isLessThan(offset2);
289+
check(position.activity).isA<BallisticScrollActivity>()
290+
.velocity.isLessThan(0);
291+
292+
// … to eventually return to being in range.
293+
await tester.pump(Duration(seconds: 1));
294+
check(position.pixels - position.maxScrollExtent).equals(0);
295+
check(position.activity).isA<IdleScrollActivity>();
296+
297+
debugDefaultTargetPlatformOverride = null;
298+
});
258299
});
259300
});
260301
}

0 commit comments

Comments
 (0)