Skip to content

Commit 0e33d57

Browse files
chrisbobbegnprice
authored andcommitted
widgets: Implement stream-colored UnreadCountBadge
1 parent d990df5 commit 0e33d57

File tree

4 files changed

+122
-3
lines changed

4 files changed

+122
-3
lines changed

lib/widgets/recent_dm_conversations.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ class RecentDmConversationsItem extends StatelessWidget {
132132
const SizedBox(width: 12),
133133
unreadCount > 0
134134
? Padding(padding: const EdgeInsetsDirectional.only(end: 16),
135-
child: UnreadCountBadge(count: unreadCount))
135+
child: UnreadCountBadge(baseStreamColor: null,
136+
count: unreadCount))
136137
: const SizedBox(),
137138
])));
138139
}

lib/widgets/unread_count_badge.dart

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:ui';
22

3+
import 'package:flutter_color_models/flutter_color_models.dart';
34
import 'package:flutter/material.dart';
45

56
import 'text.dart';
@@ -12,18 +13,57 @@ class UnreadCountBadge extends StatelessWidget {
1213
const UnreadCountBadge({
1314
super.key,
1415
required this.count,
16+
required this.baseStreamColor,
1517
this.bold = false,
1618
});
1719

1820
final int count;
1921
final bool bold;
2022

23+
/// A base stream color, from a stream subscription in user data, or null.
24+
///
25+
/// If not null, the background will be colored with an appropriate
26+
/// transformation of this.
27+
///
28+
/// If null, the default neutral background will be used.
29+
final Color? baseStreamColor;
30+
31+
@visibleForTesting
32+
Color getBackgroundColor() {
33+
if (baseStreamColor == null) {
34+
return const Color.fromRGBO(102, 102, 153, 0.15);
35+
}
36+
37+
// Follows `.unread-count` in Vlad's replit:
38+
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
39+
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
40+
41+
// The design uses "LCH", not "LAB", but we haven't found a Dart libary
42+
// that can work with LCH:
43+
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
44+
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1677537>
45+
// We use LAB because some quick reading suggests that the "L" axis
46+
// is the same in both representations:
47+
// <https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch>
48+
// and because the design doesn't use the LCH representation except to
49+
// adjust an "L" value.
50+
//
51+
// TODO try LCH; see linked discussion
52+
// TODO fix bug where our results differ from the replit's (see unit tests)
53+
// TODO profiling for expensive computation
54+
final asLab = LabColor.fromColor(baseStreamColor!);
55+
return asLab
56+
.copyWith(lightness: asLab.lightness.clamp(30, 70))
57+
.toColor()
58+
.withOpacity(0.3);
59+
}
60+
2161
@override
2262
Widget build(BuildContext context) {
2363
return DecoratedBox(
2464
decoration: BoxDecoration(
2565
borderRadius: BorderRadius.circular(3),
26-
color: const Color.fromRGBO(102, 102, 153, 0.15),
66+
color: getBackgroundColor(),
2767
),
2868
child: Padding(
2969
padding: const EdgeInsetsDirectional.fromSTEB(4, 0, 4, 1),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'dart:ui';
2+
3+
import 'package:checks/checks.dart';
4+
import 'package:zulip/widgets/unread_count_badge.dart';
5+
6+
extension UnreadCountBadgeChecks on Subject<UnreadCountBadge> {
7+
Subject<int> get count => has((b) => b.count, 'count');
8+
Subject<bool> get bold => has((b) => b.bold, 'bold');
9+
Subject<Color> get backgroundColor => has((b) => b.getBackgroundColor(), 'background color');
10+
}
+69-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,83 @@
1+
import 'package:checks/checks.dart';
12
import 'package:flutter/widgets.dart';
23

34
import 'package:flutter_test/flutter_test.dart';
45
import 'package:zulip/widgets/unread_count_badge.dart';
56

7+
import 'unread_count_badge_checks.dart';
8+
69
void main() {
710
group('UnreadCountBadge', () {
811
testWidgets('smoke test; no crash', (tester) async {
912
await tester.pumpWidget(
1013
const Directionality(textDirection: TextDirection.ltr,
11-
child: UnreadCountBadge(count: 1)));
14+
child: UnreadCountBadge(count: 1, baseStreamColor: null)));
1215
tester.widget(find.text("1"));
1316
});
17+
18+
test('colors', () {
19+
void runCheck(Color? baseStreamColor, Color expectedBackgroundColor) {
20+
check(UnreadCountBadge(count: 1, baseStreamColor: baseStreamColor))
21+
.backgroundColor.equals(expectedBackgroundColor);
22+
}
23+
24+
runCheck(null, const Color(0x26666699));
25+
26+
// Check against everything in ZULIP_ASSIGNMENT_COLORS and EXTREME_COLORS
27+
// in <https://replit.com/@VladKorobov/zulip-sidebar#script.js>.
28+
// On how to extract expected results from the replit, see:
29+
// https://github.com/zulip/zulip-flutter/pull/371#discussion_r1393643523
30+
31+
// TODO Fix bug causing our implementation's results to differ from the
32+
// replit's. Where they differ, see comment with what the replit gives.
33+
34+
// ZULIP_ASSIGNMENT_COLORS
35+
runCheck(const Color(0xff76ce90), const Color(0x4d65bd80));
36+
runCheck(const Color(0xfffae589), const Color(0x4dbdab53)); // 0x4dbdaa52
37+
runCheck(const Color(0xffa6c7e5), const Color(0x4d8eafcc)); // 0x4d8fb0cd
38+
runCheck(const Color(0xffe79ab5), const Color(0x4de295b0)); // 0x4de194af
39+
runCheck(const Color(0xffbfd56f), const Color(0x4d9eb551)); // 0x4d9eb450
40+
runCheck(const Color(0xfff4ae55), const Color(0x4de19d45)); // 0x4de09c44
41+
runCheck(const Color(0xffb0a5fd), const Color(0x4daba0f8)); // 0x4daca2f9
42+
runCheck(const Color(0xffaddfe5), const Color(0x4d83b4b9)); // 0x4d83b4ba
43+
runCheck(const Color(0xfff5ce6e), const Color(0x4dcba749)); // 0x4dcaa648
44+
runCheck(const Color(0xffc2726a), const Color(0x4dc2726a));
45+
runCheck(const Color(0xff94c849), const Color(0x4d86ba3c)); // 0x4d86ba3b
46+
runCheck(const Color(0xffbd86e5), const Color(0x4dbd86e5));
47+
runCheck(const Color(0xffee7e4a), const Color(0x4dee7e4a));
48+
runCheck(const Color(0xffa6dcbf), const Color(0x4d82b69b)); // 0x4d82b79b
49+
runCheck(const Color(0xff95a5fd), const Color(0x4d95a5fd));
50+
runCheck(const Color(0xff53a063), const Color(0x4d53a063));
51+
runCheck(const Color(0xff9987e1), const Color(0x4d9987e1));
52+
runCheck(const Color(0xffe4523d), const Color(0x4de4523d));
53+
runCheck(const Color(0xffc2c2c2), const Color(0x4dababab));
54+
runCheck(const Color(0xff4f8de4), const Color(0x4d4f8de4));
55+
runCheck(const Color(0xffc6a8ad), const Color(0x4dc2a4a9)); // 0x4dc1a4a9
56+
runCheck(const Color(0xffe7cc4d), const Color(0x4dc3ab2a)); // 0x4dc2aa28
57+
runCheck(const Color(0xffc8bebf), const Color(0x4db3a9aa));
58+
runCheck(const Color(0xffa47462), const Color(0x4da47462));
59+
60+
// EXTREME_COLORS
61+
runCheck(const Color(0xFFFFFFFF), const Color(0x4dababab));
62+
runCheck(const Color(0xFF000000), const Color(0x4d474747));
63+
runCheck(const Color(0xFFD3D3D3), const Color(0x4dababab));
64+
runCheck(const Color(0xFFA9A9A9), const Color(0x4da9a9a9));
65+
runCheck(const Color(0xFF808080), const Color(0x4d808080));
66+
runCheck(const Color(0xFFFFFF00), const Color(0x4dacb300)); // 0x4dacb200
67+
runCheck(const Color(0xFFFF0000), const Color(0x4dff0000));
68+
runCheck(const Color(0xFF008000), const Color(0x4d008000));
69+
runCheck(const Color(0xFF0000FF), const Color(0x4d0000ff)); // 0x4d0902ff
70+
runCheck(const Color(0xFFEE82EE), const Color(0x4dee82ee));
71+
runCheck(const Color(0xFFFFA500), const Color(0x4def9800)); // 0x4ded9600
72+
runCheck(const Color(0xFF800080), const Color(0x4d810181)); // 0x4d810281
73+
runCheck(const Color(0xFF00FFFF), const Color(0x4d00c2c3)); // 0x4d00c3c5
74+
runCheck(const Color(0xFFFF00FF), const Color(0x4dff00ff));
75+
runCheck(const Color(0xFF00FF00), const Color(0x4d00cb00));
76+
runCheck(const Color(0xFF800000), const Color(0x4d8d140c)); // 0x4d8b130b
77+
runCheck(const Color(0xFF008080), const Color(0x4d008080));
78+
runCheck(const Color(0xFF000080), const Color(0x4d492bae)); // 0x4d4b2eb3
79+
runCheck(const Color(0xFFFFFFE0), const Color(0x4dadad90)); // 0x4dacad90
80+
runCheck(const Color(0xFFFF69B4), const Color(0x4dff69b4));
81+
});
1482
});
1583
}

0 commit comments

Comments
 (0)