From 5dc88d50b94d3f375d5e4ad2a6a2aba1c8c47382 Mon Sep 17 00:00:00 2001 From: vauvenal5 Date: Fri, 20 Feb 2026 11:10:21 +0100 Subject: [PATCH] feat(neon_framework): add app capabilities to neon framework for further use in photos and files apps Signed-off-by: vauvenal5 --- packages/neon_framework/lib/blocs.dart | 1 + packages/neon_framework/lib/models.dart | 1 + .../neon_framework/lib/src/blocs/apps.dart | 13 +++- .../lib/src/models/app_capabilities.dart | 66 +++++++++++++++++++ .../lib/src/models/app_implementation.dart | 4 ++ .../src/utils/app_capability_extension.dart | 13 ++++ 6 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 packages/neon_framework/lib/src/models/app_capabilities.dart create mode 100644 packages/neon_framework/lib/src/utils/app_capability_extension.dart diff --git a/packages/neon_framework/lib/blocs.dart b/packages/neon_framework/lib/blocs.dart index 375537c17ed..4929f948e08 100644 --- a/packages/neon_framework/lib/blocs.dart +++ b/packages/neon_framework/lib/blocs.dart @@ -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'; diff --git a/packages/neon_framework/lib/models.dart b/packages/neon_framework/lib/models.dart index 1510c083628..20273e8747b 100644 --- a/packages/neon_framework/lib/models.dart +++ b/packages/neon_framework/lib/models.dart @@ -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'; diff --git a/packages/neon_framework/lib/src/blocs/apps.dart b/packages/neon_framework/lib/src/blocs/apps.dart index 248418781af..14f5857810d 100644 --- a/packages/neon_framework/lib/src/blocs/apps.dart +++ b/packages/neon_framework/lib/src/blocs/apps.dart @@ -56,6 +56,10 @@ abstract class AppsBloc implements InteractiveBloc { /// Returns the active [Bloc] for every registered [AppImplementation] wrapped in a Provider. List> 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]. @@ -283,5 +287,12 @@ class _AppsBloc extends InteractiveBloc implements AppsBloc { @override List> 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; } diff --git a/packages/neon_framework/lib/src/models/app_capabilities.dart b/packages/neon_framework/lib/src/models/app_capabilities.dart new file mode 100644 index 00000000000..9891222a62e --- /dev/null +++ b/packages/neon_framework/lib/src/models/app_capabilities.dart @@ -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 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? 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? 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 { + /// 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 handle(BuildContext context, C capability); +} diff --git a/packages/neon_framework/lib/src/models/app_implementation.dart b/packages/neon_framework/lib/src/models/app_implementation.dart index c9f7cf1af34..3f5cba0eb8c 100644 --- a/packages/neon_framework/lib/src/models/app_implementation.dart +++ b/packages/neon_framework/lib/src/models/app_implementation.dart @@ -194,4 +194,8 @@ abstract class AppImplementation 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; } diff --git a/packages/neon_framework/lib/src/utils/app_capability_extension.dart b/packages/neon_framework/lib/src/utils/app_capability_extension.dart new file mode 100644 index 00000000000..f220dde157b --- /dev/null +++ b/packages/neon_framework/lib/src/utils/app_capability_extension.dart @@ -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? handleAppCapability(BuildContext context, C? capability) => capability == null ? null : + findAppCapabilityHandler(capability)?.handle(context, capability); +}