Skip to content

Commit 97c8a93

Browse files
committed
inbox: Fix collapsing a section from its sticky header
Before this fix, when scrolled down through the inbox page, collapsing a section (either the all-DMs section or a stream section) through its sticky header would work but cause an abnormal behavior, pushing the current section header and some of the following sections off the screen by the amount of space the current section occupied. After this fix, collapsing a section through a sticky header would work as expected, without pushing the header and the following sections off the screen. Fixes: #391
1 parent c4c1a61 commit 97c8a93

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

lib/widgets/inbox.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,19 @@ abstract class _HeaderItem extends StatelessWidget {
209209
final int count;
210210
final bool hasMention;
211211

212+
/// Context to access the [_StreamSection] or [_AllDmsSection].
213+
///
214+
/// Used to ensure the [_StreamSection] or [_AllDmsSection] that encloses the
215+
/// current [_HeaderItem] is visible after being collapsed through this
216+
/// [_HeaderItem].
217+
final BuildContext sectionContext;
218+
212219
const _HeaderItem({
213220
required this.collapsed,
214221
required this.pageState,
215222
required this.count,
216223
required this.hasMention,
224+
required this.sectionContext,
217225
});
218226

219227
String get title;
@@ -223,7 +231,14 @@ abstract class _HeaderItem extends StatelessWidget {
223231
Color get uncollapsedBackgroundColor;
224232
Color? get unreadCountBadgeBackgroundColor;
225233

226-
void Function() get onCollapseButtonTap;
234+
Future<void> Function() get onCollapseButtonTap => () async {
235+
if (!collapsed) {
236+
await Scrollable.ensureVisible(
237+
sectionContext,
238+
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
239+
);
240+
}
241+
};
227242
void Function() get onRowTap;
228243

229244
@override
@@ -271,6 +286,7 @@ class _AllDmsHeaderItem extends _HeaderItem {
271286
required super.pageState,
272287
required super.count,
273288
required super.hasMention,
289+
required super.sectionContext,
274290
});
275291

276292
@override get title => 'Direct messages'; // TODO(i18n)
@@ -280,7 +296,8 @@ class _AllDmsHeaderItem extends _HeaderItem {
280296
@override get uncollapsedBackgroundColor => const Color(0xFFF3F0E7);
281297
@override get unreadCountBadgeBackgroundColor => null;
282298

283-
@override get onCollapseButtonTap => () {
299+
@override get onCollapseButtonTap => () async {
300+
await super.onCollapseButtonTap();
284301
pageState.allDmsCollapsed = !collapsed;
285302
};
286303
@override get onRowTap => onCollapseButtonTap; // TODO open all-DMs narrow?
@@ -304,6 +321,7 @@ class _AllDmsSection extends StatelessWidget {
304321
hasMention: data.hasMention,
305322
collapsed: collapsed,
306323
pageState: pageState,
324+
sectionContext: context,
307325
);
308326
return StickyHeaderItem(
309327
header: header,
@@ -386,6 +404,7 @@ class _StreamHeaderItem extends _HeaderItem {
386404
required super.pageState,
387405
required super.count,
388406
required super.hasMention,
407+
required super.sectionContext,
389408
});
390409

391410
@override get title => subscription.name;
@@ -397,7 +416,8 @@ class _StreamHeaderItem extends _HeaderItem {
397416
@override get unreadCountBadgeBackgroundColor =>
398417
subscription.colorSwatch().unreadCountBadgeBackground;
399418

400-
@override get onCollapseButtonTap => () {
419+
@override get onCollapseButtonTap => () async {
420+
await super.onCollapseButtonTap();
401421
if (collapsed) {
402422
pageState.uncollapseStream(subscription.streamId);
403423
} else {
@@ -427,6 +447,7 @@ class _StreamSection extends StatelessWidget {
427447
hasMention: data.hasMention,
428448
collapsed: collapsed,
429449
pageState: pageState,
450+
sectionContext: context,
430451
);
431452
return StickyHeaderItem(
432453
header: header,

test/widgets/inbox_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ void main() {
5151
await tester.pumpAndSettle();
5252
}
5353

54+
List<StreamMessage> generateStreamMessages(ZulipStream stream, int count) {
55+
return List.generate(count, (index) => eg.streamMessage(
56+
stream: stream, topic: '${stream.name} topic $index', flags: []));
57+
}
58+
5459
/// Set up an inbox view with lots of interesting content.
5560
Future<void> setupVarious(WidgetTester tester) async {
5661
final stream1 = eg.stream(streamId: 1, name: 'stream 1');
@@ -64,7 +69,9 @@ void main() {
6469
users: [eg.selfUser, eg.otherUser, eg.thirdUser],
6570
unreadMessages: [
6671
eg.streamMessage(stream: stream1, topic: 'specific topic', flags: []),
72+
...generateStreamMessages(stream1, 10),
6773
eg.streamMessage(stream: stream2, flags: []),
74+
...generateStreamMessages(stream2, 40),
6875
eg.dmMessage(from: eg.otherUser, to: [eg.selfUser], flags: []),
6976
eg.dmMessage(from: eg.otherUser, to: [eg.selfUser, eg.thirdUser], flags: []),
7077
]);
@@ -310,6 +317,30 @@ void main() {
310317
checkAppearsUncollapsed(tester, findSectionContent);
311318
});
312319

320+
testWidgets('collapse all-DMs section after scroll', (tester) async {
321+
await setupVarious(tester);
322+
323+
final listFinder = find.byType(Scrollable);
324+
// Scroll the [StickyHeaderListView] enough so that
325+
// the [_AllDmsSection] shows a sticky header
326+
await tester.drag(listFinder, const Offset(0, -50));
327+
328+
final headerRow = findAllDmsHeaderRow(tester);
329+
// Check that the sticky header is present
330+
check(headerRow).isNotNull();
331+
332+
final findSectionContent = find.text(eg.otherUser.fullName);
333+
334+
checkAppearsUncollapsed(tester, findSectionContent);
335+
await tapCollapseIcon(tester);
336+
// Check that the header is still visible even after
337+
// collapsing the section
338+
check(headerRow).isNotNull();
339+
checkAppearsCollapsed(tester, findSectionContent);
340+
await tapCollapseIcon(tester);
341+
checkAppearsUncollapsed(tester, findSectionContent);
342+
});
343+
313344
// TODO check it remains collapsed even if you scroll far away and back
314345

315346
// TODO check that it's always uncollapsed when it appears after being
@@ -385,6 +416,30 @@ void main() {
385416
checkAppearsUncollapsed(tester, 1, findSectionContent);
386417
});
387418

419+
testWidgets('collapse stream section after scroll', (tester) async {
420+
await setupVarious(tester);
421+
422+
final listFinder = find.byType(Scrollable);
423+
// Scroll the [StickyHeaderListView] enough so that
424+
// the [_StreamSection] shows a sticky header
425+
await tester.drag(listFinder, const Offset(0, -200));
426+
427+
final headerRow = findStreamHeaderRow(tester, 1);
428+
// Check that the sticky header is present
429+
check(headerRow).isNotNull();
430+
431+
final findSectionContent = find.text('specific topic');
432+
433+
checkAppearsUncollapsed(tester, 1, findSectionContent);
434+
await tapCollapseIcon(tester, 1);
435+
// Check that the header is still visible even after
436+
// collapsing the section
437+
check(headerRow).isNotNull();
438+
checkAppearsCollapsed(tester, 1, findSectionContent);
439+
await tapCollapseIcon(tester, 1);
440+
checkAppearsUncollapsed(tester, 1, findSectionContent);
441+
});
442+
388443
// TODO check it remains collapsed even if you scroll far away and back
389444

390445
// TODO check that it's always uncollapsed when it appears after being

0 commit comments

Comments
 (0)