From c734e00667bd2544336933a5f4e8a2365f5b4d8c Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:33 +0200 Subject: [PATCH 01/10] refactor(neon): make SettingsStorage and AppStorage internal Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/settings.dart | 1 - packages/neon/neon/lib/src/settings/models/storage.dart | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/neon/neon/lib/settings.dart b/packages/neon/neon/lib/settings.dart index 08c0053e..8a1e4d8e 100644 --- a/packages/neon/neon/lib/settings.dart +++ b/packages/neon/neon/lib/settings.dart @@ -2,6 +2,5 @@ export 'package:neon/src/models/label_builder.dart'; export 'package:neon/src/settings/models/nextcloud_app_options.dart'; export 'package:neon/src/settings/models/options_category.dart'; export 'package:neon/src/settings/models/select_option.dart'; -export 'package:neon/src/settings/models/storage.dart'; export 'package:neon/src/settings/models/toggle_option.dart'; export 'package:neon/src/settings/widgets/settings_list.dart'; diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index d033964d..ab3ec136 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -1,6 +1,7 @@ import 'package:meta/meta.dart'; import 'package:shared_preferences/shared_preferences.dart'; +@internal abstract interface class SettingsStorage { String? getString(final String key); @@ -14,6 +15,8 @@ abstract interface class SettingsStorage { Future remove(final String key); } +@immutable +@internal class AppStorage implements SettingsStorage { AppStorage(this._id); From 9321f1f6d71393b13d5e7b21b90edeaae828b2ff Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:33 +0200 Subject: [PATCH 02/10] feat(neon): make AppStorage constant Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/src/blocs/accounts.dart | 2 +- packages/neon/neon/lib/src/blocs/first_launch.dart | 2 +- packages/neon/neon/lib/src/blocs/push_notifications.dart | 2 +- packages/neon/neon/lib/src/settings/models/storage.dart | 2 +- packages/neon/neon/lib/src/utils/global_options.dart | 2 +- packages/neon/neon/lib/src/utils/push_utils.dart | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index 10605cea..76a93fe5 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -94,7 +94,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState } } - late final AppStorage _storage = AppStorage('accounts'); + late final AppStorage _storage = const AppStorage('accounts'); final GlobalOptions _globalOptions; final Iterable _allAppImplementations; final _keyLastUsedAccount = 'last-used-account'; diff --git a/packages/neon/neon/lib/src/blocs/first_launch.dart b/packages/neon/neon/lib/src/blocs/first_launch.dart index fef5589c..6879ac22 100644 --- a/packages/neon/neon/lib/src/blocs/first_launch.dart +++ b/packages/neon/neon/lib/src/blocs/first_launch.dart @@ -16,7 +16,7 @@ abstract class FirstLaunchBlocStates { class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunchBlocStates { FirstLaunchBloc({ final bool disabled = false, - }) : _storage = AppStorage(_keyFirstLaunch) { + }) : _storage = const AppStorage(_keyFirstLaunch) { if (!disabled && !_storage.containsKey(_keyFirstLaunch)) { onFirstLaunch.add(null); unawaited(_storage.setBool(_keyFirstLaunch, false)); diff --git a/packages/neon/neon/lib/src/blocs/push_notifications.dart b/packages/neon/neon/lib/src/blocs/push_notifications.dart index c25a71f1..7e3c6698 100644 --- a/packages/neon/neon/lib/src/blocs/push_notifications.dart +++ b/packages/neon/neon/lib/src/blocs/push_notifications.dart @@ -37,7 +37,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, } final AccountsBloc _accountsBloc; - late final _storage = AppStorage(AppIDs.notifications); + late final _storage = const AppStorage(AppIDs.notifications); final GlobalOptions _globalOptions; final _notificationsController = StreamController(); diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index ab3ec136..2169c182 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -18,7 +18,7 @@ abstract interface class SettingsStorage { @immutable @internal class AppStorage implements SettingsStorage { - AppStorage(this._id); + const AppStorage(this._id); final String _id; diff --git a/packages/neon/neon/lib/src/utils/global_options.dart b/packages/neon/neon/lib/src/utils/global_options.dart index d6b319f1..9319b927 100644 --- a/packages/neon/neon/lib/src/utils/global_options.dart +++ b/packages/neon/neon/lib/src/utils/global_options.dart @@ -49,7 +49,7 @@ class GlobalOptions { } } - late final AppStorage _storage = AppStorage('global'); + late final AppStorage _storage = const AppStorage('global'); final PackageInfo _packageInfo; late final _distributorsMap = { diff --git a/packages/neon/neon/lib/src/utils/push_utils.dart b/packages/neon/neon/lib/src/utils/push_utils.dart index 1588dbce..b3bce24f 100644 --- a/packages/neon/neon/lib/src/utils/push_utils.dart +++ b/packages/neon/neon/lib/src/utils/push_utils.dart @@ -72,7 +72,7 @@ class PushUtils { ); await AppStorage.init(); - final keypair = await loadRSAKeypair(AppStorage(AppIDs.notifications)); + final keypair = await loadRSAKeypair(const AppStorage(AppIDs.notifications)); for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { final data = json.decode(message) as Map; @@ -98,7 +98,7 @@ class PushUtils { NotificationsNotification? notification; AndroidBitmap? largeIconBitmap; try { - accounts = loadAccounts(AppStorage('accounts')); + accounts = loadAccounts(const AppStorage('accounts')); account = accounts.tryFind(instance); if (account != null) { notification = From b272c0fbf55098e07cb794163872973b620b578b Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:33 +0200 Subject: [PATCH 03/10] feat(neon): AppStorage skip initialization if already initialized Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/src/settings/models/storage.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 2169c182..349845ab 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -32,6 +32,10 @@ class AppStorage implements SettingsStorage { /// /// Required to be called before accessing [reqireDatabase]. static Future init() async { + if (_sharedPreferences != null) { + return; + } + _sharedPreferences = await SharedPreferences.getInstance(); } From 28cc5f0c5c0a438bd24e00dafc7450a8d12a9912 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:33 +0200 Subject: [PATCH 04/10] refactor(neon): externalize NeonStorage Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/neon.dart | 2 +- .../neon/lib/src/settings/models/storage.dart | 39 ++++++++++--------- .../neon/neon/lib/src/utils/push_utils.dart | 2 +- packages/neon/neon/test/storage_test.dart | 6 +-- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/neon/neon/lib/neon.dart b/packages/neon/neon/lib/neon.dart index 8b23e1b6..62d4e126 100644 --- a/packages/neon/neon/lib/neon.dart +++ b/packages/neon/neon/lib/neon.dart @@ -31,7 +31,7 @@ Future runNeon({ await NeonPlatform.setup(); await RequestManager.instance.initCache(); - await AppStorage.init(); + await NeonStorage.init(); final packageInfo = await PackageInfo.fromPlatform(); buildUserAgent(packageInfo); diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 349845ab..885b4e67 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -15,22 +15,17 @@ abstract interface class SettingsStorage { Future remove(final String key); } -@immutable @internal -class AppStorage implements SettingsStorage { - const AppStorage(this._id); - - final String _id; - +final class NeonStorage { /// Shared preferences instance. /// - /// Use [reqireDatabase] to access it. + /// Use [database] to access it. /// Make sure it has been initialized wiht [init] before. static SharedPreferences? _sharedPreferences; /// Sets up the [SharedPreferences] instance. /// - /// Required to be called before accessing [reqireDatabase]. + /// Required to be called before accessing [database]. static Future init() async { if (_sharedPreferences != null) { return; @@ -40,37 +35,45 @@ class AppStorage implements SettingsStorage { } @visibleForTesting - static SharedPreferences get reqireDatabase { + static SharedPreferences get database { if (_sharedPreferences == null) { throw StateError( - 'AppStorage has not been initialized yet. Please make sure AppStorage.init() has been called before and completed.', + 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', ); } return _sharedPreferences!; } +} + +@immutable +@internal +class AppStorage implements SettingsStorage { + const AppStorage(this._id); + + final String _id; String _formatKey(final String key) => '$_id-$key'; - bool containsKey(final String key) => reqireDatabase.containsKey(_formatKey(key)); + bool containsKey(final String key) => NeonStorage.database.containsKey(_formatKey(key)); @override - Future remove(final String key) => reqireDatabase.remove(_formatKey(key)); + Future remove(final String key) => NeonStorage.database.remove(_formatKey(key)); @override - String? getString(final String key) => reqireDatabase.getString(_formatKey(key)); + String? getString(final String key) => NeonStorage.database.getString(_formatKey(key)); @override - Future setString(final String key, final String value) => reqireDatabase.setString(_formatKey(key), value); + Future setString(final String key, final String value) => NeonStorage.database.setString(_formatKey(key), value); @override - bool? getBool(final String key) => reqireDatabase.getBool(_formatKey(key)); + bool? getBool(final String key) => NeonStorage.database.getBool(_formatKey(key)); @override - Future setBool(final String key, final bool value) => reqireDatabase.setBool(_formatKey(key), value); + Future setBool(final String key, final bool value) => NeonStorage.database.setBool(_formatKey(key), value); - List? getStringList(final String key) => reqireDatabase.getStringList(_formatKey(key)); + List? getStringList(final String key) => NeonStorage.database.getStringList(_formatKey(key)); Future setStringList(final String key, final List value) => - reqireDatabase.setStringList(_formatKey(key), value); + NeonStorage.database.setStringList(_formatKey(key), value); } diff --git a/packages/neon/neon/lib/src/utils/push_utils.dart b/packages/neon/neon/lib/src/utils/push_utils.dart index b3bce24f..d119e5c2 100644 --- a/packages/neon/neon/lib/src/utils/push_utils.dart +++ b/packages/neon/neon/lib/src/utils/push_utils.dart @@ -70,7 +70,7 @@ class PushUtils { } }, ); - await AppStorage.init(); + await NeonStorage.init(); final keypair = await loadRSAKeypair(const AppStorage(AppIDs.notifications)); diff --git a/packages/neon/neon/test/storage_test.dart b/packages/neon/neon/test/storage_test.dart index bda8df24..d88f8aea 100644 --- a/packages/neon/neon/test/storage_test.dart +++ b/packages/neon/neon/test/storage_test.dart @@ -4,11 +4,11 @@ import 'package:test/test.dart'; void main() { test('AppStorage', () async { - expect(() => AppStorage.reqireDatabase, throwsA(isA())); + expect(() => NeonStorage.database, throwsA(isA())); SharedPreferences.setMockInitialValues({}); - await AppStorage.init(); + await NeonStorage.init(); - expect(AppStorage.reqireDatabase, isA()); + expect(NeonStorage.database, isA()); }); } From 91a904df0d26c9ee614ae0cfdf04a4c3c96a73b6 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:34 +0200 Subject: [PATCH 05/10] feat(neon): add SingleValueStorage Signed-off-by: Nikolas Rimikis --- .../neon/neon/lib/src/blocs/accounts.dart | 11 ++++---- .../neon/neon/lib/src/blocs/first_launch.dart | 11 ++++---- .../neon/lib/src/settings/models/storage.dart | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index 76a93fe5..ed251c51 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -58,6 +58,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState this._globalOptions, this._allAppImplementations, ) { + const lastUsedStorage = SingleValueStorage('last-used-account'); + accounts ..add(loadAccounts(_storage)) ..listen((final as) async { @@ -66,16 +68,16 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState }); activeAccount.listen((final aa) async { if (aa != null) { - await _storage.setString(_keyLastUsedAccount, aa.id); + await lastUsedStorage.setString(aa.id); } else { - await _storage.remove(_keyLastUsedAccount); + await lastUsedStorage.remove(); } }); final as = accounts.value; - if (_globalOptions.rememberLastUsedAccount.value && _storage.containsKey(_keyLastUsedAccount)) { - final lastUsedAccountID = _storage.getString(_keyLastUsedAccount); + if (_globalOptions.rememberLastUsedAccount.value && lastUsedStorage.hasValue()) { + final lastUsedAccountID = lastUsedStorage.getString(); if (lastUsedAccountID != null) { final aa = as.tryFind(lastUsedAccountID); if (aa != null) { @@ -97,7 +99,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState late final AppStorage _storage = const AppStorage('accounts'); final GlobalOptions _globalOptions; final Iterable _allAppImplementations; - final _keyLastUsedAccount = 'last-used-account'; final _accountsOptions = {}; final _appsBlocs = {}; diff --git a/packages/neon/neon/lib/src/blocs/first_launch.dart b/packages/neon/neon/lib/src/blocs/first_launch.dart index 6879ac22..bcf83f8b 100644 --- a/packages/neon/neon/lib/src/blocs/first_launch.dart +++ b/packages/neon/neon/lib/src/blocs/first_launch.dart @@ -16,16 +16,15 @@ abstract class FirstLaunchBlocStates { class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunchBlocStates { FirstLaunchBloc({ final bool disabled = false, - }) : _storage = const AppStorage(_keyFirstLaunch) { - if (!disabled && !_storage.containsKey(_keyFirstLaunch)) { + }) { + const storage = SingleValueStorage('first-launch'); + + if (!disabled && !storage.hasValue()) { onFirstLaunch.add(null); - unawaited(_storage.setBool(_keyFirstLaunch, false)); + unawaited(storage.setBool(false)); } } - final AppStorage _storage; - static const _keyFirstLaunch = 'first-launch'; - @override void dispose() { unawaited(onFirstLaunch.close()); diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 885b4e67..399af196 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -46,6 +46,31 @@ final class NeonStorage { } } +@immutable +@internal +final class SingleValueStorage { + const SingleValueStorage(this._id); + + final String _id; + + bool hasValue() => NeonStorage.database.containsKey(_id); + + Future remove() => NeonStorage.database.remove(_id); + + String? getString() => NeonStorage.database.getString(_id); + + Future setString(final String value) => NeonStorage.database.setString(_id, value); + + bool? getBool() => NeonStorage.database.getBool(_id); + + // ignore: avoid_positional_boolean_parameters + Future setBool(final bool value) => NeonStorage.database.setBool(_id, value); + + List? getStringList() => NeonStorage.database.getStringList(_id); + + Future setStringList(final List value) => NeonStorage.database.setStringList(_id, value); +} + @immutable @internal class AppStorage implements SettingsStorage { From 51f334c4bfbcd012dc0e93d4f56702ec3d33a36a Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:34 +0200 Subject: [PATCH 06/10] feat(neon): make AppStorage final Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/src/settings/models/storage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 399af196..ab4fc105 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -73,7 +73,7 @@ final class SingleValueStorage { @immutable @internal -class AppStorage implements SettingsStorage { +final class AppStorage implements SettingsStorage { const AppStorage(this._id); final String _id; From d5f7d8d962f1f9ea1b5452e7f74cc231f2664c05 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:34 +0200 Subject: [PATCH 07/10] refactor(neon): refactor account storage Signed-off-by: Nikolas Rimikis --- .../neon/neon/lib/src/blocs/accounts.dart | 19 ++++++++++++++----- .../neon/neon/lib/src/utils/push_utils.dart | 3 +-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index ed251c51..02b4a6d3 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -61,10 +61,10 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState const lastUsedStorage = SingleValueStorage('last-used-account'); accounts - ..add(loadAccounts(_storage)) + ..add(loadAccounts()) ..listen((final as) async { _globalOptions.updateAccounts(as); - await _storage.setStringList(_keyAccounts, as.map((final a) => json.encode(a.toJson())).toList()); + await saveAccounts(as); }); activeAccount.listen((final aa) async { if (aa != null) { @@ -96,7 +96,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState } } - late final AppStorage _storage = const AppStorage('accounts'); final GlobalOptions _globalOptions; final Iterable _allAppImplementations; @@ -278,10 +277,12 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState ); } -/// Get a list of logged in accounts from [storage]. +/// Gets a list of logged in accounts from storage. /// /// It is not checked whether the stored information is still valid. -List loadAccounts(final AppStorage storage) { +List loadAccounts() { + const storage = AppStorage('accounts'); + if (storage.containsKey(_keyAccounts)) { return storage .getStringList(_keyAccounts)! @@ -290,3 +291,11 @@ List loadAccounts(final AppStorage storage) { } return []; } + +/// Saves the given [accounts] to the storage. +Future saveAccounts(final List accounts) async { + const storage = AppStorage('accounts'); + final values = accounts.map((final a) => json.encode(a.toJson())).toList(); + + await storage.setStringList(_keyAccounts, values); +} diff --git a/packages/neon/neon/lib/src/utils/push_utils.dart b/packages/neon/neon/lib/src/utils/push_utils.dart index d119e5c2..1ee1121e 100644 --- a/packages/neon/neon/lib/src/utils/push_utils.dart +++ b/packages/neon/neon/lib/src/utils/push_utils.dart @@ -93,12 +93,11 @@ class PushUtils { } else { final localizations = await appLocalizationsFromSystem(); - var accounts = []; + final accounts = loadAccounts(); Account? account; NotificationsNotification? notification; AndroidBitmap? largeIconBitmap; try { - accounts = loadAccounts(const AppStorage('accounts')); account = accounts.tryFind(instance); if (account != null) { notification = From 198df01bcc261afafc898d4c4fa0e8e5d434413d Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:34 +0200 Subject: [PATCH 08/10] refactor(neon): reequire unique keys for the storage Signed-off-by: Nikolas Rimikis --- .../neon/neon/lib/src/blocs/accounts.dart | 8 +-- .../neon/neon/lib/src/blocs/first_launch.dart | 2 +- .../lib/src/blocs/push_notifications.dart | 5 +- .../lib/src/models/app_implementation.dart | 2 +- .../neon/lib/src/settings/models/storage.dart | 53 ++++++++++++++----- .../neon/lib/src/utils/global_options.dart | 2 +- .../neon/neon/lib/src/utils/push_utils.dart | 6 +-- 7 files changed, 52 insertions(+), 26 deletions(-) diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index 02b4a6d3..a8ad92b1 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -58,7 +58,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState this._globalOptions, this._allAppImplementations, ) { - const lastUsedStorage = SingleValueStorage('last-used-account'); + const lastUsedStorage = SingleValueStorage(StorageKeys.lastUsedAccount); accounts ..add(loadAccounts()) @@ -210,7 +210,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState /// Use [activeOptions] to get them for the [activeAccount]. AccountSpecificOptions getOptionsFor(final Account account) => _accountsOptions[account.id] ??= AccountSpecificOptions( - AppStorage('accounts-${account.id}'), + AppStorage(StorageKeys.accounts, account.id), getAppsBlocFor(account), ); @@ -281,7 +281,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState /// /// It is not checked whether the stored information is still valid. List loadAccounts() { - const storage = AppStorage('accounts'); + const storage = AppStorage(StorageKeys.accounts); if (storage.containsKey(_keyAccounts)) { return storage @@ -294,7 +294,7 @@ List loadAccounts() { /// Saves the given [accounts] to the storage. Future saveAccounts(final List accounts) async { - const storage = AppStorage('accounts'); + const storage = AppStorage(StorageKeys.accounts); final values = accounts.map((final a) => json.encode(a.toJson())).toList(); await storage.setStringList(_keyAccounts, values); diff --git a/packages/neon/neon/lib/src/blocs/first_launch.dart b/packages/neon/neon/lib/src/blocs/first_launch.dart index bcf83f8b..056e9378 100644 --- a/packages/neon/neon/lib/src/blocs/first_launch.dart +++ b/packages/neon/neon/lib/src/blocs/first_launch.dart @@ -17,7 +17,7 @@ class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunch FirstLaunchBloc({ final bool disabled = false, }) { - const storage = SingleValueStorage('first-launch'); + const storage = SingleValueStorage(StorageKeys.firstLaunch); if (!disabled && !storage.hasValue()) { onFirstLaunch.add(null); diff --git a/packages/neon/neon/lib/src/blocs/push_notifications.dart b/packages/neon/neon/lib/src/blocs/push_notifications.dart index 7e3c6698..a1fde3fd 100644 --- a/packages/neon/neon/lib/src/blocs/push_notifications.dart +++ b/packages/neon/neon/lib/src/blocs/push_notifications.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/models/app_ids.dart'; import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/settings/models/storage.dart'; @@ -37,7 +36,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, } final AccountsBloc _accountsBloc; - late final _storage = const AppStorage(AppIDs.notifications); + late final _storage = const AppStorage(StorageKeys.notifications); final GlobalOptions _globalOptions; final _notificationsController = StreamController(); @@ -69,7 +68,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, Future _setupUnifiedPush() async { // We just use a single RSA keypair for all accounts - final keypair = await PushUtils.loadRSAKeypair(_storage); + final keypair = await PushUtils.loadRSAKeypair(); await UnifiedPush.initialize( onNewEndpoint: (final endpoint, final instance) async { diff --git a/packages/neon/neon/lib/src/models/app_implementation.dart b/packages/neon/neon/lib/src/models/app_implementation.dart index 4628d10d..1051a571 100644 --- a/packages/neon/neon/lib/src/models/app_implementation.dart +++ b/packages/neon/neon/lib/src/models/app_implementation.dart @@ -25,7 +25,7 @@ abstract class AppImplementation String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); @protected - late final AppStorage storage = AppStorage('app-$id'); + late final AppStorage storage = AppStorage(StorageKeys.apps, id); @mustBeOverridden R get options; diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index ab4fc105..76575fc8 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -1,4 +1,5 @@ import 'package:meta/meta.dart'; +import 'package:neon/src/models/app_ids.dart'; import 'package:shared_preferences/shared_preferences.dart'; @internal @@ -15,6 +16,21 @@ abstract interface class SettingsStorage { Future remove(final String key); } +@internal +enum StorageKeys { + apps._('app'), + accounts._('accounts'), + global._('global'), + lastUsedAccount._('last-used-account'), + lastEndpoint._('last-endpoint'), + firstLaunch._('first-launch'), + notifications._(AppIDs.notifications); + + const StorageKeys._(this.value); + + final String value; +} + @internal final class NeonStorage { /// Shared preferences instance. @@ -49,36 +65,47 @@ final class NeonStorage { @immutable @internal final class SingleValueStorage { - const SingleValueStorage(this._id); + const SingleValueStorage(this.key); - final String _id; + final StorageKeys key; - bool hasValue() => NeonStorage.database.containsKey(_id); + bool hasValue() => NeonStorage.database.containsKey(key.value); - Future remove() => NeonStorage.database.remove(_id); + Future remove() => NeonStorage.database.remove(key.value); - String? getString() => NeonStorage.database.getString(_id); + String? getString() => NeonStorage.database.getString(key.value); - Future setString(final String value) => NeonStorage.database.setString(_id, value); + Future setString(final String value) => NeonStorage.database.setString(key.value, value); - bool? getBool() => NeonStorage.database.getBool(_id); + bool? getBool() => NeonStorage.database.getBool(key.value); // ignore: avoid_positional_boolean_parameters - Future setBool(final bool value) => NeonStorage.database.setBool(_id, value); + Future setBool(final bool value) => NeonStorage.database.setBool(key.value, value); - List? getStringList() => NeonStorage.database.getStringList(_id); + List? getStringList() => NeonStorage.database.getStringList(key.value); - Future setStringList(final List value) => NeonStorage.database.setStringList(_id, value); + Future setStringList(final List value) => NeonStorage.database.setStringList(key.value, value); } @immutable @internal final class AppStorage implements SettingsStorage { - const AppStorage(this._id); + const AppStorage( + this.key, [ + this.suffix, + ]); + + final StorageKeys key; - final String _id; + final String? suffix; - String _formatKey(final String key) => '$_id-$key'; + String _formatKey(final String key) { + if (suffix != null) { + return '${this.key.value}-$suffix-$key'; + } + + return '${this.key.value}-$key'; + } bool containsKey(final String key) => NeonStorage.database.containsKey(_formatKey(key)); diff --git a/packages/neon/neon/lib/src/utils/global_options.dart b/packages/neon/neon/lib/src/utils/global_options.dart index 9319b927..ca31d8c5 100644 --- a/packages/neon/neon/lib/src/utils/global_options.dart +++ b/packages/neon/neon/lib/src/utils/global_options.dart @@ -49,7 +49,7 @@ class GlobalOptions { } } - late final AppStorage _storage = const AppStorage('global'); + late final AppStorage _storage = const AppStorage(StorageKeys.global); final PackageInfo _packageInfo; late final _distributorsMap = { diff --git a/packages/neon/neon/lib/src/utils/push_utils.dart b/packages/neon/neon/lib/src/utils/push_utils.dart index 1ee1121e..35b7cd1a 100644 --- a/packages/neon/neon/lib/src/utils/push_utils.dart +++ b/packages/neon/neon/lib/src/utils/push_utils.dart @@ -11,7 +11,6 @@ import 'package:image/image.dart' as img; import 'package:meta/meta.dart'; import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/models/app_ids.dart'; import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/theme/colors.dart'; @@ -22,7 +21,8 @@ import 'package:nextcloud/nextcloud.dart'; @internal @immutable class PushUtils { - static Future loadRSAKeypair(final AppStorage storage) async { + static Future loadRSAKeypair() async { + const storage = AppStorage(StorageKeys.notifications); const keyDevicePrivateKey = 'device-private-key'; late RSAKeypair keypair; @@ -72,7 +72,7 @@ class PushUtils { ); await NeonStorage.init(); - final keypair = await loadRSAKeypair(const AppStorage(AppIDs.notifications)); + final keypair = await loadRSAKeypair(); for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { final data = json.decode(message) as Map; From 0e563cec189f9c835112a8fe4d05e06cc4a4ecca Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 29 Aug 2023 17:13:34 +0200 Subject: [PATCH 09/10] refactor(neon,neon_files,neon_news,neon_notes): make Option.key unique Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/settings.dart | 1 + .../neon/lib/src/settings/models/option.dart | 2 +- .../src/settings/models/select_option.dart | 8 ++-- .../neon/lib/src/settings/models/storage.dart | 7 ++- .../src/settings/models/toggle_option.dart | 8 ++-- .../neon/lib/src/utils/account_options.dart | 11 ++++- .../neon/lib/src/utils/global_options.dart | 44 ++++++++++++++----- .../lib/src/utils/settings_export_helper.dart | 2 +- packages/neon/neon/test/option_test.dart | 31 ++++++++----- packages/neon/neon_files/lib/options.dart | 29 +++++++++--- packages/neon/neon_news/lib/options.dart | 41 ++++++++++++----- packages/neon/neon_notes/lib/options.dart | 26 ++++++++--- 12 files changed, 151 insertions(+), 59 deletions(-) diff --git a/packages/neon/neon/lib/settings.dart b/packages/neon/neon/lib/settings.dart index 8a1e4d8e..13193417 100644 --- a/packages/neon/neon/lib/settings.dart +++ b/packages/neon/neon/lib/settings.dart @@ -2,5 +2,6 @@ export 'package:neon/src/models/label_builder.dart'; export 'package:neon/src/settings/models/nextcloud_app_options.dart'; export 'package:neon/src/settings/models/options_category.dart'; export 'package:neon/src/settings/models/select_option.dart'; +export 'package:neon/src/settings/models/storage.dart' show Storable; export 'package:neon/src/settings/models/toggle_option.dart'; export 'package:neon/src/settings/widgets/settings_list.dart'; diff --git a/packages/neon/neon/lib/src/settings/models/option.dart b/packages/neon/neon/lib/src/settings/models/option.dart index 4d33bfb3..ac954b4d 100644 --- a/packages/neon/neon/lib/src/settings/models/option.dart +++ b/packages/neon/neon/lib/src/settings/models/option.dart @@ -38,7 +38,7 @@ abstract class Option extends ChangeNotifier implements ValueListenable { } final SettingsStorage storage; - final String key; + final Storable key; final LabelBuilder label; final T defaultValue; final OptionsCategory? category; diff --git a/packages/neon/neon/lib/src/settings/models/select_option.dart b/packages/neon/neon/lib/src/settings/models/select_option.dart index 0cc0b9e2..6139cbe5 100644 --- a/packages/neon/neon/lib/src/settings/models/select_option.dart +++ b/packages/neon/neon/lib/src/settings/models/select_option.dart @@ -21,7 +21,7 @@ class SelectOption extends Option { super.category, super.enabled, }) : _values = values, - super(initialValue: loadValue(values, storage.getString(key), forceLoad: forceLoadValue)); + super(initialValue: loadValue(values, storage.getString(key.value), forceLoad: forceLoadValue)); /// Creates a SelectOption depending on the State of another [Option]. SelectOption.depend({ @@ -39,7 +39,7 @@ class SelectOption extends Option { final bool forceLoadValue = true, super.category, }) : _values = values, - super.depend(initialValue: loadValue(values, storage.getString(key), forceLoad: forceLoadValue)); + super.depend(initialValue: loadValue(values, storage.getString(key.value), forceLoad: forceLoadValue)); static T? loadValue(final Map vs, final String? stored, {final bool forceLoad = true}) { if (forceLoad && vs.isEmpty && stored is T) { @@ -51,7 +51,7 @@ class SelectOption extends Option { @override void reset() { - unawaited(storage.remove(key)); + unawaited(storage.remove(key.value)); super.reset(); } @@ -63,7 +63,7 @@ class SelectOption extends Option { super.value = value; if (value != null) { - unawaited(storage.setString(key, serialize()!)); + unawaited(storage.setString(key.value, serialize()!)); } } diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 76575fc8..74a52862 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -16,8 +16,12 @@ abstract interface class SettingsStorage { Future remove(final String key); } +abstract interface class Storable { + String get value; +} + @internal -enum StorageKeys { +enum StorageKeys implements Storable { apps._('app'), accounts._('accounts'), global._('global'), @@ -28,6 +32,7 @@ enum StorageKeys { const StorageKeys._(this.value); + @override final String value; } diff --git a/packages/neon/neon/lib/src/settings/models/toggle_option.dart b/packages/neon/neon/lib/src/settings/models/toggle_option.dart index e3ef5397..9b3ae94b 100644 --- a/packages/neon/neon/lib/src/settings/models/toggle_option.dart +++ b/packages/neon/neon/lib/src/settings/models/toggle_option.dart @@ -11,7 +11,7 @@ class ToggleOption extends Option { required final bool defaultValue, super.category, super.enabled, - }) : super(defaultValue: storage.getBool(key) ?? defaultValue); + }) : super(defaultValue: storage.getBool(key.value) ?? defaultValue); /// Creates a ToggleOption depending on the State of another [Option]. ToggleOption.depend({ @@ -22,12 +22,12 @@ class ToggleOption extends Option { required super.enabled, super.category, }) : super.depend( - defaultValue: storage.getBool(key) ?? defaultValue, + defaultValue: storage.getBool(key.value) ?? defaultValue, ); @override void reset() { - unawaited(storage.remove(key)); + unawaited(storage.remove(key.value)); super.reset(); } @@ -36,7 +36,7 @@ class ToggleOption extends Option { set value(final bool value) { super.value = value; - unawaited(storage.setBool(key, serialize())); + unawaited(storage.setBool(key.value, serialize())); } @override diff --git a/packages/neon/neon/lib/src/utils/account_options.dart b/packages/neon/neon/lib/src/utils/account_options.dart index 6125009d..61c2c5cc 100644 --- a/packages/neon/neon/lib/src/utils/account_options.dart +++ b/packages/neon/neon/lib/src/utils/account_options.dart @@ -47,9 +47,18 @@ class AccountSpecificOptions { late final initialApp = SelectOption( storage: _storage, - key: 'initial-app', + key: AccountOptionKeys.initialApp, label: (final context) => AppLocalizations.of(context).accountOptionsInitialApp, defaultValue: null, values: {}, ); } + +enum AccountOptionKeys implements Storable { + initialApp._('initial-app'); + + const AccountOptionKeys._(this.value); + + @override + final String value; +} diff --git a/packages/neon/neon/lib/src/utils/global_options.dart b/packages/neon/neon/lib/src/utils/global_options.dart index ca31d8c5..e7081fef 100644 --- a/packages/neon/neon/lib/src/utils/global_options.dart +++ b/packages/neon/neon/lib/src/utils/global_options.dart @@ -125,7 +125,7 @@ class GlobalOptions { late final themeMode = SelectOption( storage: _storage, - key: 'theme-mode', + key: GlobalOptionKeys.themeMode, label: (final context) => AppLocalizations.of(context).globalOptionsThemeMode, defaultValue: ThemeMode.system, values: { @@ -137,28 +137,28 @@ class GlobalOptions { late final themeOLEDAsDark = ToggleOption( storage: _storage, - key: 'theme-oled-as-dark', + key: GlobalOptionKeys.themeOledAsDark, label: (final context) => AppLocalizations.of(context).globalOptionsThemeOLEDAsDark, defaultValue: false, ); late final themeKeepOriginalAccentColor = ToggleOption( storage: _storage, - key: 'theme-keep-original-accent-color', + key: GlobalOptionKeys.themeKeepOriginalAccentColor, label: (final context) => AppLocalizations.of(context).globalOptionsThemeKeepOriginalAccentColor, defaultValue: false, ); late final pushNotificationsEnabled = ToggleOption( storage: _storage, - key: 'push-notifications-enabled', + key: GlobalOptionKeys.pushNotificationsEnabled, label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled, defaultValue: false, ); late final pushNotificationsDistributor = SelectOption.depend( storage: _storage, - key: 'push-notifications-distributor', + key: GlobalOptionKeys.pushNotificationsDistributor, label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributor, defaultValue: null, values: {}, @@ -167,14 +167,14 @@ class GlobalOptions { late final startupMinimized = ToggleOption( storage: _storage, - key: 'startup-minimized', + key: GlobalOptionKeys.startupMinimized, label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimized, defaultValue: false, ); late final startupMinimizeInsteadOfExit = ToggleOption( storage: _storage, - key: 'startup-minimize-instead-of-exit', + key: GlobalOptionKeys.startupMinimizeInsteadOfExit, label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimizeInsteadOfExit, defaultValue: false, ); @@ -183,14 +183,14 @@ class GlobalOptions { late final systemTrayEnabled = ToggleOption( storage: _storage, - key: 'systemtray-enabled', + key: GlobalOptionKeys.systemtrayEnabled, label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayEnabled, defaultValue: false, ); late final systemTrayHideToTrayWhenMinimized = ToggleOption.depend( storage: _storage, - key: 'systemtray-hide-to-tray-when-minimized', + key: GlobalOptionKeys.systemtrayHideToTrayWhenMinimized, label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayHideToTrayWhenMinimized, defaultValue: true, enabled: systemTrayEnabled, @@ -198,14 +198,14 @@ class GlobalOptions { late final rememberLastUsedAccount = ToggleOption( storage: _storage, - key: 'remember-last-used-account', + key: GlobalOptionKeys.rememberLastUsedAccount, label: (final context) => AppLocalizations.of(context).globalOptionsAccountsRememberLastUsedAccount, defaultValue: true, ); late final initialAccount = SelectOption( storage: _storage, - key: 'initial-account', + key: GlobalOptionKeys.initialAccount, label: (final context) => AppLocalizations.of(context).globalOptionsAccountsInitialAccount, defaultValue: null, values: {}, @@ -213,7 +213,7 @@ class GlobalOptions { late final navigationMode = SelectOption( storage: _storage, - key: 'navigation-mode', + key: GlobalOptionKeys.navigationMode, label: (final context) => AppLocalizations.of(context).globalOptionsNavigationMode, defaultValue: Platform.isAndroid || Platform.isIOS ? NavigationMode.drawer : NavigationMode.drawerAlwaysVisible, values: { @@ -228,6 +228,26 @@ class GlobalOptions { ); } +enum GlobalOptionKeys implements Storable { + themeMode._('theme-mode'), + themeOledAsDark._('theme-oled-as-dark'), + themeKeepOriginalAccentColor._('theme-keep-original-accent-color'), + pushNotificationsEnabled._('push-notifications-enabled'), + pushNotificationsDistributor._('push-notifications-distributor'), + startupMinimized._('startup-minimized'), + startupMinimizeInsteadOfExit._('startup-minimize-instead-of-exit'), + systemtrayEnabled._('systemtray-enabled'), + systemtrayHideToTrayWhenMinimized._('systemtray-hide-to-tray-when-minimized'), + rememberLastUsedAccount._('remember-last-used-account'), + initialAccount._('initial-account'), + navigationMode._('navigation-mode'); + + const GlobalOptionKeys._(this.value); + + @override + final String value; +} + enum NavigationMode { drawer, drawerAlwaysVisible, diff --git a/packages/neon/neon/lib/src/utils/settings_export_helper.dart b/packages/neon/neon/lib/src/utils/settings_export_helper.dart index 3fe86dfa..d402eb5b 100644 --- a/packages/neon/neon/lib/src/utils/settings_export_helper.dart +++ b/packages/neon/neon/lib/src/utils/settings_export_helper.dart @@ -53,7 +53,7 @@ class SettingsExportHelper { Future _applyOptionsMapToOptions(final List