Skip to content

Commit 4d37bb2

Browse files
committed
store: Track theme setting through global store
Signed-off-by: Zixuan James Li <[email protected]>
1 parent 9bdd810 commit 4d37bb2

File tree

9 files changed

+122
-25
lines changed

9 files changed

+122
-25
lines changed

lib/model/store.dart

+36-3
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,31 @@ export 'database.dart' show Account, AccountsCompanion, AccountAlreadyExistsExce
5151
/// * [LiveGlobalStore], the implementation of this class that
5252
/// we use outside of tests.
5353
abstract class GlobalStore extends ChangeNotifier {
54-
GlobalStore({required Iterable<Account> accounts})
55-
: _accounts = Map.fromEntries(accounts.map((a) => MapEntry(a.id, a)));
54+
GlobalStore({
55+
required GlobalSettingsData globalSettings,
56+
required Iterable<Account> accounts,
57+
})
58+
: _globalSettings = globalSettings,
59+
_accounts = Map.fromEntries(accounts.map((a) => MapEntry(a.id, a)));
60+
61+
/// A cache of the [GlobalSettingsData] singleton in the underlying data store.
62+
GlobalSettingsData get globalSettings => _globalSettings;
63+
GlobalSettingsData _globalSettings;
64+
65+
/// Update the global settings in the store, return the new version.
66+
///
67+
/// The global settings must already exist in the store.
68+
Future<GlobalSettingsData> updateGlobalSettings(GlobalSettingsCompanion data) async {
69+
await doUpdateGlobalSettings(data);
70+
_globalSettings = _globalSettings.copyWithCompanion(data);
71+
notifyListeners();
72+
return _globalSettings;
73+
}
74+
75+
/// Update the global settings in the underlying data store.
76+
///
77+
/// This should only be called from [updateGlobalSettings].
78+
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data);
5679

5780
/// A cache of the [Accounts] table in the underlying data store.
5881
final Map<int, Account> _accounts;
@@ -756,6 +779,7 @@ Uri? tryResolveUrl(Uri baseUrl, String reference) {
756779
class LiveGlobalStore extends GlobalStore {
757780
LiveGlobalStore._({
758781
required AppDatabase db,
782+
required super.globalSettings,
759783
required super.accounts,
760784
}) : _db = db;
761785

@@ -772,8 +796,11 @@ class LiveGlobalStore extends GlobalStore {
772796
// by doing this loading up front before constructing a [GlobalStore].
773797
static Future<GlobalStore> load() async {
774798
final db = AppDatabase(NativeDatabase.createInBackground(await _dbFile()));
799+
final globalSettings = await db.ensureGlobalSettings();
775800
final accounts = await db.select(db.accounts).get();
776-
return LiveGlobalStore._(db: db, accounts: accounts);
801+
return LiveGlobalStore._(db: db,
802+
globalSettings: globalSettings,
803+
accounts: accounts);
777804
}
778805

779806
/// The file path to use for the app database.
@@ -800,6 +827,12 @@ class LiveGlobalStore extends GlobalStore {
800827

801828
final AppDatabase _db;
802829

830+
@override
831+
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data) async {
832+
final rowsAffected = await _db.update(_db.globalSettings).write(data);
833+
assert(rowsAffected == 1);
834+
}
835+
803836
@override
804837
Future<PerAccountStore> doLoadPerAccount(int accountId) async {
805838
final updateMachine = await UpdateMachine.load(this, accountId);

lib/widgets/app.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
174174

175175
@override
176176
Widget build(BuildContext context) {
177-
final themeData = zulipThemeData(context);
178177
return GlobalStoreWidget(
179178
child: Builder(builder: (context) {
180179
final globalStore = GlobalStoreWidget.of(context);
@@ -184,7 +183,9 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
184183
title: 'Zulip',
185184
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
186185
supportedLocales: ZulipLocalizations.supportedLocales,
187-
theme: themeData,
186+
// The context has to be taken from the [Builder] because
187+
// [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
188+
theme: zulipThemeData(context),
188189

189190
navigatorKey: ZulipApp.navigatorKey,
190191
navigatorObservers: widget.navigatorObservers ?? const [],

lib/widgets/theme.dart

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import 'package:flutter/material.dart';
22

33
import '../api/model/model.dart';
4+
import '../model/settings.dart';
45
import 'content.dart';
56
import 'emoji_reaction.dart';
67
import 'message_list.dart';
78
import 'channel_colors.dart';
9+
import 'store.dart';
810
import 'text.dart';
911

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

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

test/example_data.dart

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import 'package:zulip/api/model/submessage.dart';
88
import 'package:zulip/api/route/messages.dart';
99
import 'package:zulip/api/route/realm.dart';
1010
import 'package:zulip/api/route/channels.dart';
11+
import 'package:zulip/model/database.dart';
1112
import 'package:zulip/model/narrow.dart';
13+
import 'package:zulip/model/settings.dart';
1214
import 'package:zulip/model/store.dart';
1315

1416
import 'model/test_store.dart';
@@ -833,8 +835,15 @@ ChannelUpdateEvent channelUpdateEvent(
833835
// The entire per-account or global state.
834836
//
835837

836-
TestGlobalStore globalStore({List<Account> accounts = const []}) {
837-
return TestGlobalStore(accounts: accounts);
838+
const defaultGlobalSettings = GlobalSettingsData(
839+
themeSetting: ThemeSetting.unset,
840+
);
841+
842+
TestGlobalStore globalStore({
843+
GlobalSettingsData globalSettings = defaultGlobalSettings,
844+
List<Account> accounts = const [],
845+
}) {
846+
return TestGlobalStore(globalSettings: globalSettings, accounts: accounts);
838847
}
839848

840849
InitialSnapshot initialSnapshot({

test/model/binding.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:zulip/model/binding.dart';
1212
import 'package:zulip/model/store.dart';
1313
import 'package:zulip/widgets/app.dart';
1414

15+
import '../example_data.dart' as eg;
1516
import 'test_store.dart';
1617

1718
/// The binding instance used in tests.
@@ -86,7 +87,7 @@ class TestZulipBinding extends ZulipBinding {
8687
///
8788
/// Tests that access this getter, or that mount a [GlobalStoreWidget],
8889
/// should clean up by calling [reset].
89-
TestGlobalStore get globalStore => _globalStore ??= TestGlobalStore(accounts: []);
90+
TestGlobalStore get globalStore => _globalStore ??= eg.globalStore();
9091
TestGlobalStore? _globalStore;
9192

9293
bool _debugAlreadyLoadedStore = false;

test/model/store_test.dart

+6-3
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ void main() {
406406
late FakeApiConnection connection;
407407

408408
Future<void> prepareStore({Account? account}) async {
409-
globalStore = TestGlobalStore(accounts: []);
409+
globalStore = eg.globalStore();
410410
account ??= eg.selfAccount;
411411
await globalStore.insertAccount(account.toCompanion(false));
412412
connection = (globalStore.apiConnectionFromAccount(account)
@@ -581,7 +581,7 @@ void main() {
581581
}
582582

583583
Future<void> preparePoll({int? lastEventId}) async {
584-
globalStore = TestGlobalStore(accounts: []);
584+
globalStore = eg.globalStore();
585585
await globalStore.add(eg.selfAccount, eg.initialSnapshot(
586586
lastEventId: lastEventId));
587587
await globalStore.perAccount(eg.selfAccount.id);
@@ -1086,7 +1086,10 @@ void main() {
10861086
}
10871087

10881088
class LoadingTestGlobalStore extends TestGlobalStore {
1089-
LoadingTestGlobalStore({required super.accounts});
1089+
LoadingTestGlobalStore({
1090+
super.globalSettings = eg.defaultGlobalSettings,
1091+
required super.accounts,
1092+
});
10901093

10911094
Map<int, List<Completer<PerAccountStore>>> completers = {};
10921095

test/model/test_store.dart

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:zulip/api/model/events.dart';
22
import 'package:zulip/api/model/initial_snapshot.dart';
33
import 'package:zulip/api/model/model.dart';
4+
import 'package:zulip/model/database.dart';
45
import 'package:zulip/model/store.dart';
56
import 'package:zulip/widgets/store.dart';
67

@@ -22,7 +23,15 @@ import '../example_data.dart' as eg;
2223
///
2324
/// See also [TestZulipBinding.globalStore], which provides one of these.
2425
class TestGlobalStore extends GlobalStore {
25-
TestGlobalStore({required super.accounts});
26+
TestGlobalStore({required super.globalSettings, required super.accounts})
27+
: _globalSettings = globalSettings;
28+
29+
GlobalSettingsData? _globalSettings;
30+
31+
@override
32+
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data) async {
33+
_globalSettings = _globalSettings!.copyWithCompanion(data);
34+
}
2635

2736
final Map<
2837
({Uri realmUrl, int? zulipFeatureLevel, String? email, String? apiKey}),

test/widgets/test_app.dart

+17-12
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,22 @@ class TestZulipApp extends StatelessWidget {
2828
@override
2929
Widget build(BuildContext context) {
3030
return GlobalStoreWidget(
31-
child: MaterialApp(
32-
title: 'Zulip',
33-
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
34-
supportedLocales: ZulipLocalizations.supportedLocales,
35-
theme: zulipThemeData(context),
36-
37-
navigatorObservers: navigatorObservers ?? const [],
38-
39-
home: accountId != null
40-
? PerAccountStoreWidget(accountId: accountId!, child: child)
41-
: child,
42-
));
31+
child: Builder(
32+
builder: (context) {
33+
return MaterialApp(
34+
title: 'Zulip',
35+
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
36+
supportedLocales: ZulipLocalizations.supportedLocales,
37+
// The context has to be taken from the [Builder] because
38+
// [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
39+
theme: zulipThemeData(context),
40+
41+
navigatorObservers: navigatorObservers ?? const [],
42+
43+
home: accountId != null
44+
? PerAccountStoreWidget(accountId: accountId!, child: child)
45+
: child
46+
);
47+
}));
4348
}
4449
}

test/widgets/theme_test.dart

+25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
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';
9+
import 'package:zulip/widgets/store.dart';
610
import 'package:zulip/widgets/text.dart';
711
import 'package:zulip/widgets/theme.dart';
812

@@ -97,6 +101,27 @@ void main() {
97101
check(() => a.lerp(b, 0.5)).returnsNormally();
98102
});
99103
});
104+
105+
testWidgets('follow globalSettings.themeSetting if not unset', (tester) async {
106+
addTearDown(testBinding.reset);
107+
108+
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
109+
addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue);
110+
111+
await tester.pumpWidget(const TestZulipApp());
112+
await tester.pump();
113+
114+
final element = tester.element(find.byType(Placeholder));
115+
check(zulipThemeData(element).brightness).equals(Brightness.light);
116+
117+
await GlobalStoreWidget.of(element).updateGlobalSettings(
118+
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.dark)));
119+
check(zulipThemeData(element).brightness).equals(Brightness.dark);
120+
121+
await GlobalStoreWidget.of(element).updateGlobalSettings(
122+
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.unset)));
123+
check(zulipThemeData(element).brightness).equals(Brightness.light);
124+
});
100125
});
101126

102127
group('colorSwatchFor', () {

0 commit comments

Comments
 (0)