Skip to content

Commit 5c33240

Browse files
committed
theme: Track theme through global settings
Signed-off-by: Zixuan James Li <[email protected]>
1 parent 3bb9ef1 commit 5c33240

File tree

4 files changed

+81
-35
lines changed

4 files changed

+81
-35
lines changed

lib/widgets/app.dart

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -212,41 +212,48 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
212212

213213
@override
214214
Widget build(BuildContext context) {
215-
final themeData = zulipThemeData(context);
216215
return GlobalStoreWidget(
217-
child: MaterialApp(
218-
onGenerateTitle: (BuildContext context) {
219-
return ZulipLocalizations.of(context).zulipAppTitle;
220-
},
221-
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
222-
supportedLocales: ZulipLocalizations.supportedLocales,
223-
theme: themeData,
224-
225-
navigatorKey: ZulipApp.navigatorKey,
226-
navigatorObservers: [
227-
if (widget.navigatorObservers != null)
228-
...widget.navigatorObservers!,
229-
_PreventEmptyStack(),
230-
],
231-
builder: (BuildContext context, Widget? child) {
232-
if (!ZulipApp.ready.value) {
233-
SchedulerBinding.instance.addPostFrameCallback(
234-
(_) => widget._declareReady());
235-
}
236-
GlobalLocalizations.zulipLocalizations = ZulipLocalizations.of(context);
237-
return child!;
238-
},
239-
240-
// We use onGenerateInitialRoutes for the real work of specifying the
241-
// initial nav state. To do that we need [MaterialApp] to decide to
242-
// build a [Navigator]... which means specifying either `home`, `routes`,
243-
// `onGenerateRoute`, or `onUnknownRoute`. Make it `onGenerateRoute`.
244-
// It never actually gets called, though: `onGenerateInitialRoutes`
245-
// handles startup, and then we always push whole routes with methods
246-
// like [Navigator.push], never mere names as with [Navigator.pushNamed].
247-
onGenerateRoute: (_) => null,
248-
249-
onGenerateInitialRoutes: _handleGenerateInitialRoutes));
216+
child: Builder(
217+
builder: (context) {
218+
return MaterialApp(
219+
onGenerateTitle: (BuildContext context) {
220+
return ZulipLocalizations.of(context).zulipAppTitle;
221+
},
222+
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
223+
supportedLocales: ZulipLocalizations.supportedLocales,
224+
// The context has to be taken from the [Builder] because
225+
// [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
226+
// TODO: any way to remove the [Builder], like how we pulled out
227+
// [_handleGenerateInitialRoutes]?
228+
theme: zulipThemeData(context),
229+
230+
navigatorKey: ZulipApp.navigatorKey,
231+
navigatorObservers: [
232+
if (widget.navigatorObservers != null)
233+
...widget.navigatorObservers!,
234+
_PreventEmptyStack(),
235+
],
236+
builder: (BuildContext context, Widget? child) {
237+
if (!ZulipApp.ready.value) {
238+
SchedulerBinding.instance.addPostFrameCallback(
239+
(_) => widget._declareReady());
240+
}
241+
GlobalLocalizations.zulipLocalizations = ZulipLocalizations.of(context);
242+
return child!;
243+
},
244+
245+
// We use onGenerateInitialRoutes for the real work of specifying the
246+
// initial nav state. To do that we need [MaterialApp] to decide to
247+
// build a [Navigator]... which means specifying either `home`, `routes`,
248+
// `onGenerateRoute`, or `onUnknownRoute`. Make it `onGenerateRoute`.
249+
// It never actually gets called, though: `onGenerateInitialRoutes`
250+
// handles startup, and then we always push whole routes with methods
251+
// like [Navigator.push], never mere names as with [Navigator.pushNamed].
252+
onGenerateRoute: (_) => null,
253+
254+
onGenerateInitialRoutes: _handleGenerateInitialRoutes);
255+
}
256+
));
250257
}
251258
}
252259

lib/widgets/theme.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
import 'package:flutter/material.dart';
22

33
import '../api/model/model.dart';
4+
import '../model/settings.dart';
45
import 'compose_box.dart';
56
import 'content.dart';
67
import 'emoji_reaction.dart';
78
import 'message_list.dart';
89
import 'channel_colors.dart';
10+
import 'store.dart';
911
import 'text.dart';
1012

1113
ThemeData zulipThemeData(BuildContext context) {
1214
final DesignVariables designVariables;
1315
final List<ThemeExtension> themeExtensions;
14-
Brightness brightness = MediaQuery.platformBrightnessOf(context);
16+
final globalSettings = GlobalStoreWidget.of(context).globalSettings;
17+
Brightness brightness;
18+
switch (globalSettings.themeSetting) {
19+
case ThemeSetting.unset:
20+
brightness = MediaQuery.platformBrightnessOf(context);
21+
case ThemeSetting.light:
22+
brightness = Brightness.light;
23+
case ThemeSetting.dark:
24+
brightness = Brightness.dark;
25+
}
1526

1627
// This applies Material 3's color system to produce a palette of
1728
// appropriately matching and contrasting colors for use in a UI.

test/flutter_checks.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ extension ValueListenableChecks<T> on Subject<ValueListenable<T>> {
6161
Subject<T> get value => has((c) => c.value, 'value');
6262
}
6363

64+
extension ThemeDataChecks on Subject<ThemeData> {
65+
Subject<Brightness> get brightness => has((x) => x.brightness, 'brightness');
66+
}
67+
6468
extension TextChecks on Subject<Text> {
6569
Subject<String?> get data => has((t) => t.data, 'data');
6670
Subject<TextStyle?> get style => has((t) => t.style, 'style');

test/widgets/theme_test.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'package:checks/checks.dart';
2+
import 'package:drift/drift.dart';
23
import 'package:flutter/material.dart';
34
import 'package:flutter/rendering.dart';
45
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:zulip/model/database.dart';
7+
import 'package:zulip/model/settings.dart';
58
import 'package:zulip/widgets/channel_colors.dart';
69
import 'package:zulip/widgets/text.dart';
710
import 'package:zulip/widgets/theme.dart';
@@ -97,6 +100,27 @@ void main() {
97100
check(() => a.lerp(b, 0.5)).returnsNormally();
98101
});
99102
});
103+
104+
testWidgets('follow globalSettings.themeSetting if not unset', (tester) async {
105+
addTearDown(testBinding.reset);
106+
107+
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
108+
addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue);
109+
110+
await tester.pumpWidget(const TestZulipApp(child: Placeholder()));
111+
await tester.pump();
112+
113+
final element = tester.element(find.byType(Placeholder));
114+
check(zulipThemeData(element)).brightness.equals(Brightness.light);
115+
116+
await testBinding.globalStore.updateGlobalSettings(
117+
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.dark)));
118+
check(zulipThemeData(element)).brightness.equals(Brightness.dark);
119+
120+
await testBinding.globalStore.updateGlobalSettings(
121+
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.unset)));
122+
check(zulipThemeData(element)).brightness.equals(Brightness.light);
123+
});
100124
});
101125

102126
group('colorSwatchFor', () {

0 commit comments

Comments
 (0)