Browse Source

Merge pull request #1030 from nextcloud/doc/neon_settings

docs(neon): document neon settings
pull/1031/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
16afa09c02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      packages/neon/neon/lib/src/settings/models/option.dart
  2. 4
      packages/neon/neon/lib/src/settings/models/options_category.dart
  3. 4
      packages/neon/neon/lib/src/settings/models/options_collection.dart
  4. 116
      packages/neon/neon/lib/src/settings/models/storage.dart
  5. 3
      packages/neon/neon/lib/src/settings/utils/settings_export_helper.dart
  6. 6
      packages/neon/neon/lib/src/settings/widgets/account_settings_tile.dart

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

@ -97,7 +97,7 @@ sealed class Option<T> extends ChangeNotifier implements ValueListenable<T>, Dis
notifyListeners(); notifyListeners();
} }
/// Resets the option to its [default] value. /// Resets the option to its [defaultValue] value.
@mustBeOverridden @mustBeOverridden
void reset() { void reset() {
value = defaultValue; value = defaultValue;
@ -216,6 +216,9 @@ class SelectOption<T> extends Option<T> {
/// * [value] for the currently selected one /// * [value] for the currently selected one
Map<T, LabelBuilder> get values => _values; Map<T, LabelBuilder> get values => _values;
/// Updates the collection of possible values.
///
/// It is up to the caller to also change the [value] if it is no longer supported.
set values(final Map<T, LabelBuilder> newValues) { set values(final Map<T, LabelBuilder> newValues) {
if (_values == newValues) { if (_values == newValues) {
return; return;

4
packages/neon/neon/lib/src/settings/models/options_category.dart

@ -1,11 +1,15 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/src/models/label_builder.dart'; import 'package:neon/src/models/label_builder.dart';
import 'package:neon/src/settings/models/option.dart';
/// Category of an [Option].
@immutable @immutable
class OptionsCategory { class OptionsCategory {
/// Creates a new Category.
const OptionsCategory({ const OptionsCategory({
required this.name, required this.name,
}); });
/// Builder function for the category name.
final LabelBuilder name; final LabelBuilder name;
} }

4
packages/neon/neon/lib/src/settings/models/options_collection.dart

@ -7,6 +7,7 @@ import 'package:neon/src/settings/models/storage.dart';
/// Collection of [Option]s. /// Collection of [Option]s.
abstract class OptionsCollection implements Exportable, Disposable { abstract class OptionsCollection implements Exportable, Disposable {
/// Creates a new collection of options.
OptionsCollection(this.storage); OptionsCollection(this.storage);
/// Storage backend to use. /// Storage backend to use.
@ -56,8 +57,9 @@ abstract class OptionsCollection implements Exportable, Disposable {
} }
} }
/// OptionsCollection for a neon app. /// OptionsCollection primarily used by `AppImplementation`s.
abstract class NextcloudAppOptions extends OptionsCollection { abstract class NextcloudAppOptions extends OptionsCollection {
/// Creates a new Nextcloud options collection.
NextcloudAppOptions(super.storage); NextcloudAppOptions(super.storage);
/// Collection of categories to display the options in the settings. /// Collection of categories to display the options in the settings.

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

@ -2,32 +2,87 @@ import 'package:meta/meta.dart';
import 'package:nextcloud/ids.dart'; import 'package:nextcloud/ids.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
/// Storage interface used by `Option`s.
///
/// Mimics the interface of [SharedPreferences].
///
/// See:
/// * [SingleValueStorage] for a storage that saves a single value.
/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface.
/// * [NeonStorage] that manages the storage backend.
@internal @internal
abstract interface class SettingsStorage { abstract interface class SettingsStorage {
/// {@template NeonStorage.getString}
/// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`.
/// {@endtemplate}
String? getString(final String key); String? getString(final String key);
/// {@template NeonStorage.setString}
/// Saves a `String` [value] to persistent storage in the background.
///
/// Note: Due to limitations in Android's SharedPreferences,
/// values cannot start with any one of the following:
///
/// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu'
/// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy'
/// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu'
/// {@endtemplate}
Future<bool> setString(final String key, final String value); Future<bool> setString(final String key, final String value);
/// {@template NeonStorage.getBool}
/// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`.
/// {@endtemplate}
bool? getBool(final String key); bool? getBool(final String key);
/// {@template NeonStorage.setBool}
/// Saves a `bool` [value] to persistent storage in the background.
/// {@endtemplate}
// ignore: avoid_positional_boolean_parameters
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters
Future<bool> setBool(final String key, final bool value); Future<bool> setBool(final String key, final bool value);
/// {@template NeonStorage.remove}
/// Removes an entry from persistent storage.
/// {@endtemplate}
Future<bool> remove(final String key); Future<bool> remove(final String key);
} }
/// Interface of a storable element.
///
/// Usually used in enhanced enums to ensure uniqueness of the storage keys.
abstract interface class Storable { abstract interface class Storable {
/// The key of this storage element.
String get value; String get value;
} }
/// Unique storage keys.
///
/// Required by the users of the [NeonStorage] storage backend.
///
/// See:
/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface.
/// * [SettingsStorage] for the public interface used in `Option`s.
@internal @internal
enum StorageKeys implements Storable { enum StorageKeys implements Storable {
/// The key for the `AppImplementation`s.
apps._('app'), apps._('app'),
/// The key for the `Account`s and their `AccountSpecificOptions`.
accounts._('accounts'), accounts._('accounts'),
/// The key for the `GlobalOptions`.
global._('global'), global._('global'),
/// The key for the `AccountsBloc` last used account.
lastUsedAccount._('last-used-account'), lastUsedAccount._('last-used-account'),
/// The key used by the `PushNotificationsBloc` to persist the last used endpoint.
lastEndpoint._('last-endpoint'), lastEndpoint._('last-endpoint'),
/// The key for the `FirstLaunchBloc`.
firstLaunch._('first-launch'), firstLaunch._('first-launch'),
/// The key for the `PushUtils`.
notifications._(AppIDs.notifications); notifications._(AppIDs.notifications);
const StorageKeys._(this.value); const StorageKeys._(this.value);
@ -36,6 +91,14 @@ enum StorageKeys implements Storable {
final String value; final String value;
} }
/// Neon storage that manages the storage backend.
///
/// [init] must be called and completed before accessing individual storages.
///
/// See:
/// * [SingleValueStorage] for a storage that saves a single value.
/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface.
/// * [SettingsStorage] for the public interface used in `Option`s.
@internal @internal
final class NeonStorage { final class NeonStorage {
const NeonStorage._(); const NeonStorage._();
@ -46,6 +109,7 @@ final class NeonStorage {
/// Make sure it has been initialized with [init] before. /// Make sure it has been initialized with [init] before.
static SharedPreferences? _sharedPreferences; static SharedPreferences? _sharedPreferences;
/// Initializes the database instance with a mocked value.
@visibleForTesting @visibleForTesting
// ignore: use_setters_to_change_properties // ignore: use_setters_to_change_properties
static void mock(final SharedPreferences mock) => _sharedPreferences = mock; static void mock(final SharedPreferences mock) => _sharedPreferences = mock;
@ -61,6 +125,9 @@ final class NeonStorage {
_sharedPreferences = await SharedPreferences.getInstance(); _sharedPreferences = await SharedPreferences.getInstance();
} }
/// Returns the database instance.
///
/// Throws a `StateError` if [init] has not completed.
@visibleForTesting @visibleForTesting
static SharedPreferences get database { static SharedPreferences get database {
if (_sharedPreferences == null) { if (_sharedPreferences == null) {
@ -73,45 +140,85 @@ final class NeonStorage {
} }
} }
/// A storage that saves a single value.
///
/// [NeonStorage.init] must be called and completed before accessing individual values.
///
/// See:
/// * [NeonStorage] to initialize the storage backend.
/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface.
/// * [SettingsStorage] for the public interface used in `Option`s.
@immutable @immutable
@internal @internal
final class SingleValueStorage { final class SingleValueStorage {
/// Creates a new storage for a single value.
const SingleValueStorage(this.key); const SingleValueStorage(this.key);
/// The key used by the storage backend.
final StorageKeys key; final StorageKeys key;
/// {@macro NeonStorage.containsKey}
bool hasValue() => NeonStorage.database.containsKey(key.value); bool hasValue() => NeonStorage.database.containsKey(key.value);
/// {@macro NeonStorage.remove}
Future<bool> remove() => NeonStorage.database.remove(key.value); Future<bool> remove() => NeonStorage.database.remove(key.value);
/// {@macro NeonStorage.getString}
String? getString() => NeonStorage.database.getString(key.value); String? getString() => NeonStorage.database.getString(key.value);
/// {@macro NeonStorage.setString}
Future<bool> setString(final String value) => NeonStorage.database.setString(key.value, value); Future<bool> setString(final String value) => NeonStorage.database.setString(key.value, value);
/// {@macro NeonStorage.getBool}
bool? getBool() => NeonStorage.database.getBool(key.value); bool? getBool() => NeonStorage.database.getBool(key.value);
/// {@macro NeonStorage.setBool}
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters
Future<bool> setBool(final bool value) => NeonStorage.database.setBool(key.value, value); Future<bool> setBool(final bool value) => NeonStorage.database.setBool(key.value, value);
/// {@macro NeonStorage.getStringList}
List<String>? getStringList() => NeonStorage.database.getStringList(key.value); List<String>? getStringList() => NeonStorage.database.getStringList(key.value);
/// {@macro NeonStorage.setStringList}
Future<bool> setStringList(final List<String> value) => NeonStorage.database.setStringList(key.value, value); Future<bool> setStringList(final List<String> value) => NeonStorage.database.setStringList(key.value, value);
} }
/// A storage that can save a group of values.
///
/// Implements the interface of [SharedPreferences].
/// [NeonStorage.init] must be called and completed before accessing individual values.
///
/// See:
/// * [NeonStorage] to initialize the storage backend.
/// * [SingleValueStorage] for a storage that saves a single value.
/// * [SettingsStorage] for the public interface used in `Option`s.
@immutable @immutable
@internal @internal
final class AppStorage implements SettingsStorage { final class AppStorage implements SettingsStorage {
/// Creates a new app storage.
const AppStorage( const AppStorage(
this.groupKey, [ this.groupKey, [
this.suffix, this.suffix,
]); ]);
/// The group key for this app storage.
///
/// Keys are formatted with [formatKey]
final StorageKeys groupKey; final StorageKeys groupKey;
/// The optional suffix of the storage key.
///
/// Used to differentiate between multiple AppStorages with the same [groupKey].
final String? suffix; final String? suffix;
/// Returns the id for this app storage.
///
/// Uses the [suffix] and falling back to the [groupKey] if not present.
/// This uniquely identifies the storage and is used in `Exportable` classes.
String get id => suffix ?? groupKey.value; String get id => suffix ?? groupKey.value;
/// Concatenates the [groupKey], [suffix] and [key] to build a unique key
/// used in the storage backend.
@visibleForTesting @visibleForTesting
String formatKey(final String key) { String formatKey(final String key) {
if (suffix != null) { if (suffix != null) {
@ -121,6 +228,9 @@ final class AppStorage implements SettingsStorage {
return '${groupKey.value}-$key'; return '${groupKey.value}-$key';
} }
/// {@template NeonStorage.containsKey}
/// Returns true if the persistent storage contains the given [key].
/// {@endtemplate}
bool containsKey(final String key) => NeonStorage.database.containsKey(formatKey(key)); bool containsKey(final String key) => NeonStorage.database.containsKey(formatKey(key));
@override @override
@ -138,8 +248,14 @@ final class AppStorage implements SettingsStorage {
@override @override
Future<bool> setBool(final String key, final bool value) => NeonStorage.database.setBool(formatKey(key), value); Future<bool> setBool(final String key, final bool value) => NeonStorage.database.setBool(formatKey(key), value);
/// {@template NeonStorage.getStringList}
/// Reads a set of string values from persistent storage, throwing an `Exception` if it's not a `String` set.
/// {@endtemplate}
List<String>? getStringList(final String key) => NeonStorage.database.getStringList(formatKey(key)); List<String>? getStringList(final String key) => NeonStorage.database.getStringList(formatKey(key));
/// {@template NeonStorage.setStringList}
/// Saves a list of `String` [value]s to persistent storage in the background.
/// {@endtemplate}
Future<bool> setStringList(final String key, final List<String> value) => Future<bool> setStringList(final String key, final List<String> value) =>
NeonStorage.database.setStringList(formatKey(key), value); NeonStorage.database.setStringList(formatKey(key), value);
} }

3
packages/neon/neon/lib/src/settings/utils/settings_export_helper.dart

@ -22,6 +22,7 @@ import 'package:neon/src/settings/models/storage.dart';
@internal @internal
@immutable @immutable
class SettingsExportHelper { class SettingsExportHelper {
/// Creates a new settings exporter for the given [exportables].
const SettingsExportHelper({ const SettingsExportHelper({
required this.exportables, required this.exportables,
}); });
@ -77,6 +78,7 @@ class SettingsExportHelper {
@internal @internal
@immutable @immutable
class AppImplementationsExporter implements Exportable { class AppImplementationsExporter implements Exportable {
/// Creates a new [AppImplementation] exporter.
const AppImplementationsExporter(this.appImplementations); const AppImplementationsExporter(this.appImplementations);
/// List of apps to export. /// List of apps to export.
@ -113,6 +115,7 @@ class AppImplementationsExporter implements Exportable {
@internal @internal
@immutable @immutable
class AccountsBlocExporter implements Exportable { class AccountsBlocExporter implements Exportable {
/// Creates a new [Account] exporter.
const AccountsBlocExporter(this.accountsBloc); const AccountsBlocExporter(this.accountsBloc);
/// AccountsBloc containing the accounts to export. /// AccountsBloc containing the accounts to export.

6
packages/neon/neon/lib/src/settings/widgets/account_settings_tile.dart

@ -4,8 +4,10 @@ import 'package:neon/src/models/account.dart';
import 'package:neon/src/settings/widgets/settings_tile.dart'; import 'package:neon/src/settings/widgets/settings_tile.dart';
import 'package:neon/src/widgets/account_tile.dart'; import 'package:neon/src/widgets/account_tile.dart';
/// An [NeonAccountTile] used inside a settings list.
@internal @internal
class AccountSettingsTile extends SettingsTile { class AccountSettingsTile extends SettingsTile {
/// Creates a new account settings tile.
const AccountSettingsTile({ const AccountSettingsTile({
required this.account, required this.account,
this.trailing, this.trailing,
@ -13,9 +15,13 @@ class AccountSettingsTile extends SettingsTile {
super.key, super.key,
}); });
/// {@macro neon.AccountTile.account}
final Account account; final Account account;
/// {@macro neon.AccountTile.trailing}
final Widget? trailing; final Widget? trailing;
/// {@macro neon.AccountTile.onTap}
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
@override @override

Loading…
Cancel
Save