Browse Source

Merge pull request #674 from nextcloud/refactor/neon/settings

Refactor/neon/settings
pull/675/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
c724200fa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/neon/neon/lib/neon.dart
  2. 2
      packages/neon/neon/lib/settings.dart
  3. 32
      packages/neon/neon/lib/src/blocs/accounts.dart
  4. 11
      packages/neon/neon/lib/src/blocs/first_launch.dart
  5. 5
      packages/neon/neon/lib/src/blocs/push_notifications.dart
  6. 2
      packages/neon/neon/lib/src/models/app_implementation.dart
  7. 2
      packages/neon/neon/lib/src/settings/models/option.dart
  8. 8
      packages/neon/neon/lib/src/settings/models/select_option.dart
  9. 104
      packages/neon/neon/lib/src/settings/models/storage.dart
  10. 8
      packages/neon/neon/lib/src/settings/models/toggle_option.dart
  11. 11
      packages/neon/neon/lib/src/utils/account_options.dart
  12. 46
      packages/neon/neon/lib/src/utils/global_options.dart
  13. 11
      packages/neon/neon/lib/src/utils/push_utils.dart
  14. 2
      packages/neon/neon/lib/src/utils/settings_export_helper.dart
  15. 31
      packages/neon/neon/test/option_test.dart
  16. 119
      packages/neon/neon/test/storage_test.dart
  17. 29
      packages/neon/neon_files/lib/options.dart
  18. 41
      packages/neon/neon_news/lib/options.dart
  19. 26
      packages/neon/neon_notes/lib/options.dart

2
packages/neon/neon/lib/neon.dart

@ -31,7 +31,7 @@ Future runNeon({
await NeonPlatform.setup(); await NeonPlatform.setup();
await RequestManager.instance.initCache(); await RequestManager.instance.initCache();
await AppStorage.init(); await NeonStorage.init();
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
buildUserAgent(packageInfo); buildUserAgent(packageInfo);

2
packages/neon/neon/lib/settings.dart

@ -2,6 +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/nextcloud_app_options.dart';
export 'package:neon/src/settings/models/options_category.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/select_option.dart';
export 'package:neon/src/settings/models/storage.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/models/toggle_option.dart';
export 'package:neon/src/settings/widgets/settings_list.dart'; export 'package:neon/src/settings/widgets/settings_list.dart';

32
packages/neon/neon/lib/src/blocs/accounts.dart

@ -58,24 +58,26 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
this._globalOptions, this._globalOptions,
this._allAppImplementations, this._allAppImplementations,
) { ) {
const lastUsedStorage = SingleValueStorage(StorageKeys.lastUsedAccount);
accounts accounts
..add(loadAccounts(_storage)) ..add(loadAccounts())
..listen((final as) async { ..listen((final as) async {
_globalOptions.updateAccounts(as); _globalOptions.updateAccounts(as);
await _storage.setStringList(_keyAccounts, as.map((final a) => json.encode(a.toJson())).toList()); await saveAccounts(as);
}); });
activeAccount.listen((final aa) async { activeAccount.listen((final aa) async {
if (aa != null) { if (aa != null) {
await _storage.setString(_keyLastUsedAccount, aa.id); await lastUsedStorage.setString(aa.id);
} else { } else {
await _storage.remove(_keyLastUsedAccount); await lastUsedStorage.remove();
} }
}); });
final as = accounts.value; final as = accounts.value;
if (_globalOptions.rememberLastUsedAccount.value && _storage.containsKey(_keyLastUsedAccount)) { if (_globalOptions.rememberLastUsedAccount.value && lastUsedStorage.hasValue()) {
final lastUsedAccountID = _storage.getString(_keyLastUsedAccount); final lastUsedAccountID = lastUsedStorage.getString();
if (lastUsedAccountID != null) { if (lastUsedAccountID != null) {
final aa = as.tryFind(lastUsedAccountID); final aa = as.tryFind(lastUsedAccountID);
if (aa != null) { if (aa != null) {
@ -94,10 +96,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
} }
late final AppStorage _storage = AppStorage('accounts');
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;
final Iterable<AppImplementation> _allAppImplementations; final Iterable<AppImplementation> _allAppImplementations;
final _keyLastUsedAccount = 'last-used-account';
final _accountsOptions = <String, AccountSpecificOptions>{}; final _accountsOptions = <String, AccountSpecificOptions>{};
final _appsBlocs = <String, AppsBloc>{}; final _appsBlocs = <String, AppsBloc>{};
@ -210,7 +210,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// Use [activeOptions] to get them for the [activeAccount]. /// Use [activeOptions] to get them for the [activeAccount].
AccountSpecificOptions getOptionsFor(final Account account) => AccountSpecificOptions getOptionsFor(final Account account) =>
_accountsOptions[account.id] ??= AccountSpecificOptions( _accountsOptions[account.id] ??= AccountSpecificOptions(
AppStorage('accounts-${account.id}'), AppStorage(StorageKeys.accounts, account.id),
getAppsBlocFor(account), getAppsBlocFor(account),
); );
@ -277,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. /// It is not checked whether the stored information is still valid.
List<Account> loadAccounts(final AppStorage storage) { List<Account> loadAccounts() {
const storage = AppStorage(StorageKeys.accounts);
if (storage.containsKey(_keyAccounts)) { if (storage.containsKey(_keyAccounts)) {
return storage return storage
.getStringList(_keyAccounts)! .getStringList(_keyAccounts)!
@ -289,3 +291,11 @@ List<Account> loadAccounts(final AppStorage storage) {
} }
return []; return [];
} }
/// Saves the given [accounts] to the storage.
Future<void> saveAccounts(final List<Account> accounts) async {
const storage = AppStorage(StorageKeys.accounts);
final values = accounts.map((final a) => json.encode(a.toJson())).toList();
await storage.setStringList(_keyAccounts, values);
}

11
packages/neon/neon/lib/src/blocs/first_launch.dart

@ -16,16 +16,15 @@ abstract class FirstLaunchBlocStates {
class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunchBlocStates { class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunchBlocStates {
FirstLaunchBloc({ FirstLaunchBloc({
final bool disabled = false, final bool disabled = false,
}) : _storage = AppStorage(_keyFirstLaunch) { }) {
if (!disabled && !_storage.containsKey(_keyFirstLaunch)) { const storage = SingleValueStorage(StorageKeys.firstLaunch);
if (!disabled && !storage.hasValue()) {
onFirstLaunch.add(null); onFirstLaunch.add(null);
unawaited(_storage.setBool(_keyFirstLaunch, false)); unawaited(storage.setBool(false));
} }
} }
final AppStorage _storage;
static const _keyFirstLaunch = 'first-launch';
@override @override
void dispose() { void dispose() {
unawaited(onFirstLaunch.close()); unawaited(onFirstLaunch.close());

5
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/bloc/bloc.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.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/models/push_notification.dart';
import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
@ -37,7 +36,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
} }
final AccountsBloc _accountsBloc; final AccountsBloc _accountsBloc;
late final _storage = AppStorage(AppIDs.notifications); late final _storage = const AppStorage(StorageKeys.notifications);
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;
final _notificationsController = StreamController<PushNotification>(); final _notificationsController = StreamController<PushNotification>();
@ -69,7 +68,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
Future _setupUnifiedPush() async { Future _setupUnifiedPush() async {
// We just use a single RSA keypair for all accounts // We just use a single RSA keypair for all accounts
final keypair = await PushUtils.loadRSAKeypair(_storage); final keypair = await PushUtils.loadRSAKeypair();
await UnifiedPush.initialize( await UnifiedPush.initialize(
onNewEndpoint: (final endpoint, final instance) async { onNewEndpoint: (final endpoint, final instance) async {

2
packages/neon/neon/lib/src/models/app_implementation.dart

@ -25,7 +25,7 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context));
@protected @protected
late final AppStorage storage = AppStorage('app-$id'); late final AppStorage storage = AppStorage(StorageKeys.apps, id);
@mustBeOverridden @mustBeOverridden
R get options; R get options;

2
packages/neon/neon/lib/src/settings/models/option.dart

@ -38,7 +38,7 @@ abstract class Option<T> extends ChangeNotifier implements ValueListenable<T> {
} }
final SettingsStorage storage; final SettingsStorage storage;
final String key; final Storable key;
final LabelBuilder label; final LabelBuilder label;
final T defaultValue; final T defaultValue;
final OptionsCategory? category; final OptionsCategory? category;

8
packages/neon/neon/lib/src/settings/models/select_option.dart

@ -21,7 +21,7 @@ class SelectOption<T> extends Option<T> {
super.category, super.category,
super.enabled, super.enabled,
}) : _values = values, }) : _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]. /// Creates a SelectOption depending on the State of another [Option].
SelectOption.depend({ SelectOption.depend({
@ -39,7 +39,7 @@ class SelectOption<T> extends Option<T> {
final bool forceLoadValue = true, final bool forceLoadValue = true,
super.category, super.category,
}) : _values = values, }) : _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<T>(final Map<T, LabelBuilder> vs, final String? stored, {final bool forceLoad = true}) { static T? loadValue<T>(final Map<T, LabelBuilder> vs, final String? stored, {final bool forceLoad = true}) {
if (forceLoad && vs.isEmpty && stored is T) { if (forceLoad && vs.isEmpty && stored is T) {
@ -51,7 +51,7 @@ class SelectOption<T> extends Option<T> {
@override @override
void reset() { void reset() {
unawaited(storage.remove(key)); unawaited(storage.remove(key.value));
super.reset(); super.reset();
} }
@ -63,7 +63,7 @@ class SelectOption<T> extends Option<T> {
super.value = value; super.value = value;
if (value != null) { if (value != null) {
unawaited(storage.setString(key, serialize()!)); unawaited(storage.setString(key.value, serialize()!));
} }
} }

104
packages/neon/neon/lib/src/settings/models/storage.dart

@ -1,6 +1,8 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/src/models/app_ids.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@internal
abstract interface class SettingsStorage { abstract interface class SettingsStorage {
String? getString(final String key); String? getString(final String key);
@ -14,56 +16,126 @@ abstract interface class SettingsStorage {
Future<bool> remove(final String key); Future<bool> remove(final String key);
} }
class AppStorage implements SettingsStorage { abstract interface class Storable {
AppStorage(this._id); String get value;
}
@internal
enum StorageKeys implements Storable {
apps._('app'),
accounts._('accounts'),
global._('global'),
lastUsedAccount._('last-used-account'),
lastEndpoint._('last-endpoint'),
firstLaunch._('first-launch'),
notifications._(AppIDs.notifications);
final String _id; const StorageKeys._(this.value);
@override
final String value;
}
@internal
final class NeonStorage {
/// Shared preferences instance. /// Shared preferences instance.
/// ///
/// Use [reqireDatabase] to access it. /// Use [database] to access it.
/// Make sure it has been initialized wiht [init] before. /// Make sure it has been initialized wiht [init] before.
static SharedPreferences? _sharedPreferences; static SharedPreferences? _sharedPreferences;
@visibleForTesting
// ignore: use_setters_to_change_properties
static void mock(final SharedPreferences mock) => _sharedPreferences = mock;
/// Sets up the [SharedPreferences] instance. /// Sets up the [SharedPreferences] instance.
/// ///
/// Required to be called before accessing [reqireDatabase]. /// Required to be called before accessing [database].
static Future init() async { static Future init() async {
if (_sharedPreferences != null) {
return;
}
_sharedPreferences = await SharedPreferences.getInstance(); _sharedPreferences = await SharedPreferences.getInstance();
} }
@visibleForTesting @visibleForTesting
static SharedPreferences get reqireDatabase { static SharedPreferences get database {
if (_sharedPreferences == null) { if (_sharedPreferences == null) {
throw StateError( 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!; return _sharedPreferences!;
} }
}
@immutable
@internal
final class SingleValueStorage {
const SingleValueStorage(this.key);
final StorageKeys key;
bool hasValue() => NeonStorage.database.containsKey(key.value);
Future<bool> remove() => NeonStorage.database.remove(key.value);
String? getString() => NeonStorage.database.getString(key.value);
String _formatKey(final String key) => '$_id-$key'; Future setString(final String value) => NeonStorage.database.setString(key.value, value);
bool? getBool() => NeonStorage.database.getBool(key.value);
// ignore: avoid_positional_boolean_parameters
Future setBool(final bool value) => NeonStorage.database.setBool(key.value, value);
List<String>? getStringList() => NeonStorage.database.getStringList(key.value);
Future setStringList(final List<String> value) => NeonStorage.database.setStringList(key.value, value);
}
@immutable
@internal
final class AppStorage implements SettingsStorage {
const AppStorage(
this.key, [
this.suffix,
]);
final StorageKeys key;
final String? suffix;
@visibleForTesting
String formatKey(final String key) {
if (suffix != null) {
return '${this.key.value}-$suffix-$key';
}
return '${this.key.value}-$key';
}
bool containsKey(final String key) => reqireDatabase.containsKey(_formatKey(key)); bool containsKey(final String key) => NeonStorage.database.containsKey(formatKey(key));
@override @override
Future<bool> remove(final String key) => reqireDatabase.remove(_formatKey(key)); Future<bool> remove(final String key) => NeonStorage.database.remove(formatKey(key));
@override @override
String? getString(final String key) => reqireDatabase.getString(_formatKey(key)); String? getString(final String key) => NeonStorage.database.getString(formatKey(key));
@override @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 @override
bool? getBool(final String key) => reqireDatabase.getBool(_formatKey(key)); bool? getBool(final String key) => NeonStorage.database.getBool(formatKey(key));
@override @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<String>? getStringList(final String key) => reqireDatabase.getStringList(_formatKey(key)); List<String>? getStringList(final String key) => NeonStorage.database.getStringList(formatKey(key));
Future setStringList(final String key, final List<String> value) => Future setStringList(final String key, final List<String> value) =>
reqireDatabase.setStringList(_formatKey(key), value); NeonStorage.database.setStringList(formatKey(key), value);
} }

8
packages/neon/neon/lib/src/settings/models/toggle_option.dart

@ -11,7 +11,7 @@ class ToggleOption extends Option<bool> {
required final bool defaultValue, required final bool defaultValue,
super.category, super.category,
super.enabled, 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]. /// Creates a ToggleOption depending on the State of another [Option].
ToggleOption.depend({ ToggleOption.depend({
@ -22,12 +22,12 @@ class ToggleOption extends Option<bool> {
required super.enabled, required super.enabled,
super.category, super.category,
}) : super.depend( }) : super.depend(
defaultValue: storage.getBool(key) ?? defaultValue, defaultValue: storage.getBool(key.value) ?? defaultValue,
); );
@override @override
void reset() { void reset() {
unawaited(storage.remove(key)); unawaited(storage.remove(key.value));
super.reset(); super.reset();
} }
@ -36,7 +36,7 @@ class ToggleOption extends Option<bool> {
set value(final bool value) { set value(final bool value) {
super.value = value; super.value = value;
unawaited(storage.setBool(key, serialize())); unawaited(storage.setBool(key.value, serialize()));
} }
@override @override

11
packages/neon/neon/lib/src/utils/account_options.dart

@ -47,9 +47,18 @@ class AccountSpecificOptions {
late final initialApp = SelectOption<String?>( late final initialApp = SelectOption<String?>(
storage: _storage, storage: _storage,
key: 'initial-app', key: AccountOptionKeys.initialApp,
label: (final context) => AppLocalizations.of(context).accountOptionsInitialApp, label: (final context) => AppLocalizations.of(context).accountOptionsInitialApp,
defaultValue: null, defaultValue: null,
values: {}, values: {},
); );
} }
enum AccountOptionKeys implements Storable {
initialApp._('initial-app');
const AccountOptionKeys._(this.value);
@override
final String value;
}

46
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(StorageKeys.global);
final PackageInfo _packageInfo; final PackageInfo _packageInfo;
late final _distributorsMap = <String, String Function(BuildContext)>{ late final _distributorsMap = <String, String Function(BuildContext)>{
@ -125,7 +125,7 @@ class GlobalOptions {
late final themeMode = SelectOption<ThemeMode>( late final themeMode = SelectOption<ThemeMode>(
storage: _storage, storage: _storage,
key: 'theme-mode', key: GlobalOptionKeys.themeMode,
label: (final context) => AppLocalizations.of(context).globalOptionsThemeMode, label: (final context) => AppLocalizations.of(context).globalOptionsThemeMode,
defaultValue: ThemeMode.system, defaultValue: ThemeMode.system,
values: { values: {
@ -137,28 +137,28 @@ class GlobalOptions {
late final themeOLEDAsDark = ToggleOption( late final themeOLEDAsDark = ToggleOption(
storage: _storage, storage: _storage,
key: 'theme-oled-as-dark', key: GlobalOptionKeys.themeOledAsDark,
label: (final context) => AppLocalizations.of(context).globalOptionsThemeOLEDAsDark, label: (final context) => AppLocalizations.of(context).globalOptionsThemeOLEDAsDark,
defaultValue: false, defaultValue: false,
); );
late final themeKeepOriginalAccentColor = ToggleOption( late final themeKeepOriginalAccentColor = ToggleOption(
storage: _storage, storage: _storage,
key: 'theme-keep-original-accent-color', key: GlobalOptionKeys.themeKeepOriginalAccentColor,
label: (final context) => AppLocalizations.of(context).globalOptionsThemeKeepOriginalAccentColor, label: (final context) => AppLocalizations.of(context).globalOptionsThemeKeepOriginalAccentColor,
defaultValue: false, defaultValue: false,
); );
late final pushNotificationsEnabled = ToggleOption( late final pushNotificationsEnabled = ToggleOption(
storage: _storage, storage: _storage,
key: 'push-notifications-enabled', key: GlobalOptionKeys.pushNotificationsEnabled,
label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled, label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled,
defaultValue: false, defaultValue: false,
); );
late final pushNotificationsDistributor = SelectOption<String?>.depend( late final pushNotificationsDistributor = SelectOption<String?>.depend(
storage: _storage, storage: _storage,
key: 'push-notifications-distributor', key: GlobalOptionKeys.pushNotificationsDistributor,
label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributor, label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributor,
defaultValue: null, defaultValue: null,
values: {}, values: {},
@ -167,14 +167,14 @@ class GlobalOptions {
late final startupMinimized = ToggleOption( late final startupMinimized = ToggleOption(
storage: _storage, storage: _storage,
key: 'startup-minimized', key: GlobalOptionKeys.startupMinimized,
label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimized, label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimized,
defaultValue: false, defaultValue: false,
); );
late final startupMinimizeInsteadOfExit = ToggleOption( late final startupMinimizeInsteadOfExit = ToggleOption(
storage: _storage, storage: _storage,
key: 'startup-minimize-instead-of-exit', key: GlobalOptionKeys.startupMinimizeInsteadOfExit,
label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimizeInsteadOfExit, label: (final context) => AppLocalizations.of(context).globalOptionsStartupMinimizeInsteadOfExit,
defaultValue: false, defaultValue: false,
); );
@ -183,14 +183,14 @@ class GlobalOptions {
late final systemTrayEnabled = ToggleOption( late final systemTrayEnabled = ToggleOption(
storage: _storage, storage: _storage,
key: 'systemtray-enabled', key: GlobalOptionKeys.systemtrayEnabled,
label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayEnabled, label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayEnabled,
defaultValue: false, defaultValue: false,
); );
late final systemTrayHideToTrayWhenMinimized = ToggleOption.depend( late final systemTrayHideToTrayWhenMinimized = ToggleOption.depend(
storage: _storage, storage: _storage,
key: 'systemtray-hide-to-tray-when-minimized', key: GlobalOptionKeys.systemtrayHideToTrayWhenMinimized,
label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayHideToTrayWhenMinimized, label: (final context) => AppLocalizations.of(context).globalOptionsSystemTrayHideToTrayWhenMinimized,
defaultValue: true, defaultValue: true,
enabled: systemTrayEnabled, enabled: systemTrayEnabled,
@ -198,14 +198,14 @@ class GlobalOptions {
late final rememberLastUsedAccount = ToggleOption( late final rememberLastUsedAccount = ToggleOption(
storage: _storage, storage: _storage,
key: 'remember-last-used-account', key: GlobalOptionKeys.rememberLastUsedAccount,
label: (final context) => AppLocalizations.of(context).globalOptionsAccountsRememberLastUsedAccount, label: (final context) => AppLocalizations.of(context).globalOptionsAccountsRememberLastUsedAccount,
defaultValue: true, defaultValue: true,
); );
late final initialAccount = SelectOption<String?>( late final initialAccount = SelectOption<String?>(
storage: _storage, storage: _storage,
key: 'initial-account', key: GlobalOptionKeys.initialAccount,
label: (final context) => AppLocalizations.of(context).globalOptionsAccountsInitialAccount, label: (final context) => AppLocalizations.of(context).globalOptionsAccountsInitialAccount,
defaultValue: null, defaultValue: null,
values: {}, values: {},
@ -213,7 +213,7 @@ class GlobalOptions {
late final navigationMode = SelectOption<NavigationMode>( late final navigationMode = SelectOption<NavigationMode>(
storage: _storage, storage: _storage,
key: 'navigation-mode', key: GlobalOptionKeys.navigationMode,
label: (final context) => AppLocalizations.of(context).globalOptionsNavigationMode, label: (final context) => AppLocalizations.of(context).globalOptionsNavigationMode,
defaultValue: Platform.isAndroid || Platform.isIOS ? NavigationMode.drawer : NavigationMode.drawerAlwaysVisible, defaultValue: Platform.isAndroid || Platform.isIOS ? NavigationMode.drawer : NavigationMode.drawerAlwaysVisible,
values: { 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 { enum NavigationMode {
drawer, drawer,
drawerAlwaysVisible, drawerAlwaysVisible,

11
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:meta/meta.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.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/models/push_notification.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/theme/colors.dart'; import 'package:neon/src/theme/colors.dart';
@ -22,7 +21,8 @@ import 'package:nextcloud/nextcloud.dart';
@internal @internal
@immutable @immutable
class PushUtils { class PushUtils {
static Future<RSAKeypair> loadRSAKeypair(final AppStorage storage) async { static Future<RSAKeypair> loadRSAKeypair() async {
const storage = AppStorage(StorageKeys.notifications);
const keyDevicePrivateKey = 'device-private-key'; const keyDevicePrivateKey = 'device-private-key';
late RSAKeypair keypair; late RSAKeypair keypair;
@ -70,9 +70,9 @@ class PushUtils {
} }
}, },
); );
await AppStorage.init(); await NeonStorage.init();
final keypair = await loadRSAKeypair(AppStorage(AppIDs.notifications)); final keypair = await loadRSAKeypair();
for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) {
final data = json.decode(message) as Map<String, dynamic>; final data = json.decode(message) as Map<String, dynamic>;
@ -93,12 +93,11 @@ class PushUtils {
} else { } else {
final localizations = await appLocalizationsFromSystem(); final localizations = await appLocalizationsFromSystem();
var accounts = <Account>[]; final accounts = loadAccounts();
Account? account; Account? account;
NotificationsNotification? notification; NotificationsNotification? notification;
AndroidBitmap<Object>? largeIconBitmap; AndroidBitmap<Object>? largeIconBitmap;
try { try {
accounts = loadAccounts(AppStorage('accounts'));
account = accounts.tryFind(instance); account = accounts.tryFind(instance);
if (account != null) { if (account != null) {
notification = notification =

2
packages/neon/neon/lib/src/utils/settings_export_helper.dart

@ -53,7 +53,7 @@ class SettingsExportHelper {
Future _applyOptionsMapToOptions(final List<Option> options, final Map<String, dynamic> data) async { Future _applyOptionsMapToOptions(final List<Option> options, final Map<String, dynamic> data) async {
for (final optionKey in data.keys) { for (final optionKey in data.keys) {
for (final option in options) { for (final option in options) {
if (option.key == optionKey) { if (option.key.value == optionKey) {
final Object? value = data[optionKey]; final Object? value = data[optionKey];
if (value != null) { if (value != null) {

31
packages/neon/neon/test/option_test.dart

@ -15,6 +15,15 @@ class MockCallbackFunction extends Mock {
FutureOr<void> call(); FutureOr<void> call();
} }
enum StorageKey implements Storable {
key._('storage-key');
const StorageKey._(this.value);
@override
final String value;
}
enum SelectValues { enum SelectValues {
first, first,
second, second,
@ -23,7 +32,7 @@ enum SelectValues {
void main() { void main() {
final storage = MockStorage(); final storage = MockStorage();
const key = 'storage-key'; const key = StorageKey.key;
String labelBuilder(final _) => 'label'; String labelBuilder(final _) => 'label';
group('SelectOption', () { group('SelectOption', () {
@ -36,8 +45,8 @@ void main() {
late SelectOption<SelectValues> option; late SelectOption<SelectValues> option;
setUp(() { setUp(() {
when(() => storage.setString(key, any())).thenAnswer((final _) async {}); when(() => storage.setString(key.value, any())).thenAnswer((final _) async {});
when(() => storage.remove(key)).thenAnswer((final _) async => true); when(() => storage.remove(key.value)).thenAnswer((final _) async => true);
option = SelectOption<SelectValues>( option = SelectOption<SelectValues>(
storage: storage, storage: storage,
@ -56,7 +65,7 @@ void main() {
test('Create', () { test('Create', () {
expect(option.value, option.defaultValue, reason: 'Should default to defaultValue.'); expect(option.value, option.defaultValue, reason: 'Should default to defaultValue.');
when(() => storage.getString(key)).thenReturn('SelectValues.second'); when(() => storage.getString(key.value)).thenReturn('SelectValues.second');
option = SelectOption<SelectValues>( option = SelectOption<SelectValues>(
storage: storage, storage: storage,
@ -96,7 +105,7 @@ void main() {
..value = SelectValues.third; ..value = SelectValues.third;
verify(callback.call).called(1); verify(callback.call).called(1);
verify(() => storage.setString(key, 'SelectValues.third')).called(1); verify(() => storage.setString(key.value, 'SelectValues.third')).called(1);
expect(option.value, SelectValues.third, reason: 'Should update the value.'); expect(option.value, SelectValues.third, reason: 'Should update the value.');
option.value = SelectValues.third; option.value = SelectValues.third;
@ -150,7 +159,7 @@ void main() {
option.reset(); option.reset();
verify(callback.call).called(1); verify(callback.call).called(1);
verify(() => storage.remove(key)).called(1); verify(() => storage.remove(key.value)).called(1);
expect(option.value, option.defaultValue, reason: 'Should reset the value.'); expect(option.value, option.defaultValue, reason: 'Should reset the value.');
}); });
@ -171,8 +180,8 @@ void main() {
late ToggleOption option; late ToggleOption option;
setUp(() { setUp(() {
when(() => storage.setBool(key, any())).thenAnswer((final _) async {}); when(() => storage.setBool(key.value, any())).thenAnswer((final _) async {});
when(() => storage.remove(key)).thenAnswer((final _) async => true); when(() => storage.remove(key.value)).thenAnswer((final _) async => true);
option = ToggleOption( option = ToggleOption(
storage: storage, storage: storage,
@ -190,7 +199,7 @@ void main() {
test('Create', () { test('Create', () {
expect(option.value, option.defaultValue, reason: 'Should default to defaultValue.'); expect(option.value, option.defaultValue, reason: 'Should default to defaultValue.');
when(() => storage.getBool(key)).thenReturn(true); when(() => storage.getBool(key.value)).thenReturn(true);
option = ToggleOption( option = ToggleOption(
storage: storage, storage: storage,
@ -228,7 +237,7 @@ void main() {
..value = false; ..value = false;
verify(callback.call).called(1); verify(callback.call).called(1);
verify(() => storage.setBool(key, false)).called(1); verify(() => storage.setBool(key.value, false)).called(1);
expect(option.value, false, reason: 'Should update the value.'); expect(option.value, false, reason: 'Should update the value.');
option.value = false; option.value = false;
@ -262,7 +271,7 @@ void main() {
option.reset(); option.reset();
verify(callback.call).called(1); verify(callback.call).called(1);
verify(() => storage.remove(key)).called(1); verify(() => storage.remove(key.value)).called(1);
expect(option.value, option.defaultValue, reason: 'Should reset the value.'); expect(option.value, option.defaultValue, reason: 'Should reset the value.');
}); });
}); });

119
packages/neon/neon/test/storage_test.dart

@ -1,14 +1,125 @@
import 'package:mocktail/mocktail.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
class SharedPreferencesMock extends Mock implements SharedPreferences {}
void main() { void main() {
test('AppStorage', () async { test('NeonStorage', () async {
expect(() => AppStorage.reqireDatabase, throwsA(isA<StateError>())); expect(() => NeonStorage.database, throwsA(isA<StateError>()));
SharedPreferences.setMockInitialValues({}); SharedPreferences.setMockInitialValues({});
await AppStorage.init(); await NeonStorage.init();
expect(NeonStorage.database, isA<SharedPreferences>());
});
group('AppStorage', () {
test('formatKey', () async {
var appStorage = const AppStorage(StorageKeys.accounts);
var key = appStorage.formatKey('test-key');
expect(key, 'accounts-test-key');
appStorage = const AppStorage(StorageKeys.accounts, 'test-suffix');
key = appStorage.formatKey('test-key');
expect(key, 'accounts-test-suffix-test-key');
});
test('interface', () async {
final sharedPreferences = SharedPreferencesMock();
NeonStorage.mock(sharedPreferences);
const appStorage = AppStorage(StorageKeys.accounts);
const key = 'key';
final formatedKey = appStorage.formatKey(key);
when(() => sharedPreferences.containsKey(formatedKey)).thenReturn(true);
dynamic result = appStorage.containsKey(key);
expect(result, equals(true));
verify(() => sharedPreferences.containsKey(formatedKey)).called(1);
when(() => sharedPreferences.remove(formatedKey)).thenAnswer((final _) => Future.value(false));
result = await appStorage.remove(key);
expect(result, equals(false));
verify(() => sharedPreferences.remove(formatedKey)).called(1);
when(() => sharedPreferences.getString(formatedKey)).thenReturn(null);
result = appStorage.getString(key);
expect(result, isNull);
verify(() => sharedPreferences.getString(formatedKey)).called(1);
when(() => sharedPreferences.setString(formatedKey, 'value')).thenAnswer((final _) => Future.value(false));
result = await appStorage.setString(key, 'value');
expect(result, false);
verify(() => sharedPreferences.setString(formatedKey, 'value')).called(1);
when(() => sharedPreferences.getBool(formatedKey)).thenReturn(true);
result = appStorage.getBool(key);
expect(result, equals(true));
verify(() => sharedPreferences.getBool(formatedKey)).called(1);
when(() => sharedPreferences.setBool(formatedKey, true)).thenAnswer((final _) => Future.value(true));
result = await appStorage.setBool(key, true);
expect(result, true);
verify(() => sharedPreferences.setBool(formatedKey, true)).called(1);
when(() => sharedPreferences.getStringList(formatedKey)).thenReturn(['hi there']);
result = appStorage.getStringList(key);
expect(result, equals(['hi there']));
verify(() => sharedPreferences.getStringList(formatedKey)).called(1);
when(() => sharedPreferences.setStringList(formatedKey, ['hi there']))
.thenAnswer((final _) => Future.value(false));
result = await appStorage.setStringList(key, ['hi there']);
expect(result, false);
verify(() => sharedPreferences.setStringList(formatedKey, ['hi there'])).called(1);
});
});
test('SingleValueStorage', () async {
final sharedPreferences = SharedPreferencesMock();
NeonStorage.mock(sharedPreferences);
const storage = SingleValueStorage(StorageKeys.global);
final key = StorageKeys.global.value;
when(() => sharedPreferences.containsKey(key)).thenReturn(true);
dynamic result = storage.hasValue();
expect(result, equals(true));
verify(() => sharedPreferences.containsKey(key)).called(1);
when(() => sharedPreferences.remove(key)).thenAnswer((final _) => Future.value(false));
result = await storage.remove();
expect(result, equals(false));
verify(() => sharedPreferences.remove(key)).called(1);
when(() => sharedPreferences.getString(key)).thenReturn(null);
result = storage.getString();
expect(result, isNull);
verify(() => sharedPreferences.getString(key)).called(1);
when(() => sharedPreferences.setString(key, 'value')).thenAnswer((final _) => Future.value(false));
result = await storage.setString('value');
expect(result, false);
verify(() => sharedPreferences.setString(key, 'value')).called(1);
when(() => sharedPreferences.getBool(key)).thenReturn(true);
result = storage.getBool();
expect(result, equals(true));
verify(() => sharedPreferences.getBool(key)).called(1);
when(() => sharedPreferences.setBool(key, true)).thenAnswer((final _) => Future.value(true));
result = await storage.setBool(true);
expect(result, true);
verify(() => sharedPreferences.setBool(key, true)).called(1);
when(() => sharedPreferences.getStringList(key)).thenReturn(['hi there']);
result = storage.getStringList();
expect(result, equals(['hi there']));
verify(() => sharedPreferences.getStringList(key)).called(1);
expect(AppStorage.reqireDatabase, isA<SharedPreferences>()); when(() => sharedPreferences.setStringList(key, ['hi there'])).thenAnswer((final _) => Future.value(false));
result = await storage.setStringList(['hi there']);
expect(result, false);
verify(() => sharedPreferences.setStringList(key, ['hi there'])).called(1);
}); });
} }

29
packages/neon/neon_files/lib/options.dart

@ -23,7 +23,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final filesSortPropertyOption = SelectOption<FilesSortProperty>( late final filesSortPropertyOption = SelectOption<FilesSortProperty>(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'files-sort-property', key: FilesOptionKeys.sortProperty,
label: (final context) => AppLocalizations.of(context).optionsFilesSortProperty, label: (final context) => AppLocalizations.of(context).optionsFilesSortProperty,
defaultValue: FilesSortProperty.name, defaultValue: FilesSortProperty.name,
values: { values: {
@ -37,7 +37,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final filesSortBoxOrderOption = SelectOption<SortBoxOrder>( late final filesSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'files-sort-box-order', key: FilesOptionKeys.sortOrder,
label: (final context) => AppLocalizations.of(context).optionsFilesSortOrder, label: (final context) => AppLocalizations.of(context).optionsFilesSortOrder,
defaultValue: SortBoxOrder.ascending, defaultValue: SortBoxOrder.ascending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
@ -46,7 +46,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final showPreviewsOption = ToggleOption( late final showPreviewsOption = ToggleOption(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'show-previews', key: FilesOptionKeys.showPreviews,
label: (final context) => AppLocalizations.of(context).optionsShowPreviews, label: (final context) => AppLocalizations.of(context).optionsShowPreviews,
defaultValue: true, defaultValue: true,
); );
@ -54,7 +54,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final uploadQueueParallelism = SelectOption<int>( late final uploadQueueParallelism = SelectOption<int>(
storage: storage, storage: storage,
category: generalCategory, category: generalCategory,
key: 'upload-queue-parallelism', key: FilesOptionKeys.uploadQueueParallelism,
label: (final context) => AppLocalizations.of(context).optionsUploadQueueParallelism, label: (final context) => AppLocalizations.of(context).optionsUploadQueueParallelism,
defaultValue: 4, defaultValue: 4,
values: { values: {
@ -67,7 +67,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final downloadQueueParallelism = SelectOption<int>( late final downloadQueueParallelism = SelectOption<int>(
storage: storage, storage: storage,
category: generalCategory, category: generalCategory,
key: 'download-queue-parallelism', key: FilesOptionKeys.downloadQueueParallelism,
label: (final context) => AppLocalizations.of(context).optionsDownloadQueueParallelism, label: (final context) => AppLocalizations.of(context).optionsDownloadQueueParallelism,
defaultValue: 4, defaultValue: 4,
values: { values: {
@ -97,7 +97,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final uploadSizeWarning = SelectOption<int?>( late final uploadSizeWarning = SelectOption<int?>(
storage: storage, storage: storage,
category: generalCategory, category: generalCategory,
key: 'upload-size-warning', key: FilesOptionKeys.uploadQueueParallelism,
label: (final context) => AppLocalizations.of(context).optionsUploadSizeWarning, label: (final context) => AppLocalizations.of(context).optionsUploadSizeWarning,
defaultValue: _mb(10), defaultValue: _mb(10),
values: _sizeWarningValues, values: _sizeWarningValues,
@ -106,13 +106,28 @@ class FilesAppSpecificOptions extends NextcloudAppOptions {
late final downloadSizeWarning = SelectOption<int?>( late final downloadSizeWarning = SelectOption<int?>(
storage: storage, storage: storage,
category: generalCategory, category: generalCategory,
key: 'download-size-warning', key: FilesOptionKeys.downloadSizeWarning,
label: (final context) => AppLocalizations.of(context).optionsDownloadSizeWarning, label: (final context) => AppLocalizations.of(context).optionsDownloadSizeWarning,
defaultValue: _mb(10), defaultValue: _mb(10),
values: _sizeWarningValues, values: _sizeWarningValues,
); );
} }
enum FilesOptionKeys implements Storable {
sortProperty._('files-sort-property'),
sortOrder._('files-sort-box-order'),
showPreviews._('show-previews'),
uploadQueueParallelism._('upload-queue-parallelism'),
downloadQueueParallelism._('download-queue-parallelism'),
uploadSizeWarning._('upload-size-warning'),
downloadSizeWarning._('download-size-warning');
const FilesOptionKeys._(this.value);
@override
final String value;
}
enum FilesSortProperty { enum FilesSortProperty {
name, name,
modifiedDate, modifiedDate,

41
packages/neon/neon_news/lib/options.dart

@ -42,7 +42,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final defaultCategoryOption = SelectOption<DefaultCategory>( late final defaultCategoryOption = SelectOption<DefaultCategory>(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'default-category', key: NewsOptionKeys.defaultCategory,
label: (final context) => AppLocalizations.of(context).optionsDefaultCategory, label: (final context) => AppLocalizations.of(context).optionsDefaultCategory,
defaultValue: DefaultCategory.articles, defaultValue: DefaultCategory.articles,
values: { values: {
@ -55,7 +55,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final articleViewTypeOption = SelectOption<ArticleViewType>( late final articleViewTypeOption = SelectOption<ArticleViewType>(
storage: super.storage, storage: super.storage,
category: articlesCategory, category: articlesCategory,
key: 'article-view-type', key: NewsOptionKeys.articleViewType,
label: (final context) => AppLocalizations.of(context).optionsArticleViewType, label: (final context) => AppLocalizations.of(context).optionsArticleViewType,
defaultValue: ArticleViewType.direct, defaultValue: ArticleViewType.direct,
values: { values: {
@ -71,7 +71,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final articleDisableMarkAsReadTimeoutOption = ToggleOption( late final articleDisableMarkAsReadTimeoutOption = ToggleOption(
storage: super.storage, storage: super.storage,
category: articlesCategory, category: articlesCategory,
key: 'article-disable-mark-as-read-timeout', key: NewsOptionKeys.articleDisableMarkAsReadTimeout,
label: (final context) => AppLocalizations.of(context).optionsArticleDisableMarkAsReadTimeout, label: (final context) => AppLocalizations.of(context).optionsArticleDisableMarkAsReadTimeout,
defaultValue: false, defaultValue: false,
); );
@ -79,7 +79,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final defaultArticlesFilterOption = SelectOption<FilterType>( late final defaultArticlesFilterOption = SelectOption<FilterType>(
storage: super.storage, storage: super.storage,
category: articlesCategory, category: articlesCategory,
key: 'default-articles-filter', key: NewsOptionKeys.defaultArticlesFilter,
label: (final context) => AppLocalizations.of(context).optionsDefaultArticlesFilter, label: (final context) => AppLocalizations.of(context).optionsDefaultArticlesFilter,
defaultValue: FilterType.unread, defaultValue: FilterType.unread,
values: { values: {
@ -92,7 +92,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final articlesSortPropertyOption = SelectOption<ArticlesSortProperty>( late final articlesSortPropertyOption = SelectOption<ArticlesSortProperty>(
storage: super.storage, storage: super.storage,
category: articlesCategory, category: articlesCategory,
key: 'articles-sort-property', key: NewsOptionKeys.articlesSortProperty,
label: (final context) => AppLocalizations.of(context).optionsArticlesSortProperty, label: (final context) => AppLocalizations.of(context).optionsArticlesSortProperty,
defaultValue: ArticlesSortProperty.publishDate, defaultValue: ArticlesSortProperty.publishDate,
values: { values: {
@ -107,7 +107,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final articlesSortBoxOrderOption = SelectOption<SortBoxOrder>( late final articlesSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: articlesCategory, category: articlesCategory,
key: 'articles-sort-box-order', key: NewsOptionKeys.articlesSortBoxOrder,
label: (final context) => AppLocalizations.of(context).optionsArticlesSortOrder, label: (final context) => AppLocalizations.of(context).optionsArticlesSortOrder,
defaultValue: SortBoxOrder.descending, defaultValue: SortBoxOrder.descending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
@ -116,7 +116,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final foldersSortPropertyOption = SelectOption<FoldersSortProperty>( late final foldersSortPropertyOption = SelectOption<FoldersSortProperty>(
storage: super.storage, storage: super.storage,
category: foldersCategory, category: foldersCategory,
key: 'folders-sort-property', key: NewsOptionKeys.foldersSortProperty,
label: (final context) => AppLocalizations.of(context).optionsFoldersSortProperty, label: (final context) => AppLocalizations.of(context).optionsFoldersSortProperty,
defaultValue: FoldersSortProperty.alphabetical, defaultValue: FoldersSortProperty.alphabetical,
values: { values: {
@ -130,7 +130,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final foldersSortBoxOrderOption = SelectOption<SortBoxOrder>( late final foldersSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: foldersCategory, category: foldersCategory,
key: 'folders-sort-box-order', key: NewsOptionKeys.foldersSortBoxOrder,
label: (final context) => AppLocalizations.of(context).optionsFoldersSortOrder, label: (final context) => AppLocalizations.of(context).optionsFoldersSortOrder,
defaultValue: SortBoxOrder.ascending, defaultValue: SortBoxOrder.ascending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
@ -139,7 +139,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final defaultFolderViewTypeOption = SelectOption<DefaultFolderViewType>( late final defaultFolderViewTypeOption = SelectOption<DefaultFolderViewType>(
storage: super.storage, storage: super.storage,
category: foldersCategory, category: foldersCategory,
key: 'default-folder-view-type', key: NewsOptionKeys.defaultFolderViewType,
label: (final context) => AppLocalizations.of(context).optionsDefaultFolderViewType, label: (final context) => AppLocalizations.of(context).optionsDefaultFolderViewType,
defaultValue: DefaultFolderViewType.articles, defaultValue: DefaultFolderViewType.articles,
values: { values: {
@ -151,7 +151,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final feedsSortPropertyOption = SelectOption<FeedsSortProperty>( late final feedsSortPropertyOption = SelectOption<FeedsSortProperty>(
storage: super.storage, storage: super.storage,
category: feedsCategory, category: feedsCategory,
key: 'feeds-sort-property', key: NewsOptionKeys.feedsSortProperty,
label: (final context) => AppLocalizations.of(context).optionsFeedsSortProperty, label: (final context) => AppLocalizations.of(context).optionsFeedsSortProperty,
defaultValue: FeedsSortProperty.alphabetical, defaultValue: FeedsSortProperty.alphabetical,
values: { values: {
@ -165,13 +165,32 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
late final feedsSortBoxOrderOption = SelectOption<SortBoxOrder>( late final feedsSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: feedsCategory, category: feedsCategory,
key: 'feeds-sort-box-order', key: NewsOptionKeys.feedsSortBoxOrder,
label: (final context) => AppLocalizations.of(context).optionsFeedsSortOrder, label: (final context) => AppLocalizations.of(context).optionsFeedsSortOrder,
defaultValue: SortBoxOrder.ascending, defaultValue: SortBoxOrder.ascending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
); );
} }
enum NewsOptionKeys implements Storable {
defaultCategory._('default-category'),
articleViewType._('article-view-type'),
articleDisableMarkAsReadTimeout._('article-disable-mark-as-read-timeout'),
defaultArticlesFilter._('default-articles-filter'),
articlesSortProperty._('articles-sort-property'),
articlesSortBoxOrder._('articles-sort-box-order'),
foldersSortProperty._('folders-sort-property'),
foldersSortBoxOrder._('folders-sort-box-order'),
defaultFolderViewType._('default-folder-view-type'),
feedsSortProperty._('feeds-sort-property'),
feedsSortBoxOrder._('feeds-sort-box-order');
const NewsOptionKeys._(this.value);
@override
final String value;
}
enum DefaultCategory { enum DefaultCategory {
articles, articles,
folders, folders,

26
packages/neon/neon_notes/lib/options.dart

@ -32,7 +32,7 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final defaultCategoryOption = SelectOption<DefaultCategory>( late final defaultCategoryOption = SelectOption<DefaultCategory>(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'default-category', key: NotesOptionKeys.defaultCategory,
label: (final context) => AppLocalizations.of(context).optionsDefaultCategory, label: (final context) => AppLocalizations.of(context).optionsDefaultCategory,
defaultValue: DefaultCategory.notes, defaultValue: DefaultCategory.notes,
values: { values: {
@ -44,7 +44,7 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final defaultNoteViewTypeOption = SelectOption<DefaultNoteViewType>( late final defaultNoteViewTypeOption = SelectOption<DefaultNoteViewType>(
storage: super.storage, storage: super.storage,
category: generalCategory, category: generalCategory,
key: 'default-note-view-type', key: NotesOptionKeys.defaultNoteViewType,
label: (final context) => AppLocalizations.of(context).optionsDefaultNoteViewType, label: (final context) => AppLocalizations.of(context).optionsDefaultNoteViewType,
defaultValue: DefaultNoteViewType.preview, defaultValue: DefaultNoteViewType.preview,
values: { values: {
@ -56,7 +56,7 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final notesSortPropertyOption = SelectOption<NotesSortProperty>( late final notesSortPropertyOption = SelectOption<NotesSortProperty>(
storage: super.storage, storage: super.storage,
category: notesCategory, category: notesCategory,
key: 'notes-sort-property', key: NotesOptionKeys.notesSortProperty,
label: (final context) => AppLocalizations.of(context).optionsNotesSortProperty, label: (final context) => AppLocalizations.of(context).optionsNotesSortProperty,
defaultValue: NotesSortProperty.lastModified, defaultValue: NotesSortProperty.lastModified,
values: { values: {
@ -70,7 +70,7 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final notesSortBoxOrderOption = SelectOption<SortBoxOrder>( late final notesSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: notesCategory, category: notesCategory,
key: 'notes-sort-box-order', key: NotesOptionKeys.notesSortBoxOrder,
label: (final context) => AppLocalizations.of(context).optionsNotesSortOrder, label: (final context) => AppLocalizations.of(context).optionsNotesSortOrder,
defaultValue: SortBoxOrder.descending, defaultValue: SortBoxOrder.descending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
@ -79,7 +79,7 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final categoriesSortPropertyOption = SelectOption<CategoriesSortProperty>( late final categoriesSortPropertyOption = SelectOption<CategoriesSortProperty>(
storage: super.storage, storage: super.storage,
category: categoriesCategory, category: categoriesCategory,
key: 'categories-sort-property', key: NotesOptionKeys.categoriesSortProperty,
label: (final context) => AppLocalizations.of(context).optionsCategoriesSortProperty, label: (final context) => AppLocalizations.of(context).optionsCategoriesSortProperty,
defaultValue: CategoriesSortProperty.alphabetical, defaultValue: CategoriesSortProperty.alphabetical,
values: { values: {
@ -93,13 +93,27 @@ class NotesAppSpecificOptions extends NextcloudAppOptions {
late final categoriesSortBoxOrderOption = SelectOption<SortBoxOrder>( late final categoriesSortBoxOrderOption = SelectOption<SortBoxOrder>(
storage: super.storage, storage: super.storage,
category: categoriesCategory, category: categoriesCategory,
key: 'categories-sort-box-order', key: NotesOptionKeys.categoriesSortBoxOrder,
label: (final context) => AppLocalizations.of(context).optionsCategoriesSortOrder, label: (final context) => AppLocalizations.of(context).optionsCategoriesSortOrder,
defaultValue: SortBoxOrder.ascending, defaultValue: SortBoxOrder.ascending,
values: sortBoxOrderOptionValues, values: sortBoxOrderOptionValues,
); );
} }
enum NotesOptionKeys implements Storable {
defaultCategory._('default-category'),
defaultNoteViewType._('default-note-view-type'),
notesSortProperty._('notes-sort-property'),
notesSortBoxOrder._('notes-sort-box-order'),
categoriesSortProperty._('categories-sort-property'),
categoriesSortBoxOrder._('categories-sort-box-order');
const NotesOptionKeys._(this.value);
@override
final String value;
}
enum DefaultNoteViewType { enum DefaultNoteViewType {
preview, preview,
edit, edit,

Loading…
Cancel
Save