Skip to content

Commit 1cd4d23

Browse files
committed
recent-dms: Show user status emoji in recent DMs page
Status emojis are only shown for self-1:1 and 1:1 conversation items. They're ignored for group conversations as that's what the Web does.
1 parent 0e57471 commit 1cd4d23

File tree

2 files changed

+105
-11
lines changed

2 files changed

+105
-11
lines changed

lib/widgets/recent_dm_conversations.dart

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,29 @@ class RecentDmConversationsItem extends StatelessWidget {
104104
final store = PerAccountStoreWidget.of(context);
105105
final designVariables = DesignVariables.of(context);
106106

107-
final String title;
107+
final InlineSpan title;
108108
final Widget avatar;
109109
int? userIdForPresence;
110110
switch (narrow.otherRecipientIds) { // TODO dedupe with DM items in [InboxPage]
111111
case []:
112-
title = store.selfUser.fullName;
112+
title = TextSpan(text: store.selfUser.fullName, children: [
113+
UserStatusEmoji.asWidgetSpan(userId: store.selfUserId,
114+
fontSize: 17, textScaler: MediaQuery.textScalerOf(context)),
115+
]);
113116
avatar = AvatarImage(userId: store.selfUserId, size: _avatarSize);
114117
case [var otherUserId]:
115-
title = store.userDisplayName(otherUserId);
118+
title = TextSpan(text: store.userDisplayName(otherUserId), children: [
119+
UserStatusEmoji.asWidgetSpan(userId: otherUserId,
120+
fontSize: 17, textScaler: MediaQuery.textScalerOf(context)),
121+
]);
116122
avatar = AvatarImage(userId: otherUserId, size: _avatarSize);
117123
userIdForPresence = otherUserId;
118124
default:
119-
// TODO(i18n): List formatting, like you can do in JavaScript:
120-
// new Intl.ListFormat('ja').format(['Chris', 'Greg', 'Alya'])
121-
// // 'ChrisGregAlya'
122-
title = narrow.otherRecipientIds.map(store.userDisplayName)
123-
.join(', ');
125+
title = TextSpan(
126+
// TODO(i18n): List formatting, like you can do in JavaScript:
127+
// new Intl.ListFormat('ja').format(['Chris', 'Greg', 'Alya'])
128+
// // 'Chris、Greg、Alya'
129+
text: narrow.otherRecipientIds.map(store.userDisplayName).join(', '));
124130
avatar = ColoredBox(color: designVariables.avatarPlaceholderBg,
125131
child: Center(
126132
child: Icon(color: designVariables.avatarPlaceholderIcon,
@@ -148,7 +154,7 @@ class RecentDmConversationsItem extends StatelessWidget {
148154
const SizedBox(width: 8),
149155
Expanded(child: Padding(
150156
padding: const EdgeInsets.symmetric(vertical: 4),
151-
child: Text(
157+
child: Text.rich(
152158
style: TextStyle(
153159
fontSize: 17,
154160
height: (20 / 17),

test/widgets/recent_dm_conversations_test.dart

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import 'package:flutter_checks/flutter_checks.dart';
55
import 'package:flutter_test/flutter_test.dart';
66
import 'package:zulip/api/model/events.dart';
77
import 'package:zulip/api/model/model.dart';
8+
import 'package:zulip/basic.dart';
89
import 'package:zulip/model/narrow.dart';
10+
import 'package:zulip/model/store.dart';
911
import 'package:zulip/widgets/content.dart';
1012
import 'package:zulip/widgets/home.dart';
1113
import 'package:zulip/widgets/icons.dart';
@@ -24,6 +26,8 @@ import 'message_list_checks.dart';
2426
import 'page_checks.dart';
2527
import 'test_app.dart';
2628

29+
late PerAccountStore store;
30+
2731
Future<void> setupPage(WidgetTester tester, {
2832
required List<DmMessage> dmMessages,
2933
required List<User> users,
@@ -34,7 +38,7 @@ Future<void> setupPage(WidgetTester tester, {
3438
addTearDown(testBinding.reset);
3539

3640
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
37-
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
41+
store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
3842

3943
await store.addUser(eg.selfUser);
4044
for (final user in users) {
@@ -176,7 +180,7 @@ void main() {
176180
// TODO(#232): syntax like `check(find(…), findsOneWidget)`
177181
final widget = tester.widget(find.descendant(
178182
of: find.byType(RecentDmConversationsItem),
179-
matching: find.text(expectedText),
183+
matching: find.textContaining(expectedText),
180184
));
181185
if (expectedLines != null) {
182186
final renderObject = tester.renderObject<RenderParagraph>(find.byWidget(widget));
@@ -186,6 +190,18 @@ void main() {
186190
}
187191
}
188192

193+
void checkFindsStatusEmoji(WidgetTester tester, Finder emojiFinder) {
194+
final statusEmojiFinder = find.ancestor(of: emojiFinder,
195+
matching: find.byType(UserStatusEmoji));
196+
check(statusEmojiFinder).findsOne();
197+
check(tester.firstWidget<UserStatusEmoji>(statusEmojiFinder)
198+
.neverAnimate).isTrue();
199+
200+
final itemFinder = find.ancestor(of: statusEmojiFinder,
201+
matching: find.byType(RecentDmConversationsItem));
202+
check(itemFinder).findsOne();
203+
}
204+
189205
Future<void> markMessageAsRead(WidgetTester tester, Message message) async {
190206
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
191207
await store.handleEvent(UpdateMessageFlagsAddEvent(
@@ -231,6 +247,33 @@ void main() {
231247
checkTitle(tester, name, 2);
232248
});
233249

250+
group('User status', () {
251+
testWidgets('status emoji & text are set -> emoji is displayed, text is not', (tester) async {
252+
final message = eg.dmMessage(from: eg.selfUser, to: []);
253+
await setupPage(tester, dmMessages: [message], users: []);
254+
await store.changeUserStatus(eg.selfUser.userId, UserStatusChange(
255+
text: OptionSome('Busy'),
256+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
257+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
258+
await tester.pump();
259+
260+
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}'));
261+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
262+
matching: find.text('Busy'))).findsNothing();
263+
});
264+
265+
testWidgets('status emoji is not set, text is set -> text is not displayed', (tester) async {
266+
final message = eg.dmMessage(from: eg.selfUser, to: []);
267+
await setupPage(tester, dmMessages: [message], users: []);
268+
await store.changeUserStatus(eg.selfUser.userId, UserStatusChange(
269+
text: OptionSome('Busy'), emoji: OptionNone()));
270+
await tester.pump();
271+
272+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
273+
matching: find.text('Busy'))).findsNothing();
274+
});
275+
});
276+
234277
testWidgets('unread counts', (tester) async {
235278
final message = eg.dmMessage(from: eg.selfUser, to: []);
236279
await setupPage(tester, users: [], dmMessages: [message]);
@@ -291,6 +334,35 @@ void main() {
291334
checkTitle(tester, user.fullName, 2);
292335
});
293336

337+
group('User status', () {
338+
testWidgets('status emoji & text are set -> emoji is displayed, text is not', (tester) async {
339+
final user = eg.user();
340+
final message = eg.dmMessage(from: eg.selfUser, to: [user]);
341+
await setupPage(tester, users: [user], dmMessages: [message]);
342+
await store.changeUserStatus(user.userId, UserStatusChange(
343+
text: OptionSome('Busy'),
344+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
345+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
346+
await tester.pump();
347+
348+
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}'));
349+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
350+
matching: find.text('Busy'))).findsNothing();
351+
});
352+
353+
testWidgets('status emoji is not set, text is set -> text is not displayed', (tester) async {
354+
final user = eg.user();
355+
final message = eg.dmMessage(from: eg.selfUser, to: [user]);
356+
await setupPage(tester, users: [user], dmMessages: [message]);
357+
await store.changeUserStatus(user.userId, UserStatusChange(
358+
text: OptionSome('Busy'), emoji: OptionNone()));
359+
await tester.pump();
360+
361+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
362+
matching: find.text('Busy'))).findsNothing();
363+
});
364+
});
365+
294366
testWidgets('unread counts', (tester) async {
295367
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
296368
await setupPage(tester, users: [], dmMessages: [message]);
@@ -379,6 +451,22 @@ void main() {
379451
checkTitle(tester, users.map((u) => u.fullName).join(', '), 2);
380452
});
381453

454+
testWidgets('status emoji & text are set -> none of them is displayed', (tester) async {
455+
final users = usersList(4);
456+
final message = eg.dmMessage(from: eg.selfUser, to: users);
457+
await setupPage(tester, users: users, dmMessages: [message]);
458+
await store.changeUserStatus(users.first.userId, UserStatusChange(
459+
text: OptionSome('Busy'),
460+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
461+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
462+
await tester.pump();
463+
464+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
465+
matching: find.text('\u{1f6e0}'))).findsNothing();
466+
check(find.descendant(of: find.byType(RecentDmConversationsItem),
467+
matching: find.text('Busy'))).findsNothing();
468+
});
469+
382470
testWidgets('unread counts', (tester) async {
383471
final message = eg.dmMessage(from: eg.thirdUser, to: [eg.selfUser, eg.otherUser]);
384472
await setupPage(tester, users: [], dmMessages: [message]);

0 commit comments

Comments
 (0)