Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/neon_framework/lib/blocs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export 'package:neon_framework/src/blocs/timer.dart';
export 'package:neon_framework/src/blocs/user_details.dart';
export 'package:neon_framework/src/blocs/user_status.dart';
export 'package:neon_framework/src/blocs/weather_status.dart';
export 'package:neon_framework/src/utils/app_capability_extension.dart';
1 change: 1 addition & 0 deletions packages/neon_framework/lib/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
library;

export 'package:account_repository/account_repository.dart' show Account;
export 'package:neon_framework/src/models/app_capabilities.dart';
export 'package:neon_framework/src/models/app_implementation.dart';
export 'package:neon_framework/src/models/label_builder.dart';
export 'package:neon_framework/src/models/notifications_interface.dart';
13 changes: 12 additions & 1 deletion packages/neon_framework/lib/src/blocs/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ abstract class AppsBloc implements InteractiveBloc {

/// Returns the active [Bloc] for every registered [AppImplementation] wrapped in a Provider.
List<Provider<Bloc>> get appBlocProviders;

/// Returns a handler for a given [AppCapability] if any of the active [AppImplementation]s provides one.
/// Current implementation will pick first available handler. Multiple handler for same [AppCapability] are not yet supported.
AppCapabilityHandler? findAppCapabilityHandler(AppCapability capability);
}

/// Implementation of [AppsBloc].
Expand Down Expand Up @@ -283,5 +287,12 @@ class _AppsBloc extends InteractiveBloc implements AppsBloc {
@override
List<Provider<Bloc>> get appBlocProviders =>
allAppImplementations.map((appImplementation) => appImplementation.blocProvider).toList();
// coverage:ignore-end
// coverage:ignore-end

@override
AppCapabilityHandler? findAppCapabilityHandler(AppCapability capability) =>
appImplementations.valueOrNull?.data
?.map((app) => app.appCapabilityHandler(capability))
.whereNot((handler) => handler == null)
.firstOrNull;
}
66 changes: 66 additions & 0 deletions packages/neon_framework/lib/src/models/app_capabilities.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:neon_framework/src/models/app_implementation.dart';
import 'package:nextcloud/webdav.dart' as webdav;

/// Defines capabilities that apps can provide handlers for, allowing Neon or other apps to delegate certain actions to apps that can handle them.
/// Capabilities do also carry information required for handling a specific action, as well as an optional result in case the capability is a [ResultCapability].
abstract class AppCapability {}

/// Certain capabilities can produce a result, for example the [DirectorySelectionCapability] allows apps to return a selected directory back to Neon or other apps.
abstract class ResultCapability<T> extends AppCapability {
/// The result of handling this capability. This can be set by the app providing the handler for this capability to return a result back to the caller.
///
/// There is no other way to provide a result to the caller.
/// A well behaved handler which produces a result should set this before returning the capability in the [AppCapabilityHandler.handle] method.
/// Awaiting the capability should always lead to the result being available.
T? result;
}

/// Apps can provide a handler for this capability to allow Neon to display images in the app instead of the system image viewer.
class ImageViewerCapability extends AppCapability {
/// Creates a new [ImageViewerCapability] for the given file and optional list of files.
ImageViewerCapability._({
required this.file,
this.files,
});

/// Creates a new [ImageViewerCapability] if the given file is an image that can be handled by this capability, otherwise returns null.
static ImageViewerCapability? fromFile({required webdav.WebDavFile file, List<webdav.WebDavFile>? files}) {
if(isImage(file)) {
return ImageViewerCapability._(file: file, files: files);
}
return null;
}

/// Checks if the given file is an image that can be handled by this capability.
static bool isImage(webdav.WebDavFile file) => file.mimeType != null && allImages.hasMatch(file.mimeType!);

/// A constant representing all image MIME types.
static RegExp allImages = RegExp('image/.*');

/// The file to be handled.
final webdav.WebDavFile file;

/// An optional list of files representing the context of the file. This allowes us to open a carusel view without the need to reload the files list.
final List<webdav.WebDavFile>? files;
}

/// Apps can provide a handler for this capability to allow Neon to delegate directory selection to the app, for example to allow users to select a directory in a file picker provided by the app instead of a system file picker.
class DirectorySelectionCapability extends ResultCapability<webdav.PathUri?> {
/// Creates a new [DirectorySelectionCapability] for the given current directory.
DirectorySelectionCapability(this.currentDirectory);

/// The currently visible directory for the directory selection.
final webdav.PathUri currentDirectory;
}

/// Base class for capabilities handling. Apps can provide handlers for specific capabilities by implementing [AppCapabilityHandler] and returning it in the [AppImplementation.appCapabilityHandler] method.
abstract class AppCapabilityHandler {
/// Checks if this handler can handle the given capability.
bool canHandle(AppCapability capability);

/// Handles the given capability and returns the passed capability back to the caller. It is good practice to return the same object back.
/// The returned capability can optionally contain a result if the capability is a [ResultCapability] and the handler sets the result before returning it.
/// The return value is mainly syntactic sugar to allow for easier handling of capabilities.
Future<C> handle<C extends AppCapability>(BuildContext context, C capability);
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,8 @@ abstract class AppImplementation<T extends Bloc, R extends AppImplementationOpti

@override
int get hashCode => id.hashCode;

/// If the app provides handling for a specific [AppCapability], it can return a handler allowing Neon to make use of it.
/// For example, this photos app provides a handler for the [ImageViewerCapability] which allows Neon to display images in the photos app instead of the system app.
AppCapabilityHandler? appCapabilityHandler(AppCapability capability) => null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/src/models/app_capabilities.dart';

/// Extension on [AppsBloc] for easier handling of app capabilities in the UI layer.
extension AppCapabilityExtension on AppsBloc {
/// Finds a handler for the given capability and invokes it, returning the result if available.
///
/// This function is syntactic sugar for easier handling of app capabilities.
/// It was implemented as extension to not expose the [AppsBloc] to the UI layer.
Future<C>? handleAppCapability<C extends AppCapability>(BuildContext context, C? capability) => capability == null ? null :
findAppCapabilityHandler(capability)?.handle(context, capability);
}