From 88bdf6907ababd41097a7c21cba032a31900a13a Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 10 Jul 2024 15:18:38 -0700 Subject: [PATCH 1/5] theme [nfc]: s/bgMain/mainBackground/, following change in Figma For how I retrieved the current list of variables, see https://github.com/zulip/zulip-flutter/pull/762#discussion_r1664748114 . --- lib/widgets/theme.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index ac6ff28f13..88892e180e 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -59,7 +59,7 @@ ThemeData zulipThemeData(BuildContext context) { colorScheme: ColorScheme.fromSeed( seedColor: kZulipBrandColor, ), - scaffoldBackgroundColor: designVariables.bgMain, + scaffoldBackgroundColor: designVariables.mainBackground, tooltipTheme: const TooltipThemeData(preferBelow: false), ); } @@ -76,18 +76,18 @@ const kZulipBrandColor = Color.fromRGBO(0x64, 0x92, 0xfe, 1); /// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=2945-49492&t=MEb4vtp7S26nntxm-0 class DesignVariables extends ThemeExtension { DesignVariables() : - bgMain = const Color(0xfff0f0f0), bgTopBar = const Color(0xfff5f5f5), borderBar = const Color(0x33000000), icon = const Color(0xff666699), + mainBackground = const Color(0xfff0f0f0), title = const Color(0xff1a1a1a), streamColorSwatches = StreamColorSwatches.light; DesignVariables._({ - required this.bgMain, required this.bgTopBar, required this.borderBar, required this.icon, + required this.mainBackground, required this.title, required this.streamColorSwatches, }); @@ -102,10 +102,10 @@ class DesignVariables extends ThemeExtension { return extension!; } - final Color bgMain; final Color bgTopBar; final Color borderBar; final Color icon; + final Color mainBackground; final Color title; // Not exactly from the Figma design, but from Vlad anyway. @@ -113,18 +113,18 @@ class DesignVariables extends ThemeExtension { @override DesignVariables copyWith({ - Color? bgMain, Color? bgTopBar, Color? borderBar, Color? icon, + Color? mainBackground, Color? title, StreamColorSwatches? streamColorSwatches, }) { return DesignVariables._( - bgMain: bgMain ?? this.bgMain, bgTopBar: bgTopBar ?? this.bgTopBar, borderBar: borderBar ?? this.borderBar, icon: icon ?? this.icon, + mainBackground: mainBackground ?? this.mainBackground, title: title ?? this.title, streamColorSwatches: streamColorSwatches ?? this.streamColorSwatches, ); @@ -136,10 +136,10 @@ class DesignVariables extends ThemeExtension { return this; } return DesignVariables._( - bgMain: Color.lerp(bgMain, other.bgMain, t)!, bgTopBar: Color.lerp(bgTopBar, other.bgTopBar, t)!, borderBar: Color.lerp(borderBar, other.borderBar, t)!, icon: Color.lerp(icon, other.icon, t)!, + mainBackground: Color.lerp(mainBackground, other.mainBackground, t)!, title: Color.lerp(title, other.title, t)!, streamColorSwatches: StreamColorSwatches.lerp(streamColorSwatches, other.streamColorSwatches, t), ); From 1816786bee05a4eb46cad90564f099c855764c36 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 12 Jul 2024 12:52:03 -0700 Subject: [PATCH 2/5] theme [nfc]: Add debugFollowPlatformBrightness, to use in a test --- lib/widgets/theme.dart | 44 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index 88892e180e..aadc41371b 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -5,11 +5,50 @@ import 'content.dart'; import 'stream_colors.dart'; import 'text.dart'; +/// In debug mode, controls whether the UI responds to +/// [MediaQueryData.platformBrightness]. +/// +/// Outside of debug mode, this is always false and the setter has no effect. +// TODO(#95) when dark theme is fully implemented, simplify away; +// the UI should always respond. +bool get debugFollowPlatformBrightness { + bool result = false; + assert(() { + result = _debugFollowPlatformBrightness; + return true; + }()); + return result; +} +bool _debugFollowPlatformBrightness = false; +set debugFollowPlatformBrightness(bool value) { + assert(() { + _debugFollowPlatformBrightness = value; + return true; + }()); +} + + ThemeData zulipThemeData(BuildContext context) { - final designVariables = DesignVariables(); + final DesignVariables designVariables; + final List themeExtensions; + Brightness brightness = debugFollowPlatformBrightness + ? MediaQuery.of(context).platformBrightness + : Brightness.light; + switch (brightness) { + case Brightness.light: { + designVariables = DesignVariables(); + themeExtensions = [ContentTheme.light(context), designVariables]; + } + case Brightness.dark: { + designVariables = DesignVariables(); // TODO(#95) + themeExtensions = [ContentTheme.dark(context), designVariables]; + } + } + return ThemeData( + brightness: brightness, typography: zulipTypography(context), - extensions: [ContentTheme.light(context), designVariables], + extensions: themeExtensions, appBarTheme: AppBarTheme( // Set these two fields to prevent a color change in [AppBar]s when // there is something scrolled under it. If an app bar hasn't been @@ -57,6 +96,7 @@ ThemeData zulipThemeData(BuildContext context) { // Or try this tool to see the whole palette: // https://m3.material.io/theme-builder#/custom colorScheme: ColorScheme.fromSeed( + brightness: brightness, seedColor: kZulipBrandColor, ), scaffoldBackgroundColor: designVariables.mainBackground, From b2e3ff617603561b29307b23542ab8bda93e5fec Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 12 Jul 2024 13:01:35 -0700 Subject: [PATCH 3/5] theme test [nfc]: Simplify colorSwatchFor test --- test/widgets/theme_test.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/widgets/theme_test.dart b/test/widgets/theme_test.dart index 763aab5d11..a5f509c9a6 100644 --- a/test/widgets/theme_test.dart +++ b/test/widgets/theme_test.dart @@ -86,16 +86,14 @@ void main() { await tester.pumpWidget(const ZulipApp()); await tester.pump(); - late StreamColorSwatch actualSwatch; final navigator = await ZulipApp.navigator; - navigator.push(MaterialWidgetRoute(page: Builder(builder: (context) { - actualSwatch = colorSwatchFor(context, subscription); - return const Placeholder(); - }))); + navigator.push(MaterialWidgetRoute(page: Builder(builder: (context) => + const Placeholder()))); await tester.pumpAndSettle(); + final element = tester.element(find.byType(Placeholder)); // Compares all the swatch's members; see [ColorSwatch]'s `operator ==`. - check(actualSwatch).equals(expected); + check(colorSwatchFor(element, subscription)).equals(expected); }); } From 3ac8ceccc817fd2146ca60e808eedcf1b89ff522 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 12 Jul 2024 14:54:49 -0700 Subject: [PATCH 4/5] theme test [nfc]: Remove a `doTest` helper that we don't want anymore We can check the dark and lerped states conveniently without using this helper. --- test/widgets/theme_test.dart | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/widgets/theme_test.dart b/test/widgets/theme_test.dart index a5f509c9a6..f0591cbb1a 100644 --- a/test/widgets/theme_test.dart +++ b/test/widgets/theme_test.dart @@ -77,27 +77,27 @@ void main() { }); group('colorSwatchFor', () { - void doTest(String description, int baseColor, StreamColorSwatch expected) { - testWidgets('$description $baseColor', (WidgetTester tester) async { - addTearDown(testBinding.reset); + const baseColor = 0xff76ce90; - final subscription = eg.subscription(eg.stream(), color: baseColor); + testWidgets('light $baseColor', (WidgetTester tester) async { + addTearDown(testBinding.reset); - await tester.pumpWidget(const ZulipApp()); - await tester.pump(); + final subscription = eg.subscription(eg.stream(), color: baseColor); - final navigator = await ZulipApp.navigator; - navigator.push(MaterialWidgetRoute(page: Builder(builder: (context) => - const Placeholder()))); - await tester.pumpAndSettle(); + await tester.pumpWidget(const ZulipApp()); + await tester.pump(); - final element = tester.element(find.byType(Placeholder)); - // Compares all the swatch's members; see [ColorSwatch]'s `operator ==`. - check(colorSwatchFor(element, subscription)).equals(expected); - }); - } + final navigator = await ZulipApp.navigator; + navigator.push(MaterialWidgetRoute(page: Builder(builder: (context) => + const Placeholder()))); + await tester.pumpAndSettle(); + + final element = tester.element(find.byType(Placeholder)); + // Compares all the swatch's members; see [ColorSwatch]'s `operator ==`. + check(colorSwatchFor(element, subscription)) + .equals(StreamColorSwatch.light(baseColor)); + }); - doTest('light', 0xff76ce90, StreamColorSwatch.light(0xff76ce90)); // TODO(#95) test with Brightness.dark and lerping between light/dark }); } From af2e17862560a886a500aa00f26606d3b7283611 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 10 Jul 2024 15:26:24 -0700 Subject: [PATCH 5/5] theme: Implement DesignVariables.dark --- lib/widgets/theme.dart | 30 ++++++++++++++------- test/widgets/theme_test.dart | 52 +++++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index aadc41371b..c8742cae72 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -36,11 +36,11 @@ ThemeData zulipThemeData(BuildContext context) { : Brightness.light; switch (brightness) { case Brightness.light: { - designVariables = DesignVariables(); + designVariables = DesignVariables.light(); themeExtensions = [ContentTheme.light(context), designVariables]; } case Brightness.dark: { - designVariables = DesignVariables(); // TODO(#95) + designVariables = DesignVariables.dark(); themeExtensions = [ContentTheme.dark(context), designVariables]; } } @@ -115,13 +115,25 @@ const kZulipBrandColor = Color.fromRGBO(0x64, 0x92, 0xfe, 1); /// For how to export these from the Figma, see: /// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=2945-49492&t=MEb4vtp7S26nntxm-0 class DesignVariables extends ThemeExtension { - DesignVariables() : - bgTopBar = const Color(0xfff5f5f5), - borderBar = const Color(0x33000000), - icon = const Color(0xff666699), - mainBackground = const Color(0xfff0f0f0), - title = const Color(0xff1a1a1a), - streamColorSwatches = StreamColorSwatches.light; + DesignVariables.light() : + this._( + bgTopBar: const Color(0xfff5f5f5), + borderBar: const Color(0x33000000), + icon: const Color(0xff666699), + mainBackground: const Color(0xfff0f0f0), + title: const Color(0xff1a1a1a), + streamColorSwatches: StreamColorSwatches.light, + ); + + DesignVariables.dark() : + this._( + bgTopBar: const Color(0xff242424), + borderBar: Colors.black.withOpacity(0.41), + icon: const Color(0xff7070c2), + mainBackground: const Color(0xff1d1d1d), + title: const Color(0xffffffff), + streamColorSwatches: StreamColorSwatches.dark, + ); DesignVariables._({ required this.bgTopBar, diff --git a/test/widgets/theme_test.dart b/test/widgets/theme_test.dart index f0591cbb1a..b6d3c98a68 100644 --- a/test/widgets/theme_test.dart +++ b/test/widgets/theme_test.dart @@ -76,14 +76,48 @@ void main() { button: TextButton(onPressed: () {}, child: const Text(buttonText))); }); + group('DesignVariables', () { + group('lerp', () { + testWidgets('light -> light', (tester) async { + final a = DesignVariables.light(); + final b = DesignVariables.light(); + check(() => a.lerp(b, 0.5)).returnsNormally(); + }); + + testWidgets('light -> dark', (tester) async { + final a = DesignVariables.light(); + final b = DesignVariables.dark(); + check(() => a.lerp(b, 0.5)).returnsNormally(); + }); + + testWidgets('dark -> light', (tester) async { + final a = DesignVariables.dark(); + final b = DesignVariables.light(); + check(() => a.lerp(b, 0.5)).returnsNormally(); + }); + + testWidgets('dark -> dark', (tester) async { + final a = DesignVariables.dark(); + final b = DesignVariables.dark(); + check(() => a.lerp(b, 0.5)).returnsNormally(); + }); + }); + }); + group('colorSwatchFor', () { const baseColor = 0xff76ce90; - testWidgets('light $baseColor', (WidgetTester tester) async { + testWidgets('light–dark animation', (WidgetTester tester) async { addTearDown(testBinding.reset); final subscription = eg.subscription(eg.stream(), color: baseColor); + assert(!debugFollowPlatformBrightness); // to be removed with #95 + debugFollowPlatformBrightness = true; + addTearDown(() { debugFollowPlatformBrightness = false; }); + tester.platformDispatcher.platformBrightnessTestValue = Brightness.light; + addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue); + await tester.pumpWidget(const ZulipApp()); await tester.pump(); @@ -96,8 +130,20 @@ void main() { // Compares all the swatch's members; see [ColorSwatch]'s `operator ==`. check(colorSwatchFor(element, subscription)) .equals(StreamColorSwatch.light(baseColor)); - }); - // TODO(#95) test with Brightness.dark and lerping between light/dark + tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + await tester.pump(); + + await tester.pump(kThemeAnimationDuration * 0.4); + check(colorSwatchFor(element, subscription)) + .equals(StreamColorSwatch.lerp( + StreamColorSwatch.light(baseColor), + StreamColorSwatch.dark(baseColor), + 0.4)!); + + await tester.pump(kThemeAnimationDuration * 0.6); + check(colorSwatchFor(element, subscription)) + .equals(StreamColorSwatch.dark(baseColor)); + }); }); }