Skip to content

Commit 4bd0b8f

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 91609c3 commit 4bd0b8f

File tree

2 files changed

+100
-12
lines changed

2 files changed

+100
-12
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: 85 additions & 3 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';
@@ -20,10 +22,13 @@ import '../model/binding.dart';
2022
import '../model/test_store.dart';
2123
import '../test_navigation.dart';
2224
import 'content_checks.dart';
25+
import 'finders.dart';
2326
import 'message_list_checks.dart';
2427
import 'page_checks.dart';
2528
import 'test_app.dart';
2629

30+
late PerAccountStore store;
31+
2732
Future<void> setupPage(WidgetTester tester, {
2833
required List<DmMessage> dmMessages,
2934
required List<User> users,
@@ -36,7 +41,7 @@ Future<void> setupPage(WidgetTester tester, {
3641
selfUser ??= eg.selfUser;
3742
final selfAccount = eg.account(user: selfUser);
3843
await testBinding.globalStore.add(selfAccount, eg.initialSnapshot());
39-
final store = await testBinding.globalStore.perAccount(selfAccount.id);
44+
store = await testBinding.globalStore.perAccount(selfAccount.id);
4045

4146
await store.addUser(selfUser);
4247
for (final user in users) {
@@ -173,8 +178,9 @@ void main() {
173178
// TODO(#232): syntax like `check(find(…), findsOneWidget)`
174179
final widget = tester.widget(find.descendant(
175180
of: find.byType(RecentDmConversationsItem),
176-
matching: find.text(expectedText),
177-
));
181+
// The title might contain a WidgetSpan (for status emoji); exclude
182+
// the resulting placeholder character from the text to be matched.
183+
matching: findText(expectedText, includePlaceholders: false)));
178184
if (expectedLines != null) {
179185
final renderObject = tester.renderObject<RenderParagraph>(find.byWidget(widget));
180186
check(renderObject.size.height).equals(
@@ -183,6 +189,16 @@ void main() {
183189
}
184190
}
185191

192+
void checkFindsStatusEmoji(WidgetTester tester, Finder emojiFinder) {
193+
final statusEmojiFinder = find.ancestor(of: emojiFinder,
194+
matching: find.byType(UserStatusEmoji));
195+
check(statusEmojiFinder).findsOne();
196+
check(tester.widget<UserStatusEmoji>(statusEmojiFinder)
197+
.neverAnimate).isTrue();
198+
check(find.ancestor(of: statusEmojiFinder,
199+
matching: find.byType(RecentDmConversationsItem))).findsOne();
200+
}
201+
186202
Future<void> markMessageAsRead(WidgetTester tester, Message message) async {
187203
final store = await testBinding.globalStore.perAccount(
188204
testBinding.globalStore.accounts.single.id);
@@ -229,6 +245,31 @@ void main() {
229245
checkTitle(tester, name, 2);
230246
});
231247

248+
group('User status', () {
249+
testWidgets('emoji & text are set -> emoji is displayed, text is not', (tester) async {
250+
final message = eg.dmMessage(from: eg.selfUser, to: []);
251+
await setupPage(tester, dmMessages: [message], users: []);
252+
await store.changeUserStatus(eg.selfUser.userId, UserStatusChange(
253+
text: OptionSome('Busy'),
254+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
255+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
256+
await tester.pump();
257+
258+
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}'));
259+
check(find.textContaining('Busy')).findsNothing();
260+
});
261+
262+
testWidgets('emoji is not set, text is set -> text is not displayed', (tester) async {
263+
final message = eg.dmMessage(from: eg.selfUser, to: []);
264+
await setupPage(tester, dmMessages: [message], users: []);
265+
await store.changeUserStatus(eg.selfUser.userId, UserStatusChange(
266+
text: OptionSome('Busy'), emoji: OptionNone()));
267+
await tester.pump();
268+
269+
check(find.textContaining('Busy')).findsNothing();
270+
});
271+
});
272+
232273
testWidgets('unread counts', (tester) async {
233274
final message = eg.dmMessage(from: eg.selfUser, to: []);
234275
await setupPage(tester, users: [], dmMessages: [message]);
@@ -289,6 +330,33 @@ void main() {
289330
checkTitle(tester, user.fullName, 2);
290331
});
291332

333+
group('User status', () {
334+
testWidgets('emoji & text are set -> emoji is displayed, text is not', (tester) async {
335+
final user = eg.user();
336+
final message = eg.dmMessage(from: eg.selfUser, to: [user]);
337+
await setupPage(tester, users: [user], dmMessages: [message]);
338+
await store.changeUserStatus(user.userId, UserStatusChange(
339+
text: OptionSome('Busy'),
340+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
341+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
342+
await tester.pump();
343+
344+
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}'));
345+
check(find.textContaining('Busy')).findsNothing();
346+
});
347+
348+
testWidgets('emoji is not set, text is set -> text is not displayed', (tester) async {
349+
final user = eg.user();
350+
final message = eg.dmMessage(from: eg.selfUser, to: [user]);
351+
await setupPage(tester, users: [user], dmMessages: [message]);
352+
await store.changeUserStatus(user.userId, UserStatusChange(
353+
text: OptionSome('Busy'), emoji: OptionNone()));
354+
await tester.pump();
355+
356+
check(find.textContaining('Busy')).findsNothing();
357+
});
358+
});
359+
292360
testWidgets('unread counts', (tester) async {
293361
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
294362
await setupPage(tester, users: [], dmMessages: [message]);
@@ -377,6 +445,20 @@ void main() {
377445
checkTitle(tester, users.map((u) => u.fullName).join(', '), 2);
378446
});
379447

448+
testWidgets('status emoji & text are set -> none of them is displayed', (tester) async {
449+
final users = usersList(4);
450+
final message = eg.dmMessage(from: eg.selfUser, to: users);
451+
await setupPage(tester, users: users, dmMessages: [message]);
452+
await store.changeUserStatus(users.first.userId, UserStatusChange(
453+
text: OptionSome('Busy'),
454+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
455+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
456+
await tester.pump();
457+
458+
check(find.text('\u{1f6e0}')).findsNothing();
459+
check(find.textContaining('Busy')).findsNothing();
460+
});
461+
380462
testWidgets('unread counts', (tester) async {
381463
final message = eg.dmMessage(from: eg.thirdUser, to: [eg.selfUser, eg.otherUser]);
382464
await setupPage(tester, users: [], dmMessages: [message]);

0 commit comments

Comments
 (0)