Skip to content

Commit c02451f

Browse files
committed
msglist: Show loading indicator at bottom as well as top
1 parent 7bc258b commit c02451f

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

lib/widgets/message_list.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -756,13 +756,21 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
756756
}
757757

758758
Widget _buildEndCap() {
759-
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
760-
TypingStatusWidget(narrow: widget.narrow),
761-
MarkAsReadWidget(narrow: widget.narrow),
762-
// To reinforce that the end of the feed has been reached:
763-
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
764-
const SizedBox(height: 36),
765-
]);
759+
if (model.haveNewest) {
760+
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
761+
TypingStatusWidget(narrow: widget.narrow),
762+
// TODO perhaps offer mark-as-read even when not done fetching?
763+
MarkAsReadWidget(narrow: widget.narrow),
764+
// To reinforce that the end of the feed has been reached:
765+
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
766+
const SizedBox(height: 36),
767+
]);
768+
} else if (model.busyFetchingMore) {
769+
// See [_buildStartCap] for why this condition shows a loading indicator.
770+
return const _MessageListLoadingMore();
771+
} else {
772+
return SizedBox.shrink();
773+
}
766774
}
767775

768776
Widget _buildItem(MessageListItem data) {

test/widgets/message_list_test.dart

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ void main() {
5959
bool foundOldest = true,
6060
int? messageCount,
6161
List<Message>? messages,
62+
GetMessagesResult? fetchResult,
6263
List<ZulipStream>? streams,
6364
List<User>? users,
6465
List<Subscription>? subscriptions,
@@ -83,12 +84,17 @@ void main() {
8384
// prepare message list data
8485
await store.addUser(eg.selfUser);
8586
await store.addUsers(users ?? []);
86-
assert((messageCount == null) != (messages == null));
87-
messages ??= List.generate(messageCount!, (index) {
88-
return eg.streamMessage(sender: eg.selfUser);
89-
});
90-
connection.prepare(json:
91-
eg.newestGetMessagesResult(foundOldest: foundOldest, messages: messages).toJson());
87+
if (fetchResult != null) {
88+
assert(foundOldest && messageCount == null && messages == null);
89+
} else {
90+
assert((messageCount == null) != (messages == null));
91+
messages ??= List.generate(messageCount!, (index) {
92+
return eg.streamMessage(sender: eg.selfUser);
93+
});
94+
fetchResult = eg.newestGetMessagesResult(
95+
foundOldest: foundOldest, messages: messages);
96+
}
97+
connection.prepare(json: fetchResult.toJson());
9298

9399
await tester.pumpWidget(TestZulipApp(accountId: selfAccount.id,
94100
skipAssertAccountExists: skipAssertAccountExists,
@@ -696,6 +702,63 @@ void main() {
696702
});
697703
});
698704

705+
// TODO test markers at start of list (`_buildStartCap`)
706+
707+
group('markers at end of list', () {
708+
final findLoadingIndicator = find.byType(CircularProgressIndicator);
709+
710+
testWidgets('spacer when have newest', (tester) async {
711+
final messages = List.generate(10,
712+
(i) => eg.streamMessage(content: '<p>message $i</p>'));
713+
await setupMessageListPage(tester, narrow: CombinedFeedNarrow(),
714+
fetchResult: eg.nearGetMessagesResult(anchor: messages.last.id,
715+
foundOldest: true, foundNewest: true, messages: messages));
716+
check(findMessageListScrollController(tester)!.position)
717+
.extentAfter.equals(0);
718+
719+
// There's no loading indicator.
720+
check(findLoadingIndicator).findsNothing();
721+
// The last message is spaced above the bottom of the viewport.
722+
check(tester.getRect(find.text('message 9')))
723+
.bottom..isGreaterThan(400)..isLessThan(570);
724+
});
725+
726+
testWidgets('loading indicator displaces spacer etc.', (tester) async {
727+
await setupMessageListPage(tester, narrow: CombinedFeedNarrow(),
728+
skipPumpAndSettle: true,
729+
// TODO(#1569) fix realism of this data: foundNewest false should mean
730+
// some messages found after anchor (and then we might need to scroll
731+
// to cause fetching newer messages).
732+
fetchResult: eg.nearGetMessagesResult(anchor: 1000,
733+
foundOldest: true, foundNewest: false,
734+
messages: List.generate(10,
735+
(i) => eg.streamMessage(id: 100 + i, content: '<p>message $i</p>'))));
736+
await tester.pump();
737+
738+
// The message list will immediately start fetching newer messages.
739+
connection.prepare(json: eg.newerGetMessagesResult(
740+
anchor: 109, foundNewest: true, messages: List.generate(100,
741+
(i) => eg.streamMessage(id: 110 + i))).toJson());
742+
await tester.pump(Duration(milliseconds: 10));
743+
await tester.pump();
744+
745+
// There's a loading indicator.
746+
check(findLoadingIndicator).findsOne();
747+
// It's at the bottom.
748+
check(findMessageListScrollController(tester)!.position)
749+
.extentAfter.equals(0);
750+
final loadingIndicatorRect = tester.getRect(findLoadingIndicator);
751+
check(loadingIndicatorRect).bottom.isGreaterThan(575);
752+
// The last message is shortly above it; no spacer or anything else.
753+
check(tester.getRect(find.text('message 9')))
754+
.bottom.isGreaterThan(loadingIndicatorRect.top - 36); // TODO(#1569) where's this space going?
755+
await tester.pumpAndSettle();
756+
});
757+
758+
// TODO(#1569) test no typing status or mark-read button when not haveNewest
759+
// (even without loading indicator)
760+
});
761+
699762
group('TypingStatusWidget', () {
700763
final users = [eg.selfUser, eg.otherUser, eg.thirdUser, eg.fourthUser];
701764
final finder = find.descendant(

0 commit comments

Comments
 (0)