Skip to content

Commit 8813949

Browse files
committed
Merge remote-tracking branch 'pr/1702'
2 parents 4d9dfab + e9f682e commit 8813949

11 files changed

+374
-42
lines changed

lib/widgets/autocomplete.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,11 @@ class _MentionAutocompleteItem extends StatelessWidget {
314314
mainAxisSize: MainAxisSize.min,
315315
crossAxisAlignment: CrossAxisAlignment.start,
316316
children: [
317-
labelWidget,
317+
Row(children: [
318+
Flexible(child: labelWidget),
319+
if (option case UserMentionAutocompleteResult(:var userId))
320+
UserStatusEmoji(userId: userId, size: 18,
321+
padding: const EdgeInsetsDirectional.only(start: 5.0))]),
318322
if (sublabelWidget != null) sublabelWidget,
319323
])),
320324
]));

lib/widgets/message_list.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,8 @@ class SenderRow extends StatelessWidget {
19431943
: designVariables.title,
19441944
).merge(weightVariableTextStyle(context, wght: 600)),
19451945
overflow: TextOverflow.ellipsis)),
1946+
UserStatusEmoji(userId: message.senderId, size: 18,
1947+
padding: const EdgeInsetsDirectional.only(start: 5.0)),
19461948
if (sender?.isBot ?? false) ...[
19471949
const SizedBox(width: 5),
19481950
Icon(

lib/widgets/new_dm_sheet.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ class _SelectedUserChip extends StatelessWidget {
317317
fontSize: 16,
318318
height: 16 / 16,
319319
color: designVariables.labelMenuButton)))),
320+
UserStatusEmoji(userId: userId, size: 16,
321+
padding: EdgeInsetsDirectional.only(end: 4)),
320322
])));
321323
}
322324
}
@@ -415,7 +417,12 @@ class _NewDmUserListItem extends StatelessWidget {
415417
Avatar(userId: userId, size: 32, borderRadius: 3),
416418
SizedBox(width: 8),
417419
Expanded(
418-
child: Text(store.userDisplayName(userId),
420+
child: Text.rich(
421+
TextSpan(
422+
children: [
423+
TextSpan(text: store.userDisplayName(userId)),
424+
UserStatusEmoji.asWidgetSpan(userId: userId, fontSize: 17,
425+
textScaler: MediaQuery.textScalerOf(context))]),
419426
style: TextStyle(
420427
fontSize: 17,
421428
height: 19 / 17,

lib/widgets/profile.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'page.dart';
1616
import 'remote_settings.dart';
1717
import 'store.dart';
1818
import 'text.dart';
19+
import 'theme.dart';
1920

2021
class _TextStyles {
2122
static const primaryFieldText = TextStyle(fontSize: 20);
@@ -47,6 +48,7 @@ class ProfilePage extends StatelessWidget {
4748
if (user == null) {
4849
return const _ProfileErrorPage();
4950
}
51+
final userStatus = store.getUserStatus(userId);
5052

5153
final nameStyle = _TextStyles.primaryFieldText
5254
.merge(weightVariableTextStyle(context, wght: 700));
@@ -73,17 +75,28 @@ class ProfilePage extends StatelessWidget {
7375
),
7476
// TODO write a test where the user is muted; check this and avatar
7577
TextSpan(text: store.userDisplayName(userId, replaceIfMuted: false)),
78+
UserStatusEmoji.asWidgetSpan(
79+
userId: userId,
80+
fontSize: 20,
81+
textScaler: MediaQuery.textScalerOf(context),
82+
neverAnimate: false,
83+
),
7684
]),
7785
textAlign: TextAlign.center,
7886
style: nameStyle),
87+
if (userStatus.text != null)
88+
Text(userStatus.text!,
89+
textAlign: TextAlign.center,
90+
style: TextStyle(fontSize: 18, height: 22 / 18,
91+
color: DesignVariables.of(context).userStatusText)),
92+
7993
if (displayEmail != null)
8094
Text(displayEmail,
8195
textAlign: TextAlign.center,
8296
style: _TextStyles.primaryFieldText),
8397
Text(roleToLabel(user.role, zulipLocalizations),
8498
textAlign: TextAlign.center,
8599
style: _TextStyles.primaryFieldText),
86-
// TODO(#197) render user status
87100
// TODO(#196) render active status
88101
// TODO(#292) render user local time
89102

lib/widgets/recent_dm_conversations.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,31 @@ class RecentDmConversationsItem extends StatelessWidget {
148148
const SizedBox(width: 8),
149149
Expanded(child: Padding(
150150
padding: const EdgeInsets.symmetric(vertical: 4),
151-
child: Text(
151+
child: Text.rich(
152+
TextSpan(
153+
children: [
154+
TextSpan(text: title),
155+
...(switch (narrow.otherRecipientIds) {
156+
// self-DM
157+
[] => [UserStatusEmoji.asWidgetSpan(userId: store.selfUserId,
158+
fontSize: 17, textScaler: MediaQuery.textScalerOf(context))],
159+
// 1:1-DM
160+
[final otherUserId] =>
161+
[UserStatusEmoji.asWidgetSpan(userId: otherUserId,
162+
fontSize: 17, textScaler: MediaQuery.textScalerOf(context))],
163+
// group-DM - show nothing
164+
[...] => [],
165+
}),
166+
]
167+
),
152168
style: TextStyle(
153169
fontSize: 17,
154170
height: (20 / 17),
155171
// TODO(design) check if this is the right variable
156172
color: designVariables.labelMenuButton,
157173
),
158174
maxLines: 2,
159-
overflow: TextOverflow.ellipsis,
160-
title))),
175+
overflow: TextOverflow.ellipsis))),
161176
const SizedBox(width: 12),
162177
unreadCount > 0
163178
? Padding(padding: const EdgeInsetsDirectional.only(end: 16),

lib/widgets/theme.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
213213
subscriptionListHeaderLine: const HSLColor.fromAHSL(0.2, 240, 0.1, 0.5).toColor(),
214214
subscriptionListHeaderText: const HSLColor.fromAHSL(1.0, 240, 0.1, 0.5).toColor(),
215215
unreadCountBadgeTextForChannel: Colors.black.withValues(alpha: 0.9),
216+
userStatusText: const Color(0xff808080),
216217
);
217218

218219
static final dark = DesignVariables._(
@@ -309,6 +310,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
309310
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
310311
subscriptionListHeaderText: const HSLColor.fromAHSL(1.0, 240, 0.1, 0.75).toColor(),
311312
unreadCountBadgeTextForChannel: Colors.white.withValues(alpha: 0.9),
313+
// TODO(design-dark) unchanged in dark theme?
314+
userStatusText: const Color(0xff808080),
312315
);
313316

314317
DesignVariables._({
@@ -388,6 +391,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
388391
required this.subscriptionListHeaderLine,
389392
required this.subscriptionListHeaderText,
390393
required this.unreadCountBadgeTextForChannel,
394+
required this.userStatusText,
391395
});
392396

393397
/// The [DesignVariables] from the context's active theme.
@@ -480,6 +484,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
480484
final Color subscriptionListHeaderLine;
481485
final Color subscriptionListHeaderText;
482486
final Color unreadCountBadgeTextForChannel;
487+
final Color userStatusText; // In Figma, but unnamed.
483488

484489
@override
485490
DesignVariables copyWith({
@@ -559,6 +564,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
559564
Color? subscriptionListHeaderLine,
560565
Color? subscriptionListHeaderText,
561566
Color? unreadCountBadgeTextForChannel,
567+
Color? userStatusText,
562568
}) {
563569
return DesignVariables._(
564570
background: background ?? this.background,
@@ -637,6 +643,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
637643
subscriptionListHeaderLine: subscriptionListHeaderLine ?? this.subscriptionListHeaderLine,
638644
subscriptionListHeaderText: subscriptionListHeaderText ?? this.subscriptionListHeaderText,
639645
unreadCountBadgeTextForChannel: unreadCountBadgeTextForChannel ?? this.unreadCountBadgeTextForChannel,
646+
userStatusText: userStatusText ?? this.userStatusText,
640647
);
641648
}
642649

@@ -722,6 +729,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
722729
subscriptionListHeaderLine: Color.lerp(subscriptionListHeaderLine, other.subscriptionListHeaderLine, t)!,
723730
subscriptionListHeaderText: Color.lerp(subscriptionListHeaderText, other.subscriptionListHeaderText, t)!,
724731
unreadCountBadgeTextForChannel: Color.lerp(unreadCountBadgeTextForChannel, other.unreadCountBadgeTextForChannel, t)!,
732+
userStatusText: Color.lerp(userStatusText, other.userStatusText, t)!,
725733
);
726734
}
727735
}

test/widgets/autocomplete_test.dart

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:zulip/api/model/model.dart';
77
import 'package:zulip/api/route/messages.dart';
88
import 'package:zulip/api/route/channels.dart';
99
import 'package:zulip/api/route/realm.dart';
10+
import 'package:zulip/basic.dart';
1011
import 'package:zulip/model/compose.dart';
1112
import 'package:zulip/model/emoji.dart';
1213
import 'package:zulip/model/localizations.dart';
@@ -15,6 +16,7 @@ import 'package:zulip/model/store.dart';
1516
import 'package:zulip/model/typing_status.dart';
1617
import 'package:zulip/widgets/compose_box.dart';
1718
import 'package:zulip/widgets/content.dart';
19+
import 'package:zulip/widgets/emoji.dart';
1820
import 'package:zulip/widgets/message_list.dart';
1921

2022
import '../api/fake_api.dart';
@@ -23,6 +25,7 @@ import '../flutter_checks.dart';
2325
import '../model/binding.dart';
2426
import '../model/test_store.dart';
2527
import '../test_images.dart';
28+
import 'message_list_test.dart';
2629
import 'test_app.dart';
2730

2831
/// Simulates loading a [MessageListPage] and tapping to focus the compose input.
@@ -36,6 +39,7 @@ import 'test_app.dart';
3639
/// before the end of the test.
3740
Future<Finder> setupToComposeInput(WidgetTester tester, {
3841
List<User> users = const [],
42+
List<(int userId, UserStatusChange change)>? userStatuses,
3943
Narrow? narrow,
4044
}) async {
4145
assert(narrow is ChannelNarrow? || narrow is SendableNarrow?);
@@ -47,6 +51,7 @@ Future<Finder> setupToComposeInput(WidgetTester tester, {
4751
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
4852
await store.addUsers([eg.selfUser, eg.otherUser]);
4953
await store.addUsers(users);
54+
await store.changeUserStatuses(userStatuses ?? []);
5055
final connection = store.connection as FakeApiConnection;
5156

5257
narrow ??= DmNarrow(
@@ -152,9 +157,24 @@ void main() {
152157
Finder findAvatarImage(int userId) =>
153158
find.byWidgetPredicate((widget) => widget is AvatarImage && widget.userId == userId);
154159

155-
void checkUserShown(User user, {required bool expected}) {
156-
check(find.text(user.fullName)).findsExactly(expected ? 1 : 0);
157-
check(findAvatarImage(user.userId)).findsExactly(expected ? 1 : 0);
160+
void checkUserShown(User user, {required bool expected, bool withStatusEmoji = false}) {
161+
assert(expected || !withStatusEmoji);
162+
163+
final nameFinder = find.text(user.fullName);
164+
check(nameFinder).findsExactly(expected ? 1 : 0);
165+
166+
final avatarFinder = findAvatarImage(user.userId);
167+
check(avatarFinder).findsExactly(expected ? 1 : 0);
168+
169+
final statusEmojiFinder = findStatusEmoji(UnicodeEmojiWidget);
170+
if (withStatusEmoji) {
171+
checkUserStatusEmoji(statusEmojiFinder, isAnimated: false);
172+
}
173+
final rowFinder = find.ancestor(of: nameFinder,
174+
matching: find.ancestor(of: avatarFinder,
175+
matching: find.ancestor(of: statusEmojiFinder,
176+
matching: find.byType(Row))));
177+
check(rowFinder).findsExactly(expected && withStatusEmoji ? 1 : 0);
158178
}
159179

160180
testWidgets('user options appear, disappear, and change correctly', (tester) async {
@@ -202,6 +222,31 @@ void main() {
202222
debugNetworkImageHttpClientProvider = null;
203223
});
204224

225+
testWidgets('status emoji is set -> emoji is displayed', (tester) async {
226+
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png');
227+
final user2 = eg.user(userId: 2, fullName: 'User Two', avatarUrl: 'user2.png');
228+
final composeInputFinder = await setupToComposeInput(tester,
229+
users: [user1, user2], userStatuses: [
230+
(
231+
user1.userId,
232+
UserStatusChange(
233+
text: OptionSome('Busy'),
234+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
235+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji)))
236+
),
237+
]);
238+
239+
// // TODO(#226): Remove this extra edit when this bug is fixed.
240+
await tester.enterText(composeInputFinder, 'hello @u');
241+
await tester.enterText(composeInputFinder, 'hello @');
242+
await tester.pumpAndSettle(); // async computation; options appear
243+
244+
checkUserShown(user1, expected: true, withStatusEmoji: true);
245+
checkUserShown(user2, expected: true, withStatusEmoji: false);
246+
247+
debugNetworkImageHttpClientProvider = null;
248+
});
249+
205250
void checkWildcardShown(WildcardMentionOption wildcard, {required bool expected}) {
206251
check(find.text(wildcard.canonicalString)).findsExactly(expected ? 1 : 0);
207252
}

0 commit comments

Comments
 (0)