Browse Source

Merge pull request #1029 from nextcloud/doc/neon_models

docs(neon): document neon models
pull/1031/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
5b089aa684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      packages/neon/neon/lib/src/models/account.dart
  2. 1
      packages/neon/neon/lib/src/models/account_cache.dart
  3. 55
      packages/neon/neon/lib/src/models/app_implementation.dart
  4. 2
      packages/neon/neon/lib/src/models/disposable.dart
  5. 4
      packages/neon/neon/lib/src/models/label_builder.dart
  6. 11
      packages/neon/neon/lib/src/models/notifications_interface.dart

21
packages/neon/neon/lib/src/models/account.dart

@ -22,9 +22,11 @@ abstract interface class Credentials {
abstract final String? password;
}
/// Account data.
@JsonSerializable()
@immutable
class Account implements Credentials {
/// Creates a new account.
Account({
required this.serverURL,
required this.username,
@ -39,8 +41,10 @@ class Account implements Credentials {
cookieJar: CookieJar(),
);
/// Creates a new account object from the given [json] data.
factory Account.fromJson(final Map<String, dynamic> json) => _$AccountFromJson(json);
/// Parses this object into a json like map.
Map<String, dynamic> toJson() => _$AccountToJson(this);
@override
@ -49,6 +53,8 @@ class Account implements Credentials {
final String username;
@override
final String? password;
/// The user agent to use.
final String? userAgent;
@override
@ -62,8 +68,13 @@ class Account implements Credentials {
@override
int get hashCode => serverURL.hashCode + username.hashCode;
/// An authenticated API client.
final NextcloudClient client;
/// The unique ID of the account.
///
/// Implemented in a primitive way hashing the [username] and [serverURL].
/// IDs are globally cached in [_idCache].
String get id {
final key = '$username@$serverURL';
@ -108,10 +119,20 @@ class Account implements Credentials {
Uri stripUri(final Uri uri) => Uri.parse(uri.toString().replaceFirst(serverURL.toString(), ''));
}
/// Global [Account.id] cache.
Map<String, String> _idCache = {};
/// Extension to find an account by id in a Iterable.
extension AccountFind on Iterable<Account> {
/// Returns the first [Account] matching [accountID] by [Account.id].
///
/// If no `Account` was found `null` is returned.
Account? tryFind(final String? accountID) => firstWhereOrNull((final account) => account.id == accountID);
/// Returns the first [Account] matching [accountID] by [Account.id].
///
/// Throws a [StateError] if no `Account` was found.
/// Use [tryFind] to get a nullable result.
Account find(final String accountID) => firstWhere((final account) => account.id == accountID);
}

1
packages/neon/neon/lib/src/models/account_cache.dart

@ -3,6 +3,7 @@ import 'package:neon/src/models/disposable.dart';
/// Cache for [Account] specific [Disposable] objects.
class AccountCache<T extends Disposable> implements Disposable {
/// Creates a new account cache.
AccountCache();
final Map<String, T> _cache = {};

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

@ -20,18 +20,39 @@ import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:vector_graphics/vector_graphics.dart';
/// Base implementation of a Neon app.
///
/// It is mandatory to provide a precompiled SVG under `assets/app.svg.vec`.
/// SVGs can be precompiled with `https://pub.dev/packages/vector_graphics_compiler`
@immutable
abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions> implements Disposable {
/// The unique id of an app.
///
/// It is common to specify them in `AppIDs`.
String get id;
/// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
LocalizationsDelegate<Object> get localizationsDelegate;
/// {@macro flutter.widgets.widgetsApp.supportedLocales}
Iterable<Locale> get supportedLocales;
/// Default localized app name used in [name].
///
/// Defaults to the frameworks mapping of the [id] to a localized name.
String nameFromLocalization(final NeonLocalizations localizations) => localizations.appImplementationName(id);
/// Localized name of this app.
String name(final BuildContext context) => nameFromLocalization(NeonLocalizations.of(context));
/// The [SettingsStorage] for this app.
@protected
late final AppStorage storage = AppStorage(StorageKeys.apps, id);
/// The options associated with this app.
///
/// Options will be added to the settings page providing a global place to
/// adjust the behavior of an app.
@mustBeOverridden
R get options;
@ -46,13 +67,25 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
) =>
null;
/// Cache for all blocs.
///
/// To access a bloc use [getBloc] instead.
final blocsCache = AccountCache<T>();
/// Returns a bloc [T] from the [blocsCache] or builds a new one if absent.
T getBloc(final Account account) => blocsCache[account] ??= buildBloc(account);
/// Build the bloc [T] for the given [account].
///
/// Blocs are long lived and should not be rebuilt for subsequent calls.
/// Use [getBloc] which also handles caching.
@protected
T buildBloc(final Account account);
/// The [Provider] building the bloc [T] the currently active account.
///
/// Blocs will not be disposed on disposal of the provider. You must handle
/// the [blocsCache] manually.
Provider<T> get blocProvider => Provider<T>(
create: (final context) {
final accountsBloc = NeonProvider.of<AccountsBloc>(context);
@ -62,10 +95,17 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
},
);
/// The count of unread notifications.
///
/// If `null` no label will be displayed.
BehaviorSubject<int>? getUnreadCounter(final T bloc) => null;
/// The main page of this app.
///
/// The framework will insert [blocProvider] into the widget tree before.
Widget get page;
/// The drawer destination used in widgets like [NavigationDrawer].
NeonNavigationDestination destination(final BuildContext context) {
final accountsBloc = NeonProvider.of<AccountsBloc>(context);
final account = accountsBloc.activeAccount.value!;
@ -89,7 +129,7 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
/// Route for the app.
///
/// All pages of the app must be specified as subroutes.
/// All pages of the app must be specified as sub routes.
/// If this is not [GoRoute] an initial route name must be specified by overriding [initialRouteName].
RouteBase get route;
@ -106,6 +146,10 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
throw FlutterError('No name for the initial route provided.');
}
/// Builds the app icon.
///
/// It is mandatory to provide a precompiled SVG under `assets/app.svg.vec`.
/// SVGs can be precompiled with `https://pub.dev/packages/vector_graphics_compiler`
Widget buildIcon({
final double? size,
final Color? color,
@ -145,7 +189,16 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
int get hashCode => id.hashCode;
}
/// Extension to find an app implementation by id in a Iterable.
extension AppImplementationFind on Iterable<AppImplementation> {
/// Returns the first [AppImplementation] matching [appID] by [AppImplementation.id].
///
/// If no `AppImplementation` was found `null` is returned.
AppImplementation? tryFind(final String? appID) => firstWhereOrNull((final app) => app.id == appID);
/// Returns the first [AppImplementation] matching [appID] by [AppImplementation.id].
///
/// Throws a [StateError] if no `AppImplementation` was found.
/// Use [tryFind] to get a nullable result.
AppImplementation find(final String appID) => firstWhere((final app) => app.id == appID);
}

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

@ -11,6 +11,7 @@ abstract interface class Disposable {
void dispose();
}
/// Extension on [Disposable] iterables.
extension DisposableIterableBloc on Iterable<Disposable> {
/// Calls [Disposable.dispose] on all entries.
///
@ -22,6 +23,7 @@ extension DisposableIterableBloc on Iterable<Disposable> {
}
}
/// Extension on [Disposable] maps.
extension DisposableMapBloc on Map<dynamic, Disposable> {
/// Calls [Disposable.dispose] on all entries.
///

4
packages/neon/neon/lib/src/models/label_builder.dart

@ -1,3 +1,7 @@
import 'package:flutter/widgets.dart';
/// The signature of a function generating a label.
///
/// The `context` includes the [WidgetsApp]'s [Localizations] widget so that
/// this method can be used to produce a localized label.
typedef LabelBuilder = String Function(BuildContext);

11
packages/neon/neon/lib/src/models/notifications_interface.dart

@ -3,8 +3,12 @@ import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/settings/models/options_collection.dart';
/// The interface of the notifications client implementation.
///
/// Use this to access the notifications client from other Neon clients.
abstract interface class NotificationsAppInterface<T extends NotificationsBlocInterface,
R extends NotificationsOptionsInterface> extends AppImplementation<T, R> {
/// Creates a new notifications client.
NotificationsAppInterface();
@override
@ -12,13 +16,20 @@ abstract interface class NotificationsAppInterface<T extends NotificationsBlocIn
R get options => throw UnimplementedError();
}
/// The interface of the bloc used by the notifications client.
abstract interface class NotificationsBlocInterface extends InteractiveBloc {
/// Creates a new notifications bloc.
NotificationsBlocInterface(this.options);
/// The options for the notifications client.
final NotificationsOptionsInterface options;
/// Deletes the notification with the given [id].
void deleteNotification(final int id);
}
/// The interface of the app options used by the notifications client.
abstract interface class NotificationsOptionsInterface extends NextcloudAppOptions {
/// Creates the nextcloud app options for the notifications client.
NotificationsOptionsInterface(super.storage);
}

Loading…
Cancel
Save