Skip to content

Commit 867c28f

Browse files
committed
model: Implement streamColorSwatchDark, for dark mode
We added the light-mode color computations in #381. We're not ready to use this yet, but the color computations are unlikely to change before that time comes, and finding the right ones is a chore that's good to get done. Related: #95
1 parent 1b97faf commit 867c28f

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

lib/api/model/model.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ class Subscription extends ZulipStream {
463463
///
464464
/// Use this in UI code for colors related to [Subscription.color],
465465
/// such as the background of an unread count badge.
466+
///
467+
/// For the dark-mode colors, see [streamColorSwatchDark].
466468
ColorSwatch<StreamColor> streamColorSwatch(int base) {
467469
final baseAsColor = Color(base);
468470

@@ -514,6 +516,67 @@ ColorSwatch<StreamColor> streamColorSwatch(int base) {
514516
return ColorSwatch<StreamColor>(base, map);
515517
}
516518

519+
/// A [ColorSwatch] with dark-mode colors related to a base stream color.
520+
///
521+
/// Use this in UI code for colors related to [Subscription.color],
522+
/// such as the background of an unread count badge.
523+
///
524+
/// For the light-mode colors, see [streamColorSwatch].
525+
ColorSwatch<StreamColor> streamColorSwatchDark(int base) {
526+
final baseAsColor = Color(base);
527+
528+
final clamped20to75 = clampLchLightness(baseAsColor, 20, 75);
529+
530+
final map = {
531+
StreamColor.base: baseAsColor,
532+
533+
// Follows `.unread-count` in Vlad's replit:
534+
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
535+
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
536+
//
537+
// TODO fix bug where our results differ from the replit's (see unit tests)
538+
StreamColor.unreadCountBadgeBackground:
539+
clampLchLightness(baseAsColor, 30, 70)
540+
.withOpacity(0.3),
541+
542+
// Follows `.sidebar-row__icon` in Vlad's replit:
543+
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
544+
//
545+
// TODO fix bug where our results differ from the replit's (see unit tests)
546+
StreamColor.iconOnPlainBackground: clamped20to75,
547+
548+
// Follows the web app (as of zulip/zulip@db03369ac); see
549+
// get_stream_privacy_icon_color in web/src/stream_color.ts.
550+
//
551+
// `.recepeient__icon` in Vlad's replit gives something different so we
552+
// don't use that:
553+
// <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>
554+
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
555+
// But that's OK because Vlad said "I feel like current dark theme contrast
556+
// is fine", and when he said that, this had been the web app's icon color
557+
// for 6+ months (since zulip/zulip@023584e04):
558+
// https://chat.zulip.org/#narrow/stream/101-design/topic/UI.20redesign.3A.20recipient.20bar.20colors/near/1675786
559+
//
560+
// TODO fix bug where our results are unexpected (see unit tests)
561+
StreamColor.iconOnBarBackground: clamped20to75,
562+
563+
// Follows `.recepient` in Vlad's replit:
564+
// <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>
565+
//
566+
// TODO I think [LabColor.interpolate] doesn't actually do LAB mixing;
567+
// it just calls up to the superclass method [ColorModel.interpolate]:
568+
// <https://pub.dev/documentation/flutter_color_models/latest/flutter_color_models/ColorModel/interpolate.html>
569+
// which does ordinary RGB mixing. Investigate and send a PR?
570+
// TODO fix bug where our results differ from the replit's (see unit tests)
571+
StreamColor.barBackground:
572+
LabColor.fromColor(const Color(0xff000000))
573+
.interpolate(LabColor.fromColor(clamped20to75), 0.38)
574+
.toColor(),
575+
};
576+
577+
return ColorSwatch<StreamColor>(base, map);
578+
}
579+
517580
enum StreamColor {
518581
/// The [Subscription.color] int that the swatch is based on.
519582
base,

test/api/model/model_test.dart

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,202 @@ void main() {
325325
runCheck(0xffacc25d, const Color(0xffe9edd6));
326326
});
327327
});
328+
329+
group('streamColorSwatchDark', () {
330+
test('base', () {
331+
check(streamColorSwatchDark(0xffffffff))[StreamColor.base]
332+
.equals(const Color(0xffffffff));
333+
});
334+
335+
test('unreadCountBadgeBackground', () {
336+
void runCheck(int base, Color expected) {
337+
check(streamColorSwatchDark(base))
338+
[StreamColor.unreadCountBadgeBackground].equals(expected);
339+
}
340+
341+
// Check against everything in ZULIP_ASSIGNMENT_COLORS and EXTREME_COLORS
342+
// in <https://replit.com/@VladKorobov/zulip-sidebar#script.js>.
343+
// On how to extract expected results from the replit, see:
344+
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972
345+
346+
// TODO Fix bug causing our implementation's results to differ from the
347+
// replit's. Where they differ, see comment with what the replit gives.
348+
349+
// ZULIP_ASSIGNMENT_COLORS
350+
runCheck(0xff76ce90, const Color(0x4d65bd80));
351+
runCheck(0xfffae589, const Color(0x4dbdab53)); // 0x4dbdaa52
352+
runCheck(0xffa6c7e5, const Color(0x4d8eafcc)); // 0x4d8fb0cd
353+
runCheck(0xffe79ab5, const Color(0x4de295b0)); // 0x4de194af
354+
runCheck(0xffbfd56f, const Color(0x4d9eb551)); // 0x4d9eb450
355+
runCheck(0xfff4ae55, const Color(0x4de19d45)); // 0x4de09c44
356+
runCheck(0xffb0a5fd, const Color(0x4daba0f8)); // 0x4daca2f9
357+
runCheck(0xffaddfe5, const Color(0x4d83b4b9)); // 0x4d83b4ba
358+
runCheck(0xfff5ce6e, const Color(0x4dcba749)); // 0x4dcaa648
359+
runCheck(0xffc2726a, const Color(0x4dc2726a));
360+
runCheck(0xff94c849, const Color(0x4d86ba3c)); // 0x4d86ba3b
361+
runCheck(0xffbd86e5, const Color(0x4dbd86e5));
362+
runCheck(0xffee7e4a, const Color(0x4dee7e4a));
363+
runCheck(0xffa6dcbf, const Color(0x4d82b69b)); // 0x4d82b79b
364+
runCheck(0xff95a5fd, const Color(0x4d95a5fd));
365+
runCheck(0xff53a063, const Color(0x4d53a063));
366+
runCheck(0xff9987e1, const Color(0x4d9987e1));
367+
runCheck(0xffe4523d, const Color(0x4de4523d));
368+
runCheck(0xffc2c2c2, const Color(0x4dababab));
369+
runCheck(0xff4f8de4, const Color(0x4d4f8de4));
370+
runCheck(0xffc6a8ad, const Color(0x4dc2a4a9)); // 0x4dc1a4a9
371+
runCheck(0xffe7cc4d, const Color(0x4dc3ab2a)); // 0x4dc2aa28
372+
runCheck(0xffc8bebf, const Color(0x4db3a9aa));
373+
runCheck(0xffa47462, const Color(0x4da47462));
374+
375+
// EXTREME_COLORS
376+
runCheck(0xFFFFFFFF, const Color(0x4dababab));
377+
runCheck(0xFF000000, const Color(0x4d474747));
378+
runCheck(0xFFD3D3D3, const Color(0x4dababab));
379+
runCheck(0xFFA9A9A9, const Color(0x4da9a9a9));
380+
runCheck(0xFF808080, const Color(0x4d808080));
381+
runCheck(0xFFFFFF00, const Color(0x4dacb300)); // 0x4dacb200
382+
runCheck(0xFFFF0000, const Color(0x4dff0000));
383+
runCheck(0xFF008000, const Color(0x4d008000));
384+
runCheck(0xFF0000FF, const Color(0x4d0000ff)); // 0x4d0902ff
385+
runCheck(0xFFEE82EE, const Color(0x4dee82ee));
386+
runCheck(0xFFFFA500, const Color(0x4def9800)); // 0x4ded9600
387+
runCheck(0xFF800080, const Color(0x4d810181)); // 0x4d810281
388+
runCheck(0xFF00FFFF, const Color(0x4d00c2c3)); // 0x4d00c3c5
389+
runCheck(0xFFFF00FF, const Color(0x4dff00ff));
390+
runCheck(0xFF00FF00, const Color(0x4d00cb00));
391+
runCheck(0xFF800000, const Color(0x4d8d140c)); // 0x4d8b130b
392+
runCheck(0xFF008080, const Color(0x4d008080));
393+
runCheck(0xFF000080, const Color(0x4d492bae)); // 0x4d4b2eb3
394+
runCheck(0xFFFFFFE0, const Color(0x4dadad90)); // 0x4dacad90
395+
runCheck(0xFFFF69B4, const Color(0x4dff69b4));
396+
});
397+
398+
test('iconOnPlainBackground', () {
399+
void runCheck(int base, Color expected) {
400+
check(streamColorSwatchDark(base))
401+
[StreamColor.iconOnPlainBackground].equals(expected);
402+
}
403+
404+
// Check against everything in ZULIP_ASSIGNMENT_COLORS
405+
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
406+
// (Skipping `streamColors` because there are 100+ of them.)
407+
// On how to extract expected results from the replit, see:
408+
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972
409+
410+
// TODO Fix bug causing our implementation's results to differ from the
411+
// replit's. Where they differ, see comment with what the replit gives.
412+
413+
runCheck(0xff76ce90, const Color(0xff73cb8d));
414+
runCheck(0xfffae589, const Color(0xffccb95f)); // 0xffcbb85e
415+
runCheck(0xffa6c7e5, const Color(0xff9cbcda)); // 0xff9cbddb
416+
runCheck(0xffe79ab5, const Color(0xffe79ab5));
417+
runCheck(0xffbfd56f, const Color(0xffacc25d));
418+
runCheck(0xfff4ae55, const Color(0xfff0ab52)); // 0xffefa951
419+
runCheck(0xffb0a5fd, const Color(0xffb0a5fd));
420+
runCheck(0xffaddfe5, const Color(0xff90c1c7)); // 0xff90c2c8
421+
runCheck(0xfff5ce6e, const Color(0xffd9b456)); // 0xffd8b355
422+
runCheck(0xffc2726a, const Color(0xffc2726a));
423+
runCheck(0xff94c849, const Color(0xff94c849));
424+
runCheck(0xffbd86e5, const Color(0xffbd86e5));
425+
runCheck(0xffee7e4a, const Color(0xffee7e4a));
426+
runCheck(0xffa6dcbf, const Color(0xff8fc4a8));
427+
runCheck(0xff95a5fd, const Color(0xff95a5fd));
428+
runCheck(0xff53a063, const Color(0xff53a063));
429+
runCheck(0xff9987e1, const Color(0xff9987e1));
430+
runCheck(0xffe4523d, const Color(0xffe4523d));
431+
runCheck(0xffc2c2c2, const Color(0xffb9b9b9));
432+
runCheck(0xff4f8de4, const Color(0xff4f8de4));
433+
runCheck(0xffc6a8ad, const Color(0xffc6a8ad));
434+
runCheck(0xffe7cc4d, const Color(0xffd1b839)); // 0xffd0b737
435+
runCheck(0xffc8bebf, const Color(0xffc0b6b7));
436+
runCheck(0xffa47462, const Color(0xffa47462));
437+
runCheck(0xffacc25d, const Color(0xffacc25d));
438+
});
439+
440+
test('iconOnBarBackground', () {
441+
void runCheck(int base, Color expected) {
442+
check(streamColorSwatchDark(base))
443+
[StreamColor.iconOnBarBackground].equals(expected);
444+
}
445+
446+
// Check against everything in ZULIP_ASSIGNMENT_COLORS
447+
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
448+
// (Skipping `streamColors` because there are 100+ of them.)
449+
// On how to generate expected results, see:
450+
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972
451+
452+
// TODO Fix bug causing our implementation's results to differ from the
453+
// web app's. Where they differ, see comment with what web uses.
454+
455+
runCheck(0xff76ce90, const Color(0xff73cb8d));
456+
runCheck(0xfffae589, const Color(0xffccb95f)); // 0xffcbb85e
457+
runCheck(0xffa6c7e5, const Color(0xff9cbcda)); // 0xff9cbddb
458+
runCheck(0xffe79ab5, const Color(0xffe79ab5));
459+
runCheck(0xffbfd56f, const Color(0xffacc25d));
460+
runCheck(0xfff4ae55, const Color(0xfff0ab52)); // 0xffefa951
461+
runCheck(0xffb0a5fd, const Color(0xffb0a5fd));
462+
runCheck(0xffaddfe5, const Color(0xff90c1c7)); // 0xff90c2c8
463+
runCheck(0xfff5ce6e, const Color(0xffd9b456)); // 0xffd8b355
464+
runCheck(0xffc2726a, const Color(0xffc2726a));
465+
runCheck(0xff94c849, const Color(0xff94c849));
466+
runCheck(0xffbd86e5, const Color(0xffbd86e5));
467+
runCheck(0xffee7e4a, const Color(0xffee7e4a));
468+
runCheck(0xffa6dcbf, const Color(0xff8fc4a8));
469+
runCheck(0xff95a5fd, const Color(0xff95a5fd));
470+
runCheck(0xff53a063, const Color(0xff53a063));
471+
runCheck(0xff9987e1, const Color(0xff9987e1));
472+
runCheck(0xffe4523d, const Color(0xffe4523d));
473+
runCheck(0xffc2c2c2, const Color(0xffb9b9b9));
474+
runCheck(0xff4f8de4, const Color(0xff4f8de4));
475+
runCheck(0xffc6a8ad, const Color(0xffc6a8ad));
476+
runCheck(0xffe7cc4d, const Color(0xffd1b839)); // 0xffd0b737
477+
runCheck(0xffc8bebf, const Color(0xffc0b6b7));
478+
runCheck(0xffa47462, const Color(0xffa47462));
479+
runCheck(0xffacc25d, const Color(0xffacc25d));
480+
});
481+
482+
test('barBackground', () {
483+
void runCheck(int base, Color expected) {
484+
check(streamColorSwatchDark(base))
485+
[StreamColor.barBackground].equals(expected);
486+
}
487+
488+
// Check against everything in ZULIP_ASSIGNMENT_COLORS
489+
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
490+
// (Skipping `streamColors` because there are 100+ of them.)
491+
// On how to extract expected results from the replit, see:
492+
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972
493+
494+
// TODO Fix bug causing our implementation's results to differ from the
495+
// replit's. Where they differ, see comment with what the replit gives.
496+
497+
runCheck(0xff76ce90, const Color(0xff2e4935));
498+
runCheck(0xfffae589, const Color(0xff4a4327));
499+
runCheck(0xffa6c7e5, const Color(0xff3a444e)); // 0xff3a454e
500+
runCheck(0xffe79ab5, const Color(0xff523a42));
501+
runCheck(0xffbfd56f, const Color(0xff404627));
502+
runCheck(0xfff4ae55, const Color(0xff563f23)); // 0xff553e23
503+
runCheck(0xffb0a5fd, const Color(0xff413d59));
504+
runCheck(0xffaddfe5, const Color(0xff374648));
505+
runCheck(0xfff5ce6e, const Color(0xff4e4224)); // 0xff4e4124
506+
runCheck(0xffc2726a, const Color(0xff472d2a));
507+
runCheck(0xff94c849, const Color(0xff394821)); // 0xff384821
508+
runCheck(0xffbd86e5, const Color(0xff453351));
509+
runCheck(0xffee7e4a, const Color(0xff563120));
510+
runCheck(0xffa6dcbf, const Color(0xff36473e));
511+
runCheck(0xff95a5fd, const Color(0xff393d59));
512+
runCheck(0xff53a063, const Color(0xff243c28));
513+
runCheck(0xff9987e1, const Color(0xff3a3350));
514+
runCheck(0xffe4523d, const Color(0xff53241c)); // 0xff53241b
515+
runCheck(0xffc2c2c2, const Color(0xff434343));
516+
runCheck(0xff4f8de4, const Color(0xff263551)); // 0xff253551
517+
runCheck(0xffc6a8ad, const Color(0xff483e40));
518+
runCheck(0xffe7cc4d, const Color(0xff4c431d)); // 0xff4c431c
519+
runCheck(0xffc8bebf, const Color(0xff464243));
520+
runCheck(0xffa47462, const Color(0xff3d2d27));
521+
runCheck(0xffacc25d, const Color(0xff404627));
522+
});
523+
});
328524
});
329525

330526
group('Message', () {

0 commit comments

Comments
 (0)