Skip to content

Commit aade783

Browse files
chrisbobbegnprice
authored andcommitted
msglist: Use [User.avatarUrl] instead of [Message.avatarUrl]
Now, as demonstrated in the new test, if the author of a message changes their avatar, we'll see the update in the message list as soon as we get the event. While we're at it, comment out the property on [Message] to guide future consumers to [User]. Related: zulip#135
1 parent 55cf074 commit aade783

File tree

5 files changed

+67
-11
lines changed

5 files changed

+67
-11
lines changed

lib/api/model/model.dart

+1-4
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class Subscription {
248248
///
249249
/// https://zulip.com/api/get-messages#response
250250
sealed class Message {
251-
final String? avatarUrl;
251+
// final String? avatarUrl; // Use [User.avatarUrl] instead; will live-update
252252
final String client;
253253
String content;
254254
final String contentType;
@@ -276,7 +276,6 @@ sealed class Message {
276276
final String? matchSubject;
277277

278278
Message({
279-
this.avatarUrl,
280279
required this.client,
281280
required this.content,
282281
required this.contentType,
@@ -315,7 +314,6 @@ class StreamMessage extends Message {
315314
final int streamId;
316315

317316
StreamMessage({
318-
super.avatarUrl,
319317
required super.client,
320318
required super.content,
321319
required super.contentType,
@@ -417,7 +415,6 @@ class DmMessage extends Message {
417415
Iterable<int> get allRecipientIds => displayRecipient.map((e) => e.id);
418416

419417
DmMessage({
420-
super.avatarUrl,
421418
required super.client,
422419
required super.content,
423420
required super.contentType,

lib/api/model/model.g.dart

-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/widgets/message_list.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,11 @@ class MessageWithSender extends StatelessWidget {
475475
@override
476476
Widget build(BuildContext context) {
477477
final store = PerAccountStoreWidget.of(context);
478+
final author = store.users[message.senderId]!;
478479

479-
final avatarUrl = message.avatarUrl == null // TODO get from user data
480+
final avatarUrl = author.avatarUrl == null
480481
? null // TODO handle computing gravatars
481-
: resolveUrl(message.avatarUrl!, store.account);
482+
: resolveUrl(author.avatarUrl!, store.account);
482483
final avatar = (avatarUrl == null)
483484
? const SizedBox.shrink()
484485
: RealmContentNetworkImage(

test/widgets/content_checks.dart

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:checks/checks.dart';
2+
import 'package:zulip/widgets/content.dart';
3+
4+
extension RealmContentNetworkImageChecks on Subject<RealmContentNetworkImage> {
5+
Subject<String> get src => has((i) => i.src, 'src');
6+
// TODO others
7+
}

test/widgets/message_list_test.dart

+56-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
import 'dart:io';
2+
13
import 'package:checks/checks.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:zulip/api/model/events.dart';
47
import 'package:zulip/api/model/model.dart';
58
import 'package:zulip/api/route/messages.dart';
69
import 'package:zulip/model/narrow.dart';
10+
import 'package:zulip/widgets/content.dart';
711
import 'package:zulip/widgets/message_list.dart';
812
import 'package:zulip/widgets/sticky_header.dart';
913
import 'package:zulip/widgets/store.dart';
1014

1115
import '../api/fake_api.dart';
16+
import '../test_images.dart';
1217
import '../example_data.dart' as eg;
1318
import '../model/binding.dart';
19+
import '../model/test_store.dart';
20+
import 'content_checks.dart';
1421

1522
Future<void> setupMessageListPage(WidgetTester tester, {
1623
required Narrow narrow,
@@ -25,8 +32,9 @@ Future<void> setupMessageListPage(WidgetTester tester, {
2532
final connection = store.connection as FakeApiConnection;
2633

2734
// prepare message list data
35+
store.addUser(eg.selfUser);
2836
final List<StreamMessage> messages = List.generate(10, (index) {
29-
return eg.streamMessage(id: index);
37+
return eg.streamMessage(id: index, sender: eg.selfUser);
3038
});
3139
connection.prepare(json: GetMessagesResult(
3240
anchor: messages[0].id,
@@ -122,4 +130,51 @@ void main() {
122130
check(scrollController.position.pixels).equals(0);
123131
});
124132
});
133+
134+
group('MessageWithSender', () {
135+
testWidgets('Updates avatar on RealmUserUpdateEvent', (tester) async {
136+
addTearDown(TestZulipBinding.instance.reset);
137+
138+
// TODO recognize avatar more reliably:
139+
// https://github.com/zulip/zulip-flutter/pull/246#discussion_r1282516308
140+
RealmContentNetworkImage? findAvatarImageWidget(WidgetTester tester) {
141+
return tester.widgetList<RealmContentNetworkImage>(
142+
find.descendant(
143+
of: find.byType(MessageWithSender),
144+
matching: find.byType(RealmContentNetworkImage))).firstOrNull;
145+
}
146+
147+
void checkResultForSender(String? avatarUrl) {
148+
if (avatarUrl == null) {
149+
check(findAvatarImageWidget(tester)).isNull();
150+
} else {
151+
check(findAvatarImageWidget(tester)).isNotNull()
152+
.src.equals(resolveUrl(avatarUrl, eg.selfAccount));
153+
}
154+
}
155+
156+
Future<void> handleNewAvatarEventAndPump(WidgetTester tester, String avatarUrl) async {
157+
final store = await TestZulipBinding.instance.globalStore.perAccount(eg.selfAccount.id);
158+
store.handleEvent(RealmUserUpdateEvent(id: 1, userId: eg.selfUser.userId, avatarUrl: avatarUrl));
159+
await tester.pump();
160+
}
161+
162+
final httpClient = FakeImageHttpClient();
163+
debugNetworkImageHttpClientProvider = () => httpClient;
164+
httpClient.request.response
165+
..statusCode = HttpStatus.ok
166+
..content = kSolidBlueAvatar;
167+
168+
await setupMessageListPage(tester, narrow: const AllMessagesNarrow());
169+
checkResultForSender(eg.selfUser.avatarUrl);
170+
171+
await handleNewAvatarEventAndPump(tester, '/foo.png');
172+
checkResultForSender('/foo.png');
173+
174+
await handleNewAvatarEventAndPump(tester, '/bar.jpg');
175+
checkResultForSender('/bar.jpg');
176+
177+
debugNetworkImageHttpClientProvider = null;
178+
});
179+
});
125180
}

0 commit comments

Comments
 (0)