Skip to content

Commit 1352007

Browse files
committed
UnreadCountBadge: Consume StreamColorSwatch for stream-colored background
1 parent aee4dab commit 1352007

File tree

7 files changed

+119
-89
lines changed

7 files changed

+119
-89
lines changed

lib/api/model/model.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
22
import 'package:flutter/painting.dart';
33
import 'package:json_annotation/json_annotation.dart';
44

5+
import '../../widgets/color.dart';
56
import 'reaction.dart';
67

78
export 'reaction.dart';
@@ -393,18 +394,29 @@ class StreamColorSwatch extends ColorSwatch<_StreamColorVariant> {
393394

394395
Color get base => this[_StreamColorVariant.base]!;
395396

397+
Color get unreadCountBadgeBackground => this[_StreamColorVariant.unreadCountBadgeBackground]!;
398+
396399
static Map<_StreamColorVariant, Color> _compute(int base) {
397400
final baseAsColor = Color(base);
398401

399402
return {
400403
_StreamColorVariant.base: baseAsColor,
404+
405+
// Follows `.unread-count` in Vlad's replit:
406+
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
407+
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
408+
//
409+
// TODO fix bug where our results differ from the replit's (see unit tests)
410+
_StreamColorVariant.unreadCountBadgeBackground:
411+
clampLchLightness(baseAsColor, 30, 70)
412+
.withOpacity(0.3),
401413
};
402414
}
403415
}
404416

405417
enum _StreamColorVariant {
406418
base,
407-
// TODO more, like the unread-count badge background color
419+
unreadCountBadgeBackground,
408420
}
409421

410422
/// As in the get-messages response.

lib/widgets/recent_dm_conversations.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class RecentDmConversationsItem extends StatelessWidget {
134134
const SizedBox(width: 12),
135135
unreadCount > 0
136136
? Padding(padding: const EdgeInsetsDirectional.only(end: 16),
137-
child: UnreadCountBadge(baseStreamColor: null,
137+
child: UnreadCountBadge(backgroundColor: null,
138138
count: unreadCount))
139139
: const SizedBox(),
140140
]))));

lib/widgets/unread_count_badge.dart

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'dart:ui';
22

33
import 'package:flutter/material.dart';
44

5-
import 'color.dart';
5+
import '../api/model/model.dart';
66
import 'text.dart';
77

88
/// A widget to display a given number of unreads in a conversation.
@@ -13,42 +13,33 @@ class UnreadCountBadge extends StatelessWidget {
1313
const UnreadCountBadge({
1414
super.key,
1515
required this.count,
16-
required this.baseStreamColor,
16+
required this.backgroundColor,
1717
this.bold = false,
1818
});
1919

2020
final int count;
2121
final bool bold;
2222

23-
/// A base stream color, from a stream subscription in user data, or null.
23+
/// The badge's background color.
2424
///
25-
/// If not null, the background will be colored with an appropriate
26-
/// transformation of this.
25+
/// Pass a [StreamColorSwatch] if this badge represents messages in one
26+
/// specific stream. The appropriate color from the swatch will be used.
2727
///
2828
/// 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-
// TODO fix bug where our results differ from the replit's (see unit tests)
42-
// TODO profiling for expensive computation
43-
return clampLchLightness(baseStreamColor!, 30, 70).withOpacity(0.3);
44-
}
29+
final Color? backgroundColor;
4530

4631
@override
4732
Widget build(BuildContext context) {
33+
final effectiveBackgroundColor = switch (backgroundColor) {
34+
StreamColorSwatch(unreadCountBadgeBackground: var color) => color,
35+
Color() => backgroundColor,
36+
null => const Color.fromRGBO(102, 102, 153, 0.15),
37+
};
38+
4839
return DecoratedBox(
4940
decoration: BoxDecoration(
5041
borderRadius: BorderRadius.circular(3),
51-
color: getBackgroundColor(),
42+
color: effectiveBackgroundColor,
5243
),
5344
child: Padding(
5445
padding: const EdgeInsets.fromLTRB(4, 0, 4, 1),

test/api/model/model_checks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extension ZulipStreamChecks on Subject<ZulipStream> {
99

1010
extension StreamColorSwatchChecks on Subject<StreamColorSwatch> {
1111
Subject<Color> get base => has((s) => s.base, 'base');
12+
Subject<Color> get unreadCountBadgeBackground => has((s) => s.unreadCountBadgeBackground, 'unreadCountBadgeBackground');
1213
}
1314

1415
extension MessageChecks on Subject<Message> {

test/api/model/model_test.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,68 @@ void main() {
131131
test('base', () {
132132
check(StreamColorSwatch(0xffffffff)).base.equals(const Color(0xffffffff));
133133
});
134+
135+
test('unreadCountBadgeBackground', () {
136+
void runCheck(int base, Color expected) {
137+
check(StreamColorSwatch(base)).unreadCountBadgeBackground.equals(expected);
138+
}
139+
140+
// Check against everything in ZULIP_ASSIGNMENT_COLORS and EXTREME_COLORS
141+
// in <https://replit.com/@VladKorobov/zulip-sidebar#script.js>.
142+
// On how to extract expected results from the replit, see:
143+
// https://github.com/zulip/zulip-flutter/pull/371#discussion_r1393643523
144+
145+
// TODO Fix bug causing our implementation's results to differ from the
146+
// replit's. Where they differ, see comment with what the replit gives.
147+
148+
// ZULIP_ASSIGNMENT_COLORS
149+
runCheck(0xff76ce90, const Color(0x4d65bd80));
150+
runCheck(0xfffae589, const Color(0x4dbdab53)); // 0x4dbdaa52
151+
runCheck(0xffa6c7e5, const Color(0x4d8eafcc)); // 0x4d8fb0cd
152+
runCheck(0xffe79ab5, const Color(0x4de295b0)); // 0x4de194af
153+
runCheck(0xffbfd56f, const Color(0x4d9eb551)); // 0x4d9eb450
154+
runCheck(0xfff4ae55, const Color(0x4de19d45)); // 0x4de09c44
155+
runCheck(0xffb0a5fd, const Color(0x4daba0f8)); // 0x4daca2f9
156+
runCheck(0xffaddfe5, const Color(0x4d83b4b9)); // 0x4d83b4ba
157+
runCheck(0xfff5ce6e, const Color(0x4dcba749)); // 0x4dcaa648
158+
runCheck(0xffc2726a, const Color(0x4dc2726a));
159+
runCheck(0xff94c849, const Color(0x4d86ba3c)); // 0x4d86ba3b
160+
runCheck(0xffbd86e5, const Color(0x4dbd86e5));
161+
runCheck(0xffee7e4a, const Color(0x4dee7e4a));
162+
runCheck(0xffa6dcbf, const Color(0x4d82b69b)); // 0x4d82b79b
163+
runCheck(0xff95a5fd, const Color(0x4d95a5fd));
164+
runCheck(0xff53a063, const Color(0x4d53a063));
165+
runCheck(0xff9987e1, const Color(0x4d9987e1));
166+
runCheck(0xffe4523d, const Color(0x4de4523d));
167+
runCheck(0xffc2c2c2, const Color(0x4dababab));
168+
runCheck(0xff4f8de4, const Color(0x4d4f8de4));
169+
runCheck(0xffc6a8ad, const Color(0x4dc2a4a9)); // 0x4dc1a4a9
170+
runCheck(0xffe7cc4d, const Color(0x4dc3ab2a)); // 0x4dc2aa28
171+
runCheck(0xffc8bebf, const Color(0x4db3a9aa));
172+
runCheck(0xffa47462, const Color(0x4da47462));
173+
174+
// EXTREME_COLORS
175+
runCheck(0xFFFFFFFF, const Color(0x4dababab));
176+
runCheck(0xFF000000, const Color(0x4d474747));
177+
runCheck(0xFFD3D3D3, const Color(0x4dababab));
178+
runCheck(0xFFA9A9A9, const Color(0x4da9a9a9));
179+
runCheck(0xFF808080, const Color(0x4d808080));
180+
runCheck(0xFFFFFF00, const Color(0x4dacb300)); // 0x4dacb200
181+
runCheck(0xFFFF0000, const Color(0x4dff0000));
182+
runCheck(0xFF008000, const Color(0x4d008000));
183+
runCheck(0xFF0000FF, const Color(0x4d0000ff)); // 0x4d0902ff
184+
runCheck(0xFFEE82EE, const Color(0x4dee82ee));
185+
runCheck(0xFFFFA500, const Color(0x4def9800)); // 0x4ded9600
186+
runCheck(0xFF800080, const Color(0x4d810181)); // 0x4d810281
187+
runCheck(0xFF00FFFF, const Color(0x4d00c2c3)); // 0x4d00c3c5
188+
runCheck(0xFFFF00FF, const Color(0x4dff00ff));
189+
runCheck(0xFF00FF00, const Color(0x4d00cb00));
190+
runCheck(0xFF800000, const Color(0x4d8d140c)); // 0x4d8b130b
191+
runCheck(0xFF008080, const Color(0x4d008080));
192+
runCheck(0xFF000080, const Color(0x4d492bae)); // 0x4d4b2eb3
193+
runCheck(0xFFFFFFE0, const Color(0x4dadad90)); // 0x4dacad90
194+
runCheck(0xFFFF69B4, const Color(0x4dff69b4));
195+
});
134196
});
135197
});
136198

test/widgets/unread_count_badge_checks.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ import 'package:zulip/widgets/unread_count_badge.dart';
66
extension UnreadCountBadgeChecks on Subject<UnreadCountBadge> {
77
Subject<int> get count => has((b) => b.count, 'count');
88
Subject<bool> get bold => has((b) => b.bold, 'bold');
9-
Subject<Color> get backgroundColor => has((b) => b.getBackgroundColor(), 'background color');
9+
Subject<Color?> get backgroundColor => has((b) => b.backgroundColor, 'backgroundColor');
1010
}
Lines changed: 28 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,47 @@
11
import 'package:checks/checks.dart';
2-
import 'package:flutter/widgets.dart';
2+
import 'package:flutter/material.dart';
33

44
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:zulip/api/model/model.dart';
56
import 'package:zulip/widgets/unread_count_badge.dart';
67

7-
import 'unread_count_badge_checks.dart';
8-
98
void main() {
109
group('UnreadCountBadge', () {
1110
testWidgets('smoke test; no crash', (tester) async {
1211
await tester.pumpWidget(
1312
const Directionality(textDirection: TextDirection.ltr,
14-
child: UnreadCountBadge(count: 1, baseStreamColor: null)));
13+
child: UnreadCountBadge(count: 1, backgroundColor: null)));
1514
tester.widget(find.text("1"));
1615
});
1716

18-
test('colors', () {
19-
void runCheck(Color? baseStreamColor, Color expectedBackgroundColor) {
20-
check(UnreadCountBadge(count: 1, baseStreamColor: baseStreamColor))
21-
.backgroundColor.equals(expectedBackgroundColor);
17+
group('background', () {
18+
Future<void> prepare(WidgetTester tester, Color? backgroundColor) async {
19+
await tester.pumpWidget(
20+
Directionality(textDirection: TextDirection.ltr,
21+
child: UnreadCountBadge(count: 1, backgroundColor: backgroundColor)));
2222
}
2323

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));
24+
Color? findBackgroundColor(WidgetTester tester) {
25+
final widget = tester.widget<DecoratedBox>(find.byType(DecoratedBox));
26+
final decoration = widget.decoration as BoxDecoration;
27+
return decoration.color;
28+
}
5929

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));
30+
testWidgets('default color', (WidgetTester tester) async {
31+
await prepare(tester, null);
32+
check(findBackgroundColor(tester)).equals(const Color(0x26666699));
33+
});
34+
35+
testWidgets('specified color', (WidgetTester tester) async {
36+
await prepare(tester, Colors.pink);
37+
check(findBackgroundColor(tester)).equals(Colors.pink);
38+
});
39+
40+
testWidgets('stream color', (WidgetTester tester) async {
41+
final swatch = StreamColorSwatch(0xff76ce90);
42+
await prepare(tester, swatch);
43+
check(findBackgroundColor(tester)).equals(swatch.unreadCountBadgeBackground);
44+
});
8145
});
8246
});
8347
}

0 commit comments

Comments
 (0)