Skip to content

Commit

Permalink
wip; store: Add global settings
Browse files Browse the repository at this point in the history
Signed-off-by: Zixuan James Li <[email protected]>
PIG208 committed Dec 18, 2024
1 parent 812b744 commit 9961f6e
Showing 7 changed files with 84 additions and 14 deletions.
40 changes: 37 additions & 3 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
@@ -51,12 +51,19 @@ 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 Iterable<Account> accounts,
required GlobalSettingsData globalSettings,
})
: _accounts = Map.fromEntries(accounts.map((a) => MapEntry(a.id, a))),
_globalSettings = globalSettings;

/// A cache of the [Accounts] table in the underlying data store.
final Map<int, Account> _accounts;

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

// TODO settings (those that are per-device rather than per-account)
// TODO push token, and other data corresponding to GlobalSessionState

@@ -223,6 +230,23 @@ abstract class GlobalStore extends ChangeNotifier {
/// Remove an account from the underlying data store.
Future<void> doRemoveAccount(int accountId);

GlobalSettingsData get globalSettings => _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);

@override
String toString() => '${objectRuntimeType(this, 'GlobalStore')}#${shortHash(this)}';
}
@@ -757,6 +781,7 @@ class LiveGlobalStore extends GlobalStore {
LiveGlobalStore._({
required AppDatabase db,
required super.accounts,
required super.globalSettings,
}) : _db = db;

@override
@@ -773,7 +798,10 @@ class LiveGlobalStore extends GlobalStore {
static Future<GlobalStore> load() async {
final db = AppDatabase(NativeDatabase.createInBackground(await _dbFile()));
final accounts = await db.select(db.accounts).get();
return LiveGlobalStore._(db: db, accounts: accounts);
final globalSettings = await db.ensureGlobalSettings();
return LiveGlobalStore._(db: db,
accounts: accounts,
globalSettings: globalSettings);
}

/// The file path to use for the app database.
@@ -835,6 +863,12 @@ class LiveGlobalStore extends GlobalStore {
assert(rowsAffected == 1);
}

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

@override
String toString() => '${objectRuntimeType(this, 'LiveGlobalStore')}#${shortHash(this)}';
}
10 changes: 7 additions & 3 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import '../generated/l10n/zulip_localizations.dart';
import '../model/avatar_url.dart';
import '../model/binding.dart';
import '../model/content.dart';
import '../model/database.dart';
import '../model/internal_link.dart';
import 'code_block.dart';
import 'dialog.dart';
@@ -1342,17 +1343,20 @@ void _launchUrl(BuildContext context, String urlString) async {
return;
}

final globalSettings = GlobalStoreWidget.of(context).globalSettings;
bool launched = false;
String? errorMessage;
try {
launched = await ZulipBinding.instance.launchUrl(url,
mode: switch (defaultTargetPlatform) {
mode: switch ((globalSettings.browserPreference, defaultTargetPlatform)) {
(BrowserPreference.embedded, _) => UrlLaunchMode.inAppBrowserView,
(BrowserPreference.external, _) => UrlLaunchMode.externalApplication,
// On iOS we prefer LaunchMode.externalApplication because (for
// HTTP URLs) LaunchMode.platformDefault uses SFSafariViewController,
// which gives an awkward UX as described here:
// https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1169118
TargetPlatform.iOS => UrlLaunchMode.externalApplication,
_ => UrlLaunchMode.platformDefault,
(BrowserPreference.unset, TargetPlatform.iOS) => UrlLaunchMode.externalApplication,
(BrowserPreference.unset, _) => UrlLaunchMode.platformDefault,
});
} on PlatformException catch (e) {
errorMessage = e.message;
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/database.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.
13 changes: 11 additions & 2 deletions test/example_data.dart
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ 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/store.dart';

@@ -805,8 +806,16 @@ ChannelUpdateEvent channelUpdateEvent(
// The entire per-account or global state.
//

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

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

InitialSnapshot initialSnapshot({
3 changes: 2 additions & 1 deletion test/model/binding.dart
Original file line number Diff line number Diff line change
@@ -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.
@@ -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;
9 changes: 6 additions & 3 deletions test/model/store_test.dart
Original file line number Diff line number Diff line change
@@ -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)
@@ -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);
@@ -1086,7 +1086,10 @@ void main() {
}

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

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

10 changes: 9 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';

@@ -22,7 +23,7 @@ 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.accounts, required super.globalSettings});

final Map<
({Uri realmUrl, int? zulipFeatureLevel, String? email, String? apiKey}),
@@ -157,6 +158,13 @@ class TestGlobalStore extends GlobalStore {
store: store, initialSnapshot: initialSnapshot);
return Future.value(store);
}

GlobalSettingsData? _globalSettings;

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

extension PerAccountStoreTestExtension on PerAccountStore {

0 comments on commit 9961f6e

Please sign in to comment.