Skip to content

Commit

Permalink
store: Track theme setting through global store
Browse files Browse the repository at this point in the history
Signed-off-by: Zixuan James Li <[email protected]>
  • Loading branch information
PIG208 committed Dec 30, 2024
1 parent a5bd6bc commit 78ddf0e
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 25 deletions.
39 changes: 36 additions & 3 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,31 @@ export 'database.dart' show Account, AccountsCompanion, AccountAlreadyExistsExce
/// * [LiveGlobalStore], the implementation of this class that
/// we use outside of tests.
abstract class GlobalStore extends ChangeNotifier {
GlobalStore({required Iterable<Account> accounts})
: _accounts = Map.fromEntries(accounts.map((a) => MapEntry(a.id, a)));
GlobalStore({
required GlobalSettingsData globalSettings,
required Iterable<Account> accounts,
})
: _globalSettings = globalSettings,
_accounts = Map.fromEntries(accounts.map((a) => MapEntry(a.id, a)));

/// A cache of the [GlobalSettingsData] singleton in the underlying data store.
GlobalSettingsData get globalSettings => _globalSettings;
GlobalSettingsData _globalSettings;

/// Update the global settings in the store, return the new version.
///
/// The global settings must already exist in the store.
Future<GlobalSettingsData> updateGlobalSettings(GlobalSettingsCompanion data) async {
await doUpdateGlobalSettings(data);
_globalSettings = _globalSettings.copyWithCompanion(data);
notifyListeners();
return _globalSettings;
}

/// Update the global settings in the underlying data store.
///
/// This should only be called from [updateGlobalSettings].
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data);

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

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

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

final AppDatabase _db;

@override
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data) async {
final rowsAffected = await _db.update(_db.globalSettings).write(data);
assert(rowsAffected == 1);
}

@override
Future<PerAccountStore> doLoadPerAccount(int accountId) async {
final updateMachine = await UpdateMachine.load(this, accountId);
Expand Down
5 changes: 3 additions & 2 deletions lib/widgets/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {

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

navigatorKey: ZulipApp.navigatorKey,
navigatorObservers: widget.navigatorObservers ?? const [],
Expand Down
13 changes: 12 additions & 1 deletion lib/widgets/theme.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import 'package:flutter/material.dart';

import '../api/model/model.dart';
import '../model/settings.dart';
import 'content.dart';
import 'emoji_reaction.dart';
import 'message_list.dart';
import 'channel_colors.dart';
import 'store.dart';
import 'text.dart';

ThemeData zulipThemeData(BuildContext context) {
final DesignVariables designVariables;
final List<ThemeExtension> themeExtensions;
Brightness brightness = MediaQuery.platformBrightnessOf(context);
final globalSettings = GlobalStoreWidget.of(context).globalSettings;
Brightness brightness;
switch (globalSettings.themeSetting) {
case ThemeSetting.unset:
brightness = MediaQuery.platformBrightnessOf(context);
case ThemeSetting.light:
brightness = Brightness.light;
case ThemeSetting.dark:
brightness = Brightness.dark;
}

// This applies Material 3's color system to produce a palette of
// appropriately matching and contrasting colors for use in a UI.
Expand Down
13 changes: 11 additions & 2 deletions test/example_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import 'package:zulip/api/model/submessage.dart';
import 'package:zulip/api/route/messages.dart';
import 'package:zulip/api/route/realm.dart';
import 'package:zulip/api/route/channels.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/model/narrow.dart';
import 'package:zulip/model/settings.dart';
import 'package:zulip/model/store.dart';

import 'model/test_store.dart';
Expand Down Expand Up @@ -805,8 +807,15 @@ ChannelUpdateEvent channelUpdateEvent(
// The entire per-account or global state.
//

TestGlobalStore globalStore({List<Account> accounts = const []}) {
return TestGlobalStore(accounts: accounts);
const defaultGlobalSettings = GlobalSettingsData(
themeSetting: ThemeSetting.unset,
);

TestGlobalStore globalStore({
GlobalSettingsData globalSettings = defaultGlobalSettings,
List<Account> accounts = const [],
}) {
return TestGlobalStore(globalSettings: globalSettings, accounts: accounts);
}

InitialSnapshot initialSnapshot({
Expand Down
3 changes: 2 additions & 1 deletion test/model/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:zulip/model/binding.dart';
import 'package:zulip/model/store.dart';
import 'package:zulip/widgets/app.dart';

import '../example_data.dart' as eg;
import 'test_store.dart';

/// The binding instance used in tests.
Expand Down Expand Up @@ -85,7 +86,7 @@ class TestZulipBinding extends ZulipBinding {
///
/// Tests that access this getter, or that mount a [GlobalStoreWidget],
/// should clean up by calling [reset].
TestGlobalStore get globalStore => _globalStore ??= TestGlobalStore(accounts: []);
TestGlobalStore get globalStore => _globalStore ??= eg.globalStore();
TestGlobalStore? _globalStore;

bool _debugAlreadyLoadedStore = false;
Expand Down
9 changes: 6 additions & 3 deletions test/model/store_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ void main() {
late FakeApiConnection connection;

Future<void> prepareStore({Account? account}) async {
globalStore = TestGlobalStore(accounts: []);
globalStore = eg.globalStore();
account ??= eg.selfAccount;
await globalStore.insertAccount(account.toCompanion(false));
connection = (globalStore.apiConnectionFromAccount(account)
Expand Down Expand Up @@ -581,7 +581,7 @@ void main() {
}

Future<void> preparePoll({int? lastEventId}) async {
globalStore = TestGlobalStore(accounts: []);
globalStore = eg.globalStore();
await globalStore.add(eg.selfAccount, eg.initialSnapshot(
lastEventId: lastEventId));
await globalStore.perAccount(eg.selfAccount.id);
Expand Down Expand Up @@ -1086,7 +1086,10 @@ void main() {
}

class LoadingTestGlobalStore extends TestGlobalStore {
LoadingTestGlobalStore({required super.accounts});
LoadingTestGlobalStore({
super.globalSettings = eg.defaultGlobalSettings,
required super.accounts,
});

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

Expand Down
11 changes: 10 additions & 1 deletion test/model/test_store.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:zulip/api/model/events.dart';
import 'package:zulip/api/model/initial_snapshot.dart';
import 'package:zulip/api/model/model.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/model/store.dart';
import 'package:zulip/widgets/store.dart';

Expand All @@ -22,7 +23,15 @@ import '../example_data.dart' as eg;
///
/// See also [TestZulipBinding.globalStore], which provides one of these.
class TestGlobalStore extends GlobalStore {
TestGlobalStore({required super.accounts});
TestGlobalStore({required super.globalSettings, required super.accounts})
: _globalSettings = globalSettings;

GlobalSettingsData? _globalSettings;

@override
Future<void> doUpdateGlobalSettings(GlobalSettingsCompanion data) async {
_globalSettings = _globalSettings!.copyWithCompanion(data);
}

final Map<
({Uri realmUrl, int? zulipFeatureLevel, String? email, String? apiKey}),
Expand Down
29 changes: 17 additions & 12 deletions test/widgets/test_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,22 @@ class TestZulipApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GlobalStoreWidget(
child: MaterialApp(
title: 'Zulip',
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
supportedLocales: ZulipLocalizations.supportedLocales,
theme: zulipThemeData(context),

navigatorObservers: navigatorObservers ?? const [],

home: accountId != null
? PerAccountStoreWidget(accountId: accountId!, child: child)
: child,
));
child: Builder(
builder: (context) {
return MaterialApp(
title: 'Zulip',
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
supportedLocales: ZulipLocalizations.supportedLocales,
// The context has to be taken from the [Builder] because
// [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
theme: zulipThemeData(context),

navigatorObservers: navigatorObservers ?? const [],

home: accountId != null
? PerAccountStoreWidget(accountId: accountId!, child: child)
: child
);
}));
}
}
23 changes: 23 additions & 0 deletions test/widgets/theme_test.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'package:checks/checks.dart';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/model/settings.dart';
import 'package:zulip/widgets/channel_colors.dart';
import 'package:zulip/widgets/store.dart';
import 'package:zulip/widgets/text.dart';
import 'package:zulip/widgets/theme.dart';

Expand Down Expand Up @@ -97,6 +101,25 @@ void main() {
check(() => a.lerp(b, 0.5)).returnsNormally();
});
});

testWidgets('follow globalSettings.themeSetting if not unset', (tester) async {
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue);

await tester.pumpWidget(const TestZulipApp());
await tester.pump();

final element = tester.element(find.byType(Placeholder));
check(zulipThemeData(element).brightness).equals(Brightness.light);

await GlobalStoreWidget.of(element).updateGlobalSettings(
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.dark)));
check(zulipThemeData(element).brightness).equals(Brightness.dark);

await GlobalStoreWidget.of(element).updateGlobalSettings(
const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.unset)));
check(zulipThemeData(element).brightness).equals(Brightness.light);
});
});

group('colorSwatchFor', () {
Expand Down

0 comments on commit 78ddf0e

Please sign in to comment.