Skip to content

model: Implement dark-mode stream color swatches #643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 10, 2024
55 changes: 44 additions & 11 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ class Subscription extends ZulipStream {
// TODO I'm not sure this is the right home for this; it seems like we might
// instead have chosen to put it in more UI-centered code, like in a custom
// material [ColorScheme] class or something. But it works for now.
StreamColorSwatch colorSwatch() => _swatch ??= StreamColorSwatch(color);
StreamColorSwatch colorSwatch() => _swatch ??= StreamColorSwatch.light(color);

@visibleForTesting
@JsonKey(includeToJson: false)
Expand Down Expand Up @@ -463,16 +463,11 @@ class Subscription extends ZulipStream {
/// Use this in UI code for colors related to [Subscription.color],
/// such as the background of an unread count badge.
class StreamColorSwatch extends ColorSwatch<StreamColorVariant> {
StreamColorSwatch(int base) : this._(base, _compute(base));
StreamColorSwatch.light(int base) : this._(base, _computeLight(base));
StreamColorSwatch.dark(int base) : this._(base, _computeDark(base));

const StreamColorSwatch._(int base, this._swatch) : super(base, _swatch);

/// A [StreamColorSwatch], from a [Map<_StreamColorVariant, Color>]
/// written manually.
@visibleForTesting
const StreamColorSwatch.debugFromBaseAndSwatch(int base, swatch)
: this._(base, swatch);

final Map<StreamColorVariant, Color> _swatch;

/// The [Subscription.color] int that the swatch is based on.
Expand All @@ -498,7 +493,7 @@ class StreamColorSwatch extends ColorSwatch<StreamColorVariant> {
/// Use this in the message list, the "Inbox" view, and the "Streams" view.
Color get barBackground => this[StreamColorVariant.barBackground]!;

static Map<StreamColorVariant, Color> _compute(int base) {
static Map<StreamColorVariant, Color> _computeLight(int base) {
final baseAsColor = Color(base);

final clamped20to75 = clampLchLightness(baseAsColor, 20, 75);
Expand All @@ -517,7 +512,7 @@ class StreamColorSwatch extends ColorSwatch<StreamColorVariant> {
.withOpacity(0.3),

// Follows `.sidebar-row__icon` in Vlad's replit:
// <https://replit.com/@VladKorobov/zulip-topic-feed-colors#script.js>
// <https://replit.com/@VladKorobov/zulip-sidebar#script.js>
//
// TODO fix bug where our results differ from the replit's (see unit tests)
StreamColorVariant.iconOnPlainBackground: clamped20to75,
Expand All @@ -529,7 +524,7 @@ class StreamColorSwatch extends ColorSwatch<StreamColorVariant> {
// TODO fix bug where our results differ from the replit's (see unit tests)
StreamColorVariant.iconOnBarBackground:
clamped20to75AsHsl
.withLightness(clamped20to75AsHsl.lightness - 0.12)
.withLightness(clampDouble(clamped20to75AsHsl.lightness - 0.12, 0.0, 1.0))
.toColor(),

// Follows `.recepient` in Vlad's replit:
Expand All @@ -547,6 +542,44 @@ class StreamColorSwatch extends ColorSwatch<StreamColorVariant> {
};
}

static Map<StreamColorVariant, Color> _computeDark(int base) {
final baseAsColor = Color(base);

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

return {
// See comments in [_computeLight] about what these computations are based
// on, and how the resulting values are a little off sometimes. The
// comments mostly apply here too.

StreamColorVariant.base: baseAsColor,
StreamColorVariant.unreadCountBadgeBackground:
clampLchLightness(baseAsColor, 30, 70)
.withOpacity(0.3),
StreamColorVariant.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)
StreamColorVariant.iconOnBarBackground: clamped20to75,

StreamColorVariant.barBackground:
LabColor.fromColor(const Color(0xff000000))
.interpolate(LabColor.fromColor(clamped20to75), 0.38)
.toColor(),
};
}

/// Copied from [ColorSwatch.lerp].
static StreamColorSwatch? lerp(StreamColorSwatch? a, StreamColorSwatch? b, double t) {
if (identical(a, b)) {
Expand Down
10 changes: 10 additions & 0 deletions lib/widgets/unread_count_badge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ class UnreadCountBadge extends StatelessWidget {
fontSize: 16,
height: (18 / 16),
fontFeatures: [FontFeature.enable('smcp')], // small caps

// From the Figma:
// https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?type=design&node-id=171-12359&mode=design&t=JKrw76SGUF51nSJG-0
// TODO or, when background is stream-colored, follow Vlad's replit?
// https://replit.com/@VladKorobov/zulip-sidebar#script.js
// which would mean:
// - in light mode use `Color.fromRGBO(0, 0, 0, 0.9)`
// - in dark mode use `Color.fromRGBO(255, 255, 255, 0.9)`
// The web app doesn't (yet?) use stream-colored unread markers
// so we can't take direction from there.
color: Color(0xFF222222),
).merge(weightVariableTextStyle(context,
wght: bold ? 600 : null)),
Expand Down
Loading
Loading