Skip to content

Commit 3121e13

Browse files
committed
profile: Show user status
Fixes: #197
1 parent e960fd8 commit 3121e13

File tree

3 files changed

+57
-1
lines changed

3 files changed

+57
-1
lines changed

lib/widgets/profile.dart

Lines changed: 13 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);
@@ -51,6 +52,7 @@ class ProfilePage extends StatelessWidget {
5152
final nameStyle = _TextStyles.primaryFieldText
5253
.merge(weightVariableTextStyle(context, wght: 700));
5354

55+
final userStatus = store.getUserStatus(userId);
5456
final displayEmail = store.userDisplayEmail(userId);
5557
final items = [
5658
Center(
@@ -73,17 +75,27 @@ 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: nameStyle.fontSize!,
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)),
7992
if (displayEmail != null)
8093
Text(displayEmail,
8194
textAlign: TextAlign.center,
8295
style: _TextStyles.primaryFieldText),
8396
Text(roleToLabel(user.role, zulipLocalizations),
8497
textAlign: TextAlign.center,
8598
style: _TextStyles.primaryFieldText),
86-
// TODO(#197) render user status
8799
// TODO(#196) render active status
88100
// TODO(#292) render user local time
89101

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/profile_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart';
99
import 'package:zulip/api/model/events.dart';
1010
import 'package:zulip/api/model/initial_snapshot.dart';
1111
import 'package:zulip/api/model/model.dart';
12+
import 'package:zulip/basic.dart';
1213
import 'package:zulip/model/narrow.dart';
1314
import 'package:zulip/model/store.dart';
1415
import 'package:zulip/widgets/button.dart';
@@ -99,6 +100,7 @@ void main() {
99100

100101
check(because: 'find user avatar', find.byType(Avatar).evaluate()).length.equals(1);
101102
check(because: 'find user name', find.text('test user').evaluate()).isNotEmpty();
103+
// Tests for user status are in their own test group.
102104
check(because: 'find user delivery email', find.text('[email protected]').evaluate()).isNotEmpty();
103105
});
104106

@@ -378,6 +380,40 @@ void main() {
378380
});
379381
});
380382

383+
group('user status', () {
384+
testWidgets('non-self profile, status set: status info appears', (tester) async {
385+
await setupPage(tester, users: [eg.otherUser], pageUserId: eg.otherUser.userId);
386+
await store.changeUserStatus(eg.otherUser.userId, UserStatusChange(
387+
text: OptionSome('Busy'),
388+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
389+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
390+
await tester.pump();
391+
392+
final statusEmojiFinder = find.ancestor(of: find.text('\u{1f6e0}'),
393+
matching: find.byType(UserStatusEmoji));
394+
check(statusEmojiFinder).findsOne();
395+
check(tester.widget<UserStatusEmoji>(statusEmojiFinder)
396+
.neverAnimate).isFalse();
397+
check(find.text('Busy')).findsOne();
398+
});
399+
400+
testWidgets('self-profile, status set: status info appears', (tester) async {
401+
await setupPage(tester, users: [eg.selfUser], pageUserId: eg.selfUser.userId);
402+
await store.changeUserStatus(eg.selfUser.userId, UserStatusChange(
403+
text: OptionSome('Busy'),
404+
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it',
405+
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji))));
406+
await tester.pump();
407+
408+
final statusEmojiFinder = find.ancestor(of: find.text('\u{1f6e0}'),
409+
matching: find.byType(UserStatusEmoji));
410+
check(statusEmojiFinder).findsOne();
411+
check(tester.widget<UserStatusEmoji>(statusEmojiFinder)
412+
.neverAnimate).isFalse();
413+
check(find.text('Busy')).findsOne();
414+
});
415+
});
416+
381417
group('invisible mode', () {
382418
final findRow = find.widgetWithText(ZulipMenuItemButton, 'Invisible mode');
383419
final findToggle = find.descendant(of: findRow, matching: find.byType(Toggle));

0 commit comments

Comments
 (0)