Skip to content

Commit 0207e2c

Browse files
committed
msglist [nfc]: Introduce MessageListRecipientHeaderItem
Instead of showing recipient headers as part of each MessageListMessageItem, they're now their own message-list items. At this stage, we still show one for each message.
1 parent 7ad6177 commit 0207e2c

File tree

4 files changed

+43
-16
lines changed

4 files changed

+43
-16
lines changed

lib/model/message_list.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@ sealed class MessageListItem {
1919
const MessageListItem();
2020
}
2121

22+
class MessageListRecipientHeaderItem extends MessageListItem {
23+
final Message message;
24+
25+
MessageListRecipientHeaderItem(this.message);
26+
}
27+
2228
/// A message to show in the message list.
2329
class MessageListMessageItem extends MessageListItem {
2430
final Message message;
2531
final ZulipContent content;
32+
final bool isLastInBlock;
2633

27-
MessageListMessageItem(this.message, this.content);
34+
MessageListMessageItem(this.message, this.content, {required this.isLastInBlock});
2835
}
2936

3037
/// Indicates the app is loading more messages at the top.
@@ -104,6 +111,8 @@ mixin _MessageSequence {
104111
switch (item.direction) {
105112
case MessageListDirection.older: return -1;
106113
}
114+
case MessageListRecipientHeaderItem(:var message):
115+
return (message.id <= messageId) ? -1 : 1;
107116
case MessageListMessageItem(:var message): return message.id.compareTo(messageId);
108117
}
109118
}
@@ -118,7 +127,7 @@ mixin _MessageSequence {
118127
assert(itemIndex > -1
119128
&& items[itemIndex] is MessageListMessageItem
120129
&& identical((items[itemIndex] as MessageListMessageItem).message, message));
121-
items[itemIndex] = MessageListMessageItem(message, content);
130+
items[itemIndex] = MessageListMessageItem(isLastInBlock: true, message, content);
122131
}
123132

124133
/// Append [message] to [messages], and update derived data accordingly.
@@ -163,7 +172,11 @@ mixin _MessageSequence {
163172
// This will get more complicated to handle the ways that messages interact
164173
// with the display of neighboring messages: sender headings #175,
165174
// recipient headings #174, and date separators #173.
166-
items.add(MessageListMessageItem(messages[index], contents[index]));
175+
items.add(MessageListRecipientHeaderItem(messages[index]));
176+
items.add(MessageListMessageItem(
177+
isLastInBlock: true,
178+
messages[index], contents[index],
179+
));
167180
}
168181

169182
/// Update [items] to include markers at start and end as appropriate.

lib/widgets/message_list.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
281281
child: Padding(
282282
padding: EdgeInsets.symmetric(vertical: 16.0),
283283
child: CircularProgressIndicator())); // TODO perhaps a different indicator
284+
case MessageListRecipientHeaderItem():
285+
final header = RecipientHeader(message: data.message);
286+
return StickyHeaderItem(allowOverflow: true,
287+
header: header, child: header);
284288
case MessageListMessageItem():
285289
return MessageItem(
286290
trailing: i == 0 ? const SizedBox(height: 8) : const SizedBox(height: 11),
@@ -376,17 +380,19 @@ class MessageItem extends StatelessWidget {
376380
// right than at bottom and in the recipient header: black 10% alpha,
377381
// vs. 88% lightness. Assume that's an accident.
378382
shape: Border(
379-
left: recipientBorder, bottom: restBorder, right: restBorder));
383+
left: recipientBorder,
384+
right: restBorder,
385+
bottom: item.isLastInBlock ? restBorder : BorderSide.none,
386+
));
380387

381-
final recipientHeader = RecipientHeader(message: message);
382388
return StickyHeaderItem(
383-
header: recipientHeader,
389+
allowOverflow: !item.isLastInBlock,
390+
header: RecipientHeader(message: message),
384391
child: Column(children: [
385-
recipientHeader,
386392
DecoratedBox(
387393
decoration: borderDecoration,
388394
child: MessageWithSender(message: message, content: item.content)),
389-
if (trailing != null) trailing!,
395+
if (trailing != null && item.isLastInBlock) trailing!,
390396
]));
391397

392398
// Web handles the left-side recipient marker in a funky way:

test/model/message_list_test.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ void checkInvariants(MessageListView model) {
486486

487487
check(model).items.length.equals(
488488
((model.haveOldest || model.fetchingOlder) ? 1 : 0)
489-
+ model.messages.length);
489+
+ 2 * model.messages.length);
490490
int i = 0;
491491
if (model.haveOldest) {
492492
check(model.items[i++]).isA<MessageListHistoryStartItem>();
@@ -495,15 +495,23 @@ void checkInvariants(MessageListView model) {
495495
check(model.items[i++]).isA<MessageListLoadingItem>();
496496
}
497497
for (int j = 0; j < model.messages.length; j++) {
498+
check(model.items[i++]).isA<MessageListRecipientHeaderItem>()
499+
.message.identicalTo(model.messages[j]);
498500
check(model.items[i++]).isA<MessageListMessageItem>()
499501
..message.identicalTo(model.messages[j])
500-
..content.identicalTo(model.contents[j]);
502+
..content.identicalTo(model.contents[j])
503+
..isLastInBlock.isTrue();
501504
}
502505
}
503506

507+
extension MessageListRecipientHeaderItemChecks on Subject<MessageListRecipientHeaderItem> {
508+
Subject<Message> get message => has((x) => x.message, 'message');
509+
}
510+
504511
extension MessageListMessageItemChecks on Subject<MessageListMessageItem> {
505512
Subject<Message> get message => has((x) => x.message, 'message');
506513
Subject<ZulipContent> get content => has((x) => x.content, 'content');
514+
Subject<bool> get isLastInBlock => has((x) => x.isLastInBlock, 'isLastInBlock');
507515
}
508516

509517
extension MessageListViewChecks on Subject<MessageListView> {

test/widgets/message_list_test.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void main() {
7070
testWidgets('basic', (tester) async {
7171
await setupMessageListPage(tester, foundOldest: false,
7272
messages: List.generate(100, (i) => eg.streamMessage(id: 950 + i, sender: eg.selfUser)));
73-
check(itemCount(tester)).equals(100);
73+
check(itemCount(tester)).equals(200);
7474

7575
// Fling-scroll upward...
7676
await tester.fling(find.byType(MessageListPage), const Offset(0, 300), 8000);
@@ -83,13 +83,13 @@ void main() {
8383
await tester.pump(Duration.zero); // Allow a frame for the response to arrive.
8484

8585
// Now we have more messages.
86-
check(itemCount(tester)).equals(200);
86+
check(itemCount(tester)).equals(400);
8787
});
8888

8989
testWidgets('observe double-fetch glitch', (tester) async {
9090
await setupMessageListPage(tester, foundOldest: false,
9191
messages: List.generate(100, (i) => eg.streamMessage(id: 950 + i, sender: eg.selfUser)));
92-
check(itemCount(tester)).equals(100);
92+
check(itemCount(tester)).equals(200);
9393

9494
// Fling-scroll upward...
9595
await tester.fling(find.byType(MessageListPage), const Offset(0, 300), 8000);
@@ -101,18 +101,18 @@ void main() {
101101
for (int i = 0; i < 30; i++) {
102102
// Find the point in the fling where the fetch starts.
103103
await tester.pump(const Duration(milliseconds: 100));
104-
if (itemCount(tester)! > 100) break; // The loading indicator appeared.
104+
if (itemCount(tester)! > 200) break; // The loading indicator appeared.
105105
}
106106
await tester.pump(Duration.zero); // Allow a frame for the response to arrive.
107-
check(itemCount(tester)).equals(200);
107+
check(itemCount(tester)).equals(400);
108108

109109
// On the next frame, we promptly fetch *another* batch.
110110
// This is a glitch and it'd be nicer if we didn't.
111111
connection.prepare(json: olderResult(anchor: 850, foundOldest: false,
112112
messages: List.generate(100, (i) => eg.streamMessage(id: 750 + i, sender: eg.selfUser))).toJson());
113113
await tester.pump(const Duration(milliseconds: 1));
114114
await tester.pump(Duration.zero);
115-
check(itemCount(tester)).equals(300);
115+
check(itemCount(tester)).equals(600);
116116
}, skip: true); // TODO this still reproduces manually, still needs debugging,
117117
// but has become harder to reproduce in a test.
118118
});

0 commit comments

Comments
 (0)