Skip to content

Commit

Permalink
model: Implement streamColorSwatchDark, for dark mode
Browse files Browse the repository at this point in the history
We added the light-mode color computations in zulip#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: zulip#95
  • Loading branch information
chrisbobbe committed May 4, 2024
1 parent 1b97faf commit 867c28f
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 0 deletions.
63 changes: 63 additions & 0 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ class Subscription extends ZulipStream {
///
/// Use this in UI code for colors related to [Subscription.color],
/// such as the background of an unread count badge.
///
/// For the dark-mode colors, see [streamColorSwatchDark].
ColorSwatch<StreamColor> streamColorSwatch(int base) {
final baseAsColor = Color(base);

Expand Down Expand Up @@ -514,6 +516,67 @@ ColorSwatch<StreamColor> streamColorSwatch(int base) {
return ColorSwatch<StreamColor>(base, map);
}

/// A [ColorSwatch] with dark-mode colors related to a base stream color.
///
/// Use this in UI code for colors related to [Subscription.color],
/// such as the background of an unread count badge.
///
/// For the light-mode colors, see [streamColorSwatch].
ColorSwatch<StreamColor> streamColorSwatchDark(int base) {
final baseAsColor = Color(base);

final clamped20to75 = clampLchLightness(baseAsColor, 20, 75);

final map = {
StreamColor.base: baseAsColor,

// Follows `.unread-count` in Vlad's replit:
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
//
// TODO fix bug where our results differ from the replit's (see unit tests)
StreamColor.unreadCountBadgeBackground:
clampLchLightness(baseAsColor, 30, 70)
.withOpacity(0.3),

// Follows `.sidebar-row__icon` in Vlad's replit:
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
//
// TODO fix bug where our results differ from the replit's (see unit tests)
StreamColor.iconOnPlainBackground: clamped20to75,

// Follows the web app (as of zulip/zulip@db03369ac); see
// get_stream_privacy_icon_color in web/src/stream_color.ts.
//
// `.recepeient__icon` in Vlad's replit gives something different so we
// don't use that:
// <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>
// <https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20.23F117.20.22Inbox.22.20screen/near/1624484>
// But that's OK because Vlad said "I feel like current dark theme contrast
// is fine", and when he said that, this had been the web app's icon color
// for 6+ months (since zulip/zulip@023584e04):
// https://chat.zulip.org/#narrow/stream/101-design/topic/UI.20redesign.3A.20recipient.20bar.20colors/near/1675786
//
// TODO fix bug where our results are unexpected (see unit tests)
StreamColor.iconOnBarBackground: clamped20to75,

// Follows `.recepient` in Vlad's replit:
// <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>
//
// TODO I think [LabColor.interpolate] doesn't actually do LAB mixing;
// it just calls up to the superclass method [ColorModel.interpolate]:
// <https://pub.dev/documentation/flutter_color_models/latest/flutter_color_models/ColorModel/interpolate.html>
// which does ordinary RGB mixing. Investigate and send a PR?
// TODO fix bug where our results differ from the replit's (see unit tests)
StreamColor.barBackground:
LabColor.fromColor(const Color(0xff000000))
.interpolate(LabColor.fromColor(clamped20to75), 0.38)
.toColor(),
};

return ColorSwatch<StreamColor>(base, map);
}

enum StreamColor {
/// The [Subscription.color] int that the swatch is based on.
base,
Expand Down
196 changes: 196 additions & 0 deletions test/api/model/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,202 @@ void main() {
runCheck(0xffacc25d, const Color(0xffe9edd6));
});
});

group('streamColorSwatchDark', () {
test('base', () {
check(streamColorSwatchDark(0xffffffff))[StreamColor.base]
.equals(const Color(0xffffffff));
});

test('unreadCountBadgeBackground', () {
void runCheck(int base, Color expected) {
check(streamColorSwatchDark(base))
[StreamColor.unreadCountBadgeBackground].equals(expected);
}

// Check against everything in ZULIP_ASSIGNMENT_COLORS and EXTREME_COLORS
// in <https://replit.com/@VladKorobov/zulip-sidebar#script.js>.
// On how to extract expected results from the replit, see:
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972

// TODO Fix bug causing our implementation's results to differ from the
// replit's. Where they differ, see comment with what the replit gives.

// ZULIP_ASSIGNMENT_COLORS
runCheck(0xff76ce90, const Color(0x4d65bd80));
runCheck(0xfffae589, const Color(0x4dbdab53)); // 0x4dbdaa52
runCheck(0xffa6c7e5, const Color(0x4d8eafcc)); // 0x4d8fb0cd
runCheck(0xffe79ab5, const Color(0x4de295b0)); // 0x4de194af
runCheck(0xffbfd56f, const Color(0x4d9eb551)); // 0x4d9eb450
runCheck(0xfff4ae55, const Color(0x4de19d45)); // 0x4de09c44
runCheck(0xffb0a5fd, const Color(0x4daba0f8)); // 0x4daca2f9
runCheck(0xffaddfe5, const Color(0x4d83b4b9)); // 0x4d83b4ba
runCheck(0xfff5ce6e, const Color(0x4dcba749)); // 0x4dcaa648
runCheck(0xffc2726a, const Color(0x4dc2726a));
runCheck(0xff94c849, const Color(0x4d86ba3c)); // 0x4d86ba3b
runCheck(0xffbd86e5, const Color(0x4dbd86e5));
runCheck(0xffee7e4a, const Color(0x4dee7e4a));
runCheck(0xffa6dcbf, const Color(0x4d82b69b)); // 0x4d82b79b
runCheck(0xff95a5fd, const Color(0x4d95a5fd));
runCheck(0xff53a063, const Color(0x4d53a063));
runCheck(0xff9987e1, const Color(0x4d9987e1));
runCheck(0xffe4523d, const Color(0x4de4523d));
runCheck(0xffc2c2c2, const Color(0x4dababab));
runCheck(0xff4f8de4, const Color(0x4d4f8de4));
runCheck(0xffc6a8ad, const Color(0x4dc2a4a9)); // 0x4dc1a4a9
runCheck(0xffe7cc4d, const Color(0x4dc3ab2a)); // 0x4dc2aa28
runCheck(0xffc8bebf, const Color(0x4db3a9aa));
runCheck(0xffa47462, const Color(0x4da47462));

// EXTREME_COLORS
runCheck(0xFFFFFFFF, const Color(0x4dababab));
runCheck(0xFF000000, const Color(0x4d474747));
runCheck(0xFFD3D3D3, const Color(0x4dababab));
runCheck(0xFFA9A9A9, const Color(0x4da9a9a9));
runCheck(0xFF808080, const Color(0x4d808080));
runCheck(0xFFFFFF00, const Color(0x4dacb300)); // 0x4dacb200
runCheck(0xFFFF0000, const Color(0x4dff0000));
runCheck(0xFF008000, const Color(0x4d008000));
runCheck(0xFF0000FF, const Color(0x4d0000ff)); // 0x4d0902ff
runCheck(0xFFEE82EE, const Color(0x4dee82ee));
runCheck(0xFFFFA500, const Color(0x4def9800)); // 0x4ded9600
runCheck(0xFF800080, const Color(0x4d810181)); // 0x4d810281
runCheck(0xFF00FFFF, const Color(0x4d00c2c3)); // 0x4d00c3c5
runCheck(0xFFFF00FF, const Color(0x4dff00ff));
runCheck(0xFF00FF00, const Color(0x4d00cb00));
runCheck(0xFF800000, const Color(0x4d8d140c)); // 0x4d8b130b
runCheck(0xFF008080, const Color(0x4d008080));
runCheck(0xFF000080, const Color(0x4d492bae)); // 0x4d4b2eb3
runCheck(0xFFFFFFE0, const Color(0x4dadad90)); // 0x4dacad90
runCheck(0xFFFF69B4, const Color(0x4dff69b4));
});

test('iconOnPlainBackground', () {
void runCheck(int base, Color expected) {
check(streamColorSwatchDark(base))
[StreamColor.iconOnPlainBackground].equals(expected);
}

// Check against everything in ZULIP_ASSIGNMENT_COLORS
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
// (Skipping `streamColors` because there are 100+ of them.)
// On how to extract expected results from the replit, see:
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972

// TODO Fix bug causing our implementation's results to differ from the
// replit's. Where they differ, see comment with what the replit gives.

runCheck(0xff76ce90, const Color(0xff73cb8d));
runCheck(0xfffae589, const Color(0xffccb95f)); // 0xffcbb85e
runCheck(0xffa6c7e5, const Color(0xff9cbcda)); // 0xff9cbddb
runCheck(0xffe79ab5, const Color(0xffe79ab5));
runCheck(0xffbfd56f, const Color(0xffacc25d));
runCheck(0xfff4ae55, const Color(0xfff0ab52)); // 0xffefa951
runCheck(0xffb0a5fd, const Color(0xffb0a5fd));
runCheck(0xffaddfe5, const Color(0xff90c1c7)); // 0xff90c2c8
runCheck(0xfff5ce6e, const Color(0xffd9b456)); // 0xffd8b355
runCheck(0xffc2726a, const Color(0xffc2726a));
runCheck(0xff94c849, const Color(0xff94c849));
runCheck(0xffbd86e5, const Color(0xffbd86e5));
runCheck(0xffee7e4a, const Color(0xffee7e4a));
runCheck(0xffa6dcbf, const Color(0xff8fc4a8));
runCheck(0xff95a5fd, const Color(0xff95a5fd));
runCheck(0xff53a063, const Color(0xff53a063));
runCheck(0xff9987e1, const Color(0xff9987e1));
runCheck(0xffe4523d, const Color(0xffe4523d));
runCheck(0xffc2c2c2, const Color(0xffb9b9b9));
runCheck(0xff4f8de4, const Color(0xff4f8de4));
runCheck(0xffc6a8ad, const Color(0xffc6a8ad));
runCheck(0xffe7cc4d, const Color(0xffd1b839)); // 0xffd0b737
runCheck(0xffc8bebf, const Color(0xffc0b6b7));
runCheck(0xffa47462, const Color(0xffa47462));
runCheck(0xffacc25d, const Color(0xffacc25d));
});

test('iconOnBarBackground', () {
void runCheck(int base, Color expected) {
check(streamColorSwatchDark(base))
[StreamColor.iconOnBarBackground].equals(expected);
}

// Check against everything in ZULIP_ASSIGNMENT_COLORS
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
// (Skipping `streamColors` because there are 100+ of them.)
// On how to generate expected results, see:
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972

// TODO Fix bug causing our implementation's results to differ from the
// web app's. Where they differ, see comment with what web uses.

runCheck(0xff76ce90, const Color(0xff73cb8d));
runCheck(0xfffae589, const Color(0xffccb95f)); // 0xffcbb85e
runCheck(0xffa6c7e5, const Color(0xff9cbcda)); // 0xff9cbddb
runCheck(0xffe79ab5, const Color(0xffe79ab5));
runCheck(0xffbfd56f, const Color(0xffacc25d));
runCheck(0xfff4ae55, const Color(0xfff0ab52)); // 0xffefa951
runCheck(0xffb0a5fd, const Color(0xffb0a5fd));
runCheck(0xffaddfe5, const Color(0xff90c1c7)); // 0xff90c2c8
runCheck(0xfff5ce6e, const Color(0xffd9b456)); // 0xffd8b355
runCheck(0xffc2726a, const Color(0xffc2726a));
runCheck(0xff94c849, const Color(0xff94c849));
runCheck(0xffbd86e5, const Color(0xffbd86e5));
runCheck(0xffee7e4a, const Color(0xffee7e4a));
runCheck(0xffa6dcbf, const Color(0xff8fc4a8));
runCheck(0xff95a5fd, const Color(0xff95a5fd));
runCheck(0xff53a063, const Color(0xff53a063));
runCheck(0xff9987e1, const Color(0xff9987e1));
runCheck(0xffe4523d, const Color(0xffe4523d));
runCheck(0xffc2c2c2, const Color(0xffb9b9b9));
runCheck(0xff4f8de4, const Color(0xff4f8de4));
runCheck(0xffc6a8ad, const Color(0xffc6a8ad));
runCheck(0xffe7cc4d, const Color(0xffd1b839)); // 0xffd0b737
runCheck(0xffc8bebf, const Color(0xffc0b6b7));
runCheck(0xffa47462, const Color(0xffa47462));
runCheck(0xffacc25d, const Color(0xffacc25d));
});

test('barBackground', () {
void runCheck(int base, Color expected) {
check(streamColorSwatchDark(base))
[StreamColor.barBackground].equals(expected);
}

// Check against everything in ZULIP_ASSIGNMENT_COLORS
// in <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>.
// (Skipping `streamColors` because there are 100+ of them.)
// On how to extract expected results from the replit, see:
// https://github.com/zulip/zulip-flutter/pull/643#issuecomment-2093940972

// TODO Fix bug causing our implementation's results to differ from the
// replit's. Where they differ, see comment with what the replit gives.

runCheck(0xff76ce90, const Color(0xff2e4935));
runCheck(0xfffae589, const Color(0xff4a4327));
runCheck(0xffa6c7e5, const Color(0xff3a444e)); // 0xff3a454e
runCheck(0xffe79ab5, const Color(0xff523a42));
runCheck(0xffbfd56f, const Color(0xff404627));
runCheck(0xfff4ae55, const Color(0xff563f23)); // 0xff553e23
runCheck(0xffb0a5fd, const Color(0xff413d59));
runCheck(0xffaddfe5, const Color(0xff374648));
runCheck(0xfff5ce6e, const Color(0xff4e4224)); // 0xff4e4124
runCheck(0xffc2726a, const Color(0xff472d2a));
runCheck(0xff94c849, const Color(0xff394821)); // 0xff384821
runCheck(0xffbd86e5, const Color(0xff453351));
runCheck(0xffee7e4a, const Color(0xff563120));
runCheck(0xffa6dcbf, const Color(0xff36473e));
runCheck(0xff95a5fd, const Color(0xff393d59));
runCheck(0xff53a063, const Color(0xff243c28));
runCheck(0xff9987e1, const Color(0xff3a3350));
runCheck(0xffe4523d, const Color(0xff53241c)); // 0xff53241b
runCheck(0xffc2c2c2, const Color(0xff434343));
runCheck(0xff4f8de4, const Color(0xff263551)); // 0xff253551
runCheck(0xffc6a8ad, const Color(0xff483e40));
runCheck(0xffe7cc4d, const Color(0xff4c431d)); // 0xff4c431c
runCheck(0xffc8bebf, const Color(0xff464243));
runCheck(0xffa47462, const Color(0xff3d2d27));
runCheck(0xffacc25d, const Color(0xff404627));
});
});
});

group('Message', () {
Expand Down

0 comments on commit 867c28f

Please sign in to comment.