diff --git a/Action Assistant/ActionViewController.swift b/Action Assistant/ActionViewController.swift
new file mode 100644
index 0000000000..8109425769
--- /dev/null
+++ b/Action Assistant/ActionViewController.swift
@@ -0,0 +1,148 @@
+//
+// ActionViewController.swift
+// Action Assistant
+//
+// Created by Marino Faggiana on 14/05/2026.
+// Copyright © 2026 Marino Faggiana. All rights reserved.
+//
+
+import UIKit
+import NextcloudKit
+import UniformTypeIdentifiers
+
+final class ActionViewController: UIViewController {
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.isHidden = true
+ view.alpha = 0
+ view.backgroundColor = .clear
+ preferredContentSize = .zero
+
+ Task {
+ await handleAction()
+ }
+ }
+
+ private func handleAction() async {
+ guard let text = await loadText() else {
+ extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ return
+ }
+
+ NCAssistantSharedTextStore.save(text)
+ openMainAppForAssistantSharedText()
+ }
+
+ private func loadText() async -> String? {
+ guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
+ return nil
+ }
+
+ for extensionItem in extensionItems {
+ guard let attachments = extensionItem.attachments else {
+ continue
+ }
+
+ for provider in attachments {
+ if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
+ return await loadText(from: provider, typeIdentifier: UTType.plainText.identifier)
+ }
+
+ if provider.hasItemConformingToTypeIdentifier(UTType.utf8PlainText.identifier) {
+ return await loadText(from: provider, typeIdentifier: UTType.utf8PlainText.identifier)
+ }
+
+ if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
+ return await loadText(from: provider, typeIdentifier: UTType.text.identifier)
+ }
+ }
+ }
+
+ return nil
+ }
+
+ private func loadText(from provider: NSItemProvider, typeIdentifier: String) async -> String? {
+ await withCheckedContinuation { continuation in
+ provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, _ in
+ let text: String?
+
+ if let string = item as? String {
+ text = string
+ } else if let attributedString = item as? NSAttributedString {
+ text = attributedString.string
+ } else if let data = item as? Data {
+ text = String(data: data, encoding: .utf8)
+ } else if let url = item as? URL {
+ text = try? String(contentsOf: url, encoding: .utf8)
+ } else {
+ text = nil
+ }
+
+ guard let text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
+ continuation.resume(returning: nil)
+ return
+ }
+
+ continuation.resume(returning: text)
+ }
+ }
+ }
+
+ private func openMainAppForAssistantSharedText() {
+ guard let url = URL(string: "nextcloud://assistant/shared-text") else {
+ extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ return
+ }
+
+ openAssistantSharedTextURLThroughResponderChain(url)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+ self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ }
+ }
+
+ /// Opens the Assistant shared-text deep link from the Share extension.
+ ///
+ /// Share extensions cannot use `UIApplication.shared` directly because it is not
+ /// extension-safe. This method walks the responder chain until it finds the hidden
+ /// `UIApplication` responder and invokes the modern `open(_:options:completionHandler:)`
+ /// Objective-C selector dynamically.
+ ///
+ /// This is intentionally isolated because it relies on Objective-C runtime dispatch.
+ ///
+ /// - Parameter url: Deep link URL to open in the containing application.
+ private func openAssistantSharedTextURLThroughResponderChain(_ url: URL) {
+ let selector = NSSelectorFromString("openURL:options:completionHandler:")
+ let applicationClass: AnyClass? = NSClassFromString("UIApplication")
+ var responder: UIResponder? = self
+
+ while let currentResponder = responder {
+ guard let applicationClass,
+ currentResponder.isKind(of: applicationClass),
+ currentResponder.responds(to: selector),
+ let implementation = currentResponder.method(for: selector) else {
+ responder = currentResponder.next
+ continue
+ }
+
+ typealias CompletionBlock = @convention(block) (Bool) -> Void
+ typealias OpenURLFunction = @convention(c) (AnyObject, Selector, NSURL, NSDictionary, CompletionBlock?) -> Void
+
+ let openURL = unsafeBitCast(implementation, to: OpenURLFunction.self)
+
+ let completion: CompletionBlock = { success in
+ if success {
+ nkLog(debug: "Assistant shared text deep link performed through modern responder chain")
+ } else {
+ nkLog(error: "Assistant shared text deep link modern responder chain returned false")
+ }
+ }
+
+ openURL(currentResponder, selector, url as NSURL, NSDictionary(), completion)
+ return
+ }
+
+ nkLog(error: "Assistant shared text deep link failed because no UIApplication responder can open URL")
+ }
+}
diff --git a/Action Assistant/Base.lproj/MainInterface.storyboard b/Action Assistant/Base.lproj/MainInterface.storyboard
new file mode 100644
index 0000000000..816db3336e
--- /dev/null
+++ b/Action Assistant/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Action Assistant/Images.xcassets/AppIcon.appiconset/Contents.json b/Action Assistant/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..8f31f592a8
--- /dev/null
+++ b/Action Assistant/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "images" : [
+ {
+ "filename" : "Senza titolo.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "Senza titolo 1.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "filename" : "Senza titolo 2.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 1.png b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 1.png
new file mode 100644
index 0000000000..a1fe849708
Binary files /dev/null and b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 1.png differ
diff --git a/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 2.png b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 2.png
new file mode 100644
index 0000000000..a1fe849708
Binary files /dev/null and b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo 2.png differ
diff --git a/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo.png b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo.png
new file mode 100644
index 0000000000..a1fe849708
Binary files /dev/null and b/Action Assistant/Images.xcassets/AppIcon.appiconset/Senza titolo.png differ
diff --git a/Action Assistant/Images.xcassets/Contents.json b/Action Assistant/Images.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/Action Assistant/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Brand/Action_Assistant.entitlements b/Brand/Action_Assistant.entitlements
new file mode 100644
index 0000000000..4ecc3f0d13
--- /dev/null
+++ b/Brand/Action_Assistant.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.it.twsweb.Crypto-Cloud
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)it.twsweb.Crypto-Cloud
+
+
+
diff --git a/Brand/Action_Assistant.plist b/Brand/Action_Assistant.plist
new file mode 100644
index 0000000000..8fe4c9c6c9
--- /dev/null
+++ b/Brand/Action_Assistant.plist
@@ -0,0 +1,23 @@
+
+
+
+
+ CFBundleDisplayName
+ Nextcloud Assistant
+
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionActivationRule
+ TRUEPREDICATE
+
+
+ NSExtensionMainStoryboard
+ MainInterface
+
+ NSExtensionPointIdentifier
+ com.apple.ui-services
+
+
+
diff --git a/Brand/Share.plist b/Brand/Share.plist
index 567991a236..ec2daea2b1 100755
--- a/Brand/Share.plist
+++ b/Brand/Share.plist
@@ -30,8 +30,7 @@
NSExtensionAttributes
NSExtensionActivationRule
- SUBQUERY (extensionItems, $extensionItem, SUBQUERY ($extensionItem.attachments,$attachment,(ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data")).@count == $extensionItem.attachments.@count).@count > 0
-
+ SUBQUERY (extensionItems, $extensionItem, SUBQUERY ($extensionItem.attachments, $attachment, (ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" OR ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text")).@count == $extensionItem.attachments.@count).@count > 0
NSExtensionMainStoryboard
MainInterface
@@ -39,4 +38,4 @@
com.apple.share-services
-
+
\ No newline at end of file
diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj
index 8bd5feb863..de48cf1181 100644
--- a/Nextcloud.xcodeproj/project.pbxproj
+++ b/Nextcloud.xcodeproj/project.pbxproj
@@ -304,7 +304,7 @@
F72CA05C2F5051DB002E2F06 /* AlertActionBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */; };
F72CD63A25C19EBF00F46F9A /* NCAutoUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */; };
F72D1007210B6882009C96B7 /* NCPushNotificationEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */; };
- F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */; };
+ F72D404923D2082500A97FD0 /* NCViewerDirectEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */; };
F72D7EB7263B1207000B3DFC /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72D7EB6263B1207000B3DFC /* MarkdownKit */; };
F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */ = {isa = PBXBuildFile; productRef = F72DA9B325F53E4E00B87DB1 /* SwiftRichString */; };
F72EA95228B7BA2A00C88F0C /* DashboardWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EA95128B7BA2A00C88F0C /* DashboardWidgetProvider.swift */; };
@@ -339,7 +339,7 @@
F7386E482DA90E0F009A00F6 /* NCAppVersionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7386E452DA90E02009A00F6 /* NCAppVersionManager.swift */; };
F73BC74F2F23811E003170C2 /* WarningBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7DF7B3E2F1A2EE400514020 /* WarningBannerView.swift */; };
F73CB3B222E072A000AD728E /* NCShareHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F73CB3B122E072A000AD728E /* NCShareHeaderView.xib */; };
- F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */; };
+ F73D11FA253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */; };
F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; };
F73EF7A82B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; };
F73EF7A92B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; };
@@ -430,6 +430,7 @@
F758B45A212C564000515F55 /* NCScan.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F758B457212C564000515F55 /* NCScan.storyboard */; };
F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45D212C569C00515F55 /* NCScanCell.swift */; };
F758B460212C56A400515F55 /* NCScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45F212C56A400515F55 /* NCScan.swift */; };
+ F75A60552FB4493A00F8247E /* NCDirectEditorAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */; };
F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
F75C0C4823D1FAE300163CC8 /* NCRichWorkspaceCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75C0C4723D1FAE300163CC8 /* NCRichWorkspaceCommon.swift */; };
@@ -740,6 +741,18 @@
F7C30DFE291BD0B80017149B /* NCNetworkingE2EEDelete.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C30DFC291BD0B80017149B /* NCNetworkingE2EEDelete.swift */; };
F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C30DFF291BD2610017149B /* NCNetworkingE2EERename.swift */; };
F7C30E01291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C30DFF291BD2610017149B /* NCNetworkingE2EERename.swift */; };
+ F7C55C512FB4A658004A974F /* NCAssistantInputModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C55C502FB4A651004A974F /* NCAssistantInputModel.swift */; };
+ F7C55C7C2FB5AEF7004A974F /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7C55C7B2FB5AEF7004A974F /* UniformTypeIdentifiers.framework */; };
+ F7C55C882FB5AEF7004A974F /* Action Assistant.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F7C55C7A2FB5AEF7004A974F /* Action Assistant.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ F7C55C8D2FB5B02C004A974F /* NCAssistantSharedTextStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FFFC9D2FB300440015441E /* NCAssistantSharedTextStore.swift */; };
+ F7C55C8E2FB5B03D004A974F /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
+ F7C55C8F2FB5B045004A974F /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; };
+ F7C55C9A2FB5B127004A974F /* ThreadSafeDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7245923289BB50B00474787 /* ThreadSafeDictionary.swift */; };
+ F7C55C9B2FB5B1A7004A974F /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */; };
+ F7C55C9F2FB5B83A004A974F /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F7C55C9E2FB5B83A004A974F /* NextcloudKit */; };
+ F7C55CC92FB5CE74004A974F /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C55CC32FB5CE74004A974F /* ActionViewController.swift */; };
+ F7C55CCA2FB5CE74004A974F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7C55CC42FB5CE74004A974F /* Images.xcassets */; };
+ F7C55CCC2FB5CE74004A974F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7C55CC72FB5CE74004A974F /* MainInterface.storyboard */; };
F7C687E92D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */; };
F7C687EA2D22BDE5004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */; };
F7C687EB2D22BDE5004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */; };
@@ -926,6 +939,9 @@
F7FDFF702E437E55000D7688 /* NCAccountRequest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7FDFF512E437E55000D7688 /* NCAccountRequest.storyboard */; };
F7FDFF722E437E55000D7688 /* NCAccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FDFF522E437E55000D7688 /* NCAccountRequest.swift */; };
F7FF2CB12842159500EBB7A1 /* NCSectionHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7FF2CB02842159500EBB7A1 /* NCSectionHeader.xib */; };
+ F7FFFCA02FB300440015441E /* NCAssistantSharedTextStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FFFC9D2FB300440015441E /* NCAssistantSharedTextStore.swift */; };
+ F7FFFCA22FB300600015441E /* NCAssistantSharedTextStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FFFC9D2FB300440015441E /* NCAssistantSharedTextStore.swift */; };
+ F7FFFCA42FB3088E0015441E /* NCShareExtension+Assistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FFFCA32FB3088D0015441E /* NCShareExtension+Assistant.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -985,6 +1001,13 @@
remoteGlobalIDString = F771E3CF20E2392D00AFB62D;
remoteInfo = "File Provider Extension";
};
+ F7C55C862FB5AEF7004A974F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = F7F67BA01A24D27800EE80DA /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = F7C55C792FB5AEF7004A974F;
+ remoteInfo = "Action Assistant";
+ };
F7C9739728F17131002C43E2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F7F67BA01A24D27800EE80DA /* Project object */;
@@ -1108,6 +1131,7 @@
2C33C48623E2C475005F963B /* Notification Service Extension.appex in Embed Foundation Extensions */,
F7C9739928F17131002C43E2 /* WidgetDashboardIntentHandler.appex in Embed Foundation Extensions */,
F7346E1C28B0EF5E006CE2D2 /* Widget.appex in Embed Foundation Extensions */,
+ F7C55C882FB5AEF7004A974F /* Action Assistant.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@@ -1370,7 +1394,7 @@
F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUpload.swift; sourceTree = ""; };
F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCPushNotificationEncryption.m; sourceTree = ""; };
F72D1006210B6882009C96B7 /* NCPushNotificationEncryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCPushNotificationEncryption.h; sourceTree = ""; };
- F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerNextcloudText.swift; sourceTree = ""; };
+ F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerDirectEditing.swift; sourceTree = ""; };
F72EA95128B7BA2A00C88F0C /* DashboardWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardWidgetProvider.swift; sourceTree = ""; };
F72EA95328B7BABA00C88F0C /* FilesWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesWidgetProvider.swift; sourceTree = ""; };
F72EA95728B7BC4F00C88F0C /* FilesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesData.swift; sourceTree = ""; };
@@ -1397,7 +1421,7 @@
F7386E452DA90E02009A00F6 /* NCAppVersionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAppVersionManager.swift; sourceTree = ""; };
F73CB3B122E072A000AD728E /* NCShareHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareHeaderView.xib; sourceTree = ""; };
F73CB5771ED46807005F2A5A /* NCBridgeSwift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCBridgeSwift.h; sourceTree = ""; };
- F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewerNextcloudText.storyboard; sourceTree = ""; };
+ F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewerDirectEditing.storyboard; sourceTree = ""; };
F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Comments.swift"; sourceTree = ""; };
F73EF7B62B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+ExternalSites.swift"; sourceTree = ""; };
F73EF7BE2B02250B0087E6E9 /* NCManageDatabase+GPS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+GPS.swift"; sourceTree = ""; };
@@ -1435,6 +1459,7 @@
F758B457212C564000515F55 /* NCScan.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCScan.storyboard; sourceTree = ""; };
F758B45D212C569C00515F55 /* NCScanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCell.swift; sourceTree = ""; };
F758B45F212C56A400515F55 /* NCScan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScan.swift; sourceTree = ""; };
+ F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCDirectEditorAdapter.swift; sourceTree = ""; };
F75A9EE523796C6F0044CFCE /* NCNetworking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCNetworking.swift; sourceTree = ""; };
F75B91E21ECAE17800199C96 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; };
F75B91F71ECAE26300199C96 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; };
@@ -1711,6 +1736,12 @@
F7C30DF9291BCF790017149B /* NCNetworkingE2EECreateFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EECreateFolder.swift; sourceTree = ""; };
F7C30DFC291BD0B80017149B /* NCNetworkingE2EEDelete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EEDelete.swift; sourceTree = ""; };
F7C30DFF291BD2610017149B /* NCNetworkingE2EERename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EERename.swift; sourceTree = ""; };
+ F7C55C502FB4A651004A974F /* NCAssistantInputModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantInputModel.swift; sourceTree = ""; };
+ F7C55C7A2FB5AEF7004A974F /* Action Assistant.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Action Assistant.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
+ F7C55C7B2FB5AEF7004A974F /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
+ F7C55CC32FB5CE74004A974F /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; };
+ F7C55CC42FB5CE74004A974F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
+ F7C55CC62FB5CE74004A974F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+RecommendedFiles.swift"; sourceTree = ""; };
F7C7B488245EBA4100D93E60 /* NCViewerQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerQuickLook.swift; sourceTree = ""; };
F7C9555221F0C4CA0024296E /* NCActivity.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCActivity.storyboard; sourceTree = ""; };
@@ -1829,6 +1860,8 @@
F7FDFF572E437E55000D7688 /* NCAccountSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAccountSettingsView.swift; sourceTree = ""; };
F7FDFF592E437E55000D7688 /* NCAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAccount.swift; sourceTree = ""; };
F7FF2CB02842159500EBB7A1 /* NCSectionHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionHeader.xib; sourceTree = ""; };
+ F7FFFC9D2FB300440015441E /* NCAssistantSharedTextStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantSharedTextStore.swift; sourceTree = ""; };
+ F7FFFCA32FB3088D0015441E /* NCShareExtension+Assistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+Assistant.swift"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -1977,6 +2010,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F7C55C772FB5AEF7004A974F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F7C55C9F2FB5B83A004A974F /* NextcloudKit in Frameworks */,
+ F7C55C7C2FB5AEF7004A974F /* UniformTypeIdentifiers.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F7C9738D28F17131002C43E2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -2159,8 +2201,10 @@
F3A0478E2BD2668800658E7B /* Assistant */ = {
isa = PBXGroup;
children = (
+ F7C55C502FB4A651004A974F /* NCAssistantInputModel.swift */,
F3A047962BD2668800658E7B /* NCAssistant.swift */,
F3A047932BD2668800658E7B /* NCAssistantModel.swift */,
+ F7FFFC9D2FB300440015441E /* NCAssistantSharedTextStore.swift */,
F3DDFE0D2F15452F00A784C8 /* Chat */,
F3DDFE1F2F1F951000A784C8 /* Chat Sessions */,
F3A047902BD2668800658E7B /* Create Task */,
@@ -2421,13 +2465,14 @@
path = Offline;
sourceTree = "";
};
- F73D11FF253C5F5400DF9BEC /* NCViewerNextcloudText */ = {
+ F73D11FF253C5F5400DF9BEC /* NCViewerDirectEditing */ = {
isa = PBXGroup;
children = (
- F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */,
- F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */,
+ F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */,
+ F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */,
+ F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */,
);
- path = NCViewerNextcloudText;
+ path = NCViewerDirectEditing;
sourceTree = "";
};
F74D3DB81BAC1941000BAE4B /* Networking */ = {
@@ -2782,7 +2827,7 @@
F79018B1240962C7007C9B6D /* NCViewerMedia */,
F723986A253C9C0E00257F49 /* NCViewerQuickLook */,
F76D3CEF2428B3DD005DFA87 /* NCViewerPDF */,
- F73D11FF253C5F5400DF9BEC /* NCViewerNextcloudText */,
+ F73D11FF253C5F5400DF9BEC /* NCViewerDirectEditing */,
F7239861253C95D500257F49 /* NCViewerRichdocument */,
);
path = Viewer;
@@ -2980,6 +3025,7 @@
AF730AF927843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift */,
AF22B215277D196700DAB0CC /* NCShareExtension+DataSource.swift */,
AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */,
+ F7FFFCA32FB3088D0015441E /* NCShareExtension+Assistant.swift */,
AF22B20B277C6F4D00DAB0CC /* NCShareCell.swift */,
F7148046262EBE4B00693E51 /* Share-Bridging-Header.h */,
);
@@ -3016,6 +3062,16 @@
path = E2EE;
sourceTree = "";
};
+ F7C55CC82FB5CE74004A974F /* Action Assistant */ = {
+ isa = PBXGroup;
+ children = (
+ F7C55CC32FB5CE74004A974F /* ActionViewController.swift */,
+ F7C55CC42FB5CE74004A974F /* Images.xcassets */,
+ F7C55CC72FB5CE74004A974F /* MainInterface.storyboard */,
+ );
+ path = "Action Assistant";
+ sourceTree = "";
+ };
F7C9739328F17131002C43E2 /* WidgetDashboardIntentHandler */ = {
isa = PBXGroup;
children = (
@@ -3202,6 +3258,7 @@
F7F1FBA62E27D13700C79E20 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ F7C55C7B2FB5AEF7004A974F /* UniformTypeIdentifiers.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -3243,6 +3300,7 @@
2C33C48023E2C475005F963B /* Notification Service Extension */,
F7346E1428B0EF5B006CE2D2 /* Widget */,
F7C9739328F17131002C43E2 /* WidgetDashboardIntentHandler */,
+ F7C55CC82FB5CE74004A974F /* Action Assistant */,
F7FC7D651DC1F98700BB2C6A /* Products */,
F30A962A2A27A9C800D7BCFE /* Tests */,
F771E3D020E2392D00AFB62D /* File Provider Extension.appex */,
@@ -3255,6 +3313,7 @@
C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */,
F7F1FBA62E27D13700C79E20 /* Frameworks */,
F31165012F9674A1009A1E37 /* AppIcon.icon */,
+ F7C55C7A2FB5AEF7004A974F /* Action Assistant.appex */,
);
sourceTree = "";
};
@@ -3614,6 +3673,7 @@
F7346E1B28B0EF5E006CE2D2 /* PBXTargetDependency */,
F7C9739828F17131002C43E2 /* PBXTargetDependency */,
F70716EC2987F81500E72C1D /* PBXTargetDependency */,
+ F7C55C872FB5AEF7004A974F /* PBXTargetDependency */,
);
name = Nextcloud;
packageProductDependencies = (
@@ -3650,6 +3710,26 @@
productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */;
productType = "com.apple.product-type.application";
};
+ F7C55C792FB5AEF7004A974F /* Action Assistant */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = F7C55C8C2FB5AEF7004A974F /* Build configuration list for PBXNativeTarget "Action Assistant" */;
+ buildPhases = (
+ F7C55C762FB5AEF7004A974F /* Sources */,
+ F7C55C772FB5AEF7004A974F /* Frameworks */,
+ F7C55C782FB5AEF7004A974F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Action Assistant";
+ packageProductDependencies = (
+ F7C55C9E2FB5B83A004A974F /* NextcloudKit */,
+ );
+ productName = "Action Assistant";
+ productReference = F7C55C7A2FB5AEF7004A974F /* Action Assistant.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
F7C9738F28F17131002C43E2 /* WidgetDashboardIntentHandler */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7C9739A28F17132002C43E2 /* Build configuration list for PBXNativeTarget "WidgetDashboardIntentHandler" */;
@@ -3682,7 +3762,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
- LastSwiftUpdateCheck = 1430;
+ LastSwiftUpdateCheck = 2640;
LastUpgradeCheck = 2640;
ORGANIZATIONNAME = "Marino Faggiana";
TargetAttributes = {
@@ -3736,6 +3816,9 @@
};
};
};
+ F7C55C792FB5AEF7004A974F = {
+ CreatedOnToolsVersion = 26.4;
+ };
F7C9738F28F17131002C43E2 = {
CreatedOnToolsVersion = 14.0;
};
@@ -3832,6 +3915,7 @@
F7346E0F28B0EF5B006CE2D2 /* Widget */,
F7C9738F28F17131002C43E2 /* WidgetDashboardIntentHandler */,
F71459B41D12E3B700CAFEEC /* Share */,
+ F7C55C792FB5AEF7004A974F /* Action Assistant */,
F771E3CF20E2392D00AFB62D /* File Provider Extension */,
F70716E22987F81400E72C1D /* File Provider Extension UI */,
2C33C47E23E2C475005F963B /* Notification Service Extension */,
@@ -3992,7 +4076,7 @@
F704B5E32430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard in Resources */,
F7EDE509262DA9D600414FE6 /* NCSelectCommandViewSelect.xib in Resources */,
F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */,
- F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */,
+ F73D11FA253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard in Resources */,
F7EDE51B262DD0C400414FE6 /* NCSelectCommandViewCopyMove.xib in Resources */,
F7FF2CB12842159500EBB7A1 /* NCSectionHeader.xib in Resources */,
F7D1612023CF19E30039EBBF /* NCViewerRichWorkspace.storyboard in Resources */,
@@ -4010,6 +4094,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F7C55C782FB5AEF7004A974F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F7C55CCA2FB5CE74004A974F /* Images.xcassets in Resources */,
+ F7C55CCC2FB5CE74004A974F /* MainInterface.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F7C9738E28F17131002C43E2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -4277,6 +4370,7 @@
F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */,
F7327E3B2B73B8D600A462C7 /* Array+Extension.swift in Sources */,
F7D7A76C2DCDD437003D2007 /* NCManageDatabase+AutoUpload.swift in Sources */,
+ F7FFFCA02FB300440015441E /* NCAssistantSharedTextStore.swift in Sources */,
F39170AF2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */,
F7D496FC2EBFA541004F9823 /* NCRecommendationsCell.swift in Sources */,
F799DF832C4B7DCC003410B5 /* NCSectionFooter.swift in Sources */,
@@ -4298,6 +4392,7 @@
F763412D2EBE255B0056F538 /* NCNetworking+NextcloudKitDelegate.swift in Sources */,
F72FD3B8297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */,
F7A76DC8256A71CD00119AB3 /* UIImage+Extension.swift in Sources */,
+ F7FFFCA42FB3088E0015441E /* NCShareExtension+Assistant.swift in Sources */,
F3E173C32C9B1067006D177A /* AwakeMode.swift in Sources */,
F711A4E52AF9310500095DD8 /* NCUtility+Image.swift in Sources */,
F73EF7AA2B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */,
@@ -4544,7 +4639,7 @@
F7D60CAF2C941ACB008FBFDD /* NCMediaPinchGesture.swift in Sources */,
F71916142E2901FB00E13E96 /* NCNetworking+Upload.swift in Sources */,
F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */,
- F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */,
+ F72D404923D2082500A97FD0 /* NCViewerDirectEditing.swift in Sources */,
AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */,
F77BB746289984CA0090FC19 /* UIViewController+Extension.swift in Sources */,
F700510522DF6A89003A3356 /* NCShare.swift in Sources */,
@@ -4601,6 +4696,7 @@
F78F74362163781100C2ADAD /* NCTrash.swift in Sources */,
F71638922FA0C20C00A913B7 /* NCMoreView.swift in Sources */,
AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */,
+ F7C55C512FB4A658004A974F /* NCAssistantInputModel.swift in Sources */,
F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */,
F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */,
F74AF3A4247FB6AE00AC767B /* NCUtilityFileSystem.swift in Sources */,
@@ -4755,6 +4851,7 @@
F3DDFE212F1F953000A784C8 /* NCAssistantChatConversations.swift in Sources */,
F72EC7262F45C91E00A2135C /* NCContextMenuNavigation.swift in Sources */,
F7E2B64F2DDCC5C30075B4D0 /* NCMedia+TransferDelegate.swift in Sources */,
+ F7FFFCA22FB300600015441E /* NCAssistantSharedTextStore.swift in Sources */,
F3DDFE0F2F15453900A784C8 /* NCAssistantChat.swift in Sources */,
F7D68FCC28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
F76882292C0DD1E7001CF441 /* NCManageE2EEModel.swift in Sources */,
@@ -4784,6 +4881,7 @@
F7D61EA82EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */,
F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */,
F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */,
+ F75A60552FB4493A00F8247E /* NCDirectEditorAdapter.swift in Sources */,
F3A0479A2BD2668800658E7B /* NCAssistantTaskDetail.swift in Sources */,
F71D2FB72E09BBD700B751CC /* NCAutoUploadModel.swift in Sources */,
F38F71252B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift in Sources */,
@@ -4803,6 +4901,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F7C55C762FB5AEF7004A974F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F7C55C8F2FB5B045004A974F /* NCBrand.swift in Sources */,
+ F7C55C8E2FB5B03D004A974F /* NCGlobal.swift in Sources */,
+ F7C55CC92FB5CE74004A974F /* ActionViewController.swift in Sources */,
+ F7C55C9B2FB5B1A7004A974F /* UIColor+Extension.swift in Sources */,
+ F7C55C8D2FB5B02C004A974F /* NCAssistantSharedTextStore.swift in Sources */,
+ F7C55C9A2FB5B127004A974F /* ThreadSafeDictionary.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F7C9738C28F17131002C43E2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -4902,6 +5013,11 @@
target = F771E3CF20E2392D00AFB62D /* File Provider Extension */;
targetProxy = F771E3E920E2392E00AFB62D /* PBXContainerItemProxy */;
};
+ F7C55C872FB5AEF7004A974F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = F7C55C792FB5AEF7004A974F /* Action Assistant */;
+ targetProxy = F7C55C862FB5AEF7004A974F /* PBXContainerItemProxy */;
+ };
F7C9739828F17131002C43E2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F7C9738F28F17131002C43E2 /* WidgetDashboardIntentHandler */;
@@ -5080,6 +5196,14 @@
name = Intent.intentdefinition;
sourceTree = "";
};
+ F7C55CC72FB5CE74004A974F /* MainInterface.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ F7C55CC62FB5CE74004A974F /* Base */,
+ );
+ name = MainInterface.storyboard;
+ sourceTree = "";
+ };
F7E70DE91A24DE4100E1B66A /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
@@ -5493,7 +5617,7 @@
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.Nextcloud.Share;
PRODUCT_NAME = "$(TARGET_NAME)";
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
@@ -5517,7 +5641,7 @@
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.Nextcloud.Share;
PRODUCT_NAME = "$(TARGET_NAME)";
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
@@ -5685,6 +5809,85 @@
};
name = Release;
};
+ F7C55C892FB5AEF7004A974F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Brand/Action_Assistant.entitlements";
+ CODE_SIGN_STYLE = Automatic;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "$(SRCROOT)/Brand/Action_Assistant.plist";
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "it.twsweb.Nextcloud.Action-Assistant";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) EXTENSION";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ };
+ name = Debug;
+ };
+ F7C55C8A2FB5AEF7004A974F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Brand/Action_Assistant.entitlements";
+ CODE_SIGN_STYLE = Automatic;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "$(SRCROOT)/Brand/Action_Assistant.plist";
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "it.twsweb.Nextcloud.Action-Assistant";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) EXTENSION";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
F7C9739B28F17132002C43E2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -5790,7 +5993,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = NKUJUXUJ3B;
@@ -5821,6 +6024,7 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-v";
OTHER_LDFLAGS = "";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) NC DEBUG";
SWIFT_OBJC_INTEROP_MODE = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -5857,7 +6061,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = NKUJUXUJ3B;
@@ -5886,6 +6090,7 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-v";
OTHER_LDFLAGS = "";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) NC";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OBJC_INTEROP_MODE = objc;
@@ -5979,6 +6184,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ F7C55C8C2FB5AEF7004A974F /* Build configuration list for PBXNativeTarget "Action Assistant" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F7C55C892FB5AEF7004A974F /* Debug */,
+ F7C55C8A2FB5AEF7004A974F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
F7C9739A28F17132002C43E2 /* Build configuration list for PBXNativeTarget "WidgetDashboardIntentHandler" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -6132,8 +6346,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nextcloud/NextcloudKit";
requirement = {
- kind = exactVersion;
- version = 7.3.1;
+ branch = main;
+ kind = branch;
};
};
F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {
@@ -6575,6 +6789,11 @@
package = F7BB7E4527A18C56009B9F29 /* XCRemoteSwiftPackageReference "Parchment" */;
productName = Parchment;
};
+ F7C55C9E2FB5B83A004A974F /* NextcloudKit */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = F783034028B511D200B84583 /* XCRemoteSwiftPackageReference "NextcloudKit" */;
+ productName = NextcloudKit;
+ };
F7D4BF532CA2ED9D00A5E746 /* VLCKitSPM */ = {
isa = XCSwiftPackageProductDependency;
package = F7D4BF4E2CA2ECCB00A5E746 /* XCRemoteSwiftPackageReference "vlckit-spm" */;
diff --git a/Share/NCShareExtension+Assistant.swift b/Share/NCShareExtension+Assistant.swift
new file mode 100644
index 0000000000..250ab85c2f
--- /dev/null
+++ b/Share/NCShareExtension+Assistant.swift
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2026 Marino Faggiana
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import UIKit
+import UniformTypeIdentifiers
+import NextcloudKit
+
+extension NCShareExtension {
+ func handleAssistantSharedTextIfNeeded(inputItems: [NSExtensionItem]) async -> Bool {
+ guard let text = await loadText(from: inputItems),
+ !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
+ return false
+ }
+
+ NCAssistantSharedTextStore.save(text)
+ openMainAppForAssistantSharedText()
+
+ return true
+ }
+
+ private func loadText(from inputItems: [NSExtensionItem]) async -> String? {
+ for item in inputItems {
+ guard let attachments = item.attachments else {
+ continue
+ }
+
+ for provider in attachments {
+ let plainTextIdentifier = UTType.plainText.identifier
+ let textIdentifier = UTType.text.identifier
+
+ if provider.hasItemConformingToTypeIdentifier(plainTextIdentifier) {
+ return await loadText(from: provider, typeIdentifier: plainTextIdentifier)
+ }
+
+ if provider.hasItemConformingToTypeIdentifier(textIdentifier) {
+ return await loadText(from: provider, typeIdentifier: textIdentifier)
+ }
+
+ if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
+ return await loadText(from: provider, typeIdentifier: UTType.text.identifier)
+ }
+ }
+ }
+
+ return nil
+ }
+
+ private func loadText(from provider: NSItemProvider, typeIdentifier: String) async -> String? {
+ await withCheckedContinuation { continuation in
+ provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, _ in
+ let text: String?
+
+ if let string = item as? String {
+ text = string
+ } else if let attributedString = item as? NSAttributedString {
+ text = attributedString.string
+ } else if let data = item as? Data {
+ text = String(data: data, encoding: .utf8)
+ } else if let url = item as? URL {
+ text = try? String(contentsOf: url, encoding: .utf8)
+ } else {
+ text = nil
+ }
+
+ guard let text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
+ continuation.resume(returning: nil)
+ return
+ }
+
+ continuation.resume(returning: text)
+ }
+ }
+ }
+
+ /// Opens the main app using the Assistant shared-text deep link.
+ private func openMainAppForAssistantSharedText() {
+ guard let url = URL(string: "nextcloud://assistant/shared-text") else {
+ extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ return
+ }
+
+ openAssistantSharedTextURLThroughResponderChain(url)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+ self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ }
+ }
+
+ /// Opens the Assistant shared-text deep link from the Share extension.
+ ///
+ /// Share extensions cannot use `UIApplication.shared` directly because it is not
+ /// extension-safe. This method walks the responder chain until it finds the hidden
+ /// `UIApplication` responder and invokes the modern `open(_:options:completionHandler:)`
+ /// Objective-C selector dynamically.
+ ///
+ /// This is intentionally isolated because it relies on Objective-C runtime dispatch.
+ ///
+ /// - Parameter url: Deep link URL to open in the containing application.
+ private func openAssistantSharedTextURLThroughResponderChain(_ url: URL) {
+ let selector = NSSelectorFromString("openURL:options:completionHandler:")
+ let applicationClass: AnyClass? = NSClassFromString("UIApplication")
+ var responder: UIResponder? = self
+
+ while let currentResponder = responder {
+ guard let applicationClass,
+ currentResponder.isKind(of: applicationClass),
+ currentResponder.responds(to: selector),
+ let implementation = currentResponder.method(for: selector) else {
+ responder = currentResponder.next
+ continue
+ }
+
+ typealias CompletionBlock = @convention(block) (Bool) -> Void
+ typealias OpenURLFunction = @convention(c) (AnyObject, Selector, NSURL, NSDictionary, CompletionBlock?) -> Void
+
+ let openURL = unsafeBitCast(implementation, to: OpenURLFunction.self)
+
+ let completion: CompletionBlock = { success in
+ if success {
+ nkLog(debug: "Assistant shared text deep link performed through modern responder chain")
+ } else {
+ nkLog(error: "Assistant shared text deep link modern responder chain returned false")
+ }
+ }
+
+ openURL(currentResponder, selector, url as NSURL, NSDictionary(), completion)
+ return
+ }
+
+ nkLog(error: "Assistant shared text deep link failed because no UIApplication responder can open URL")
+ }
+}
diff --git a/Share/NCShareExtension.swift b/Share/NCShareExtension.swift
index f3e5f8b60b..1d0ba7cc68 100644
--- a/Share/NCShareExtension.swift
+++ b/Share/NCShareExtension.swift
@@ -4,6 +4,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import UIKit
+import UniformTypeIdentifiers
import NextcloudKit
import LucidBanner
import SwiftUI
@@ -150,20 +151,33 @@ class NCShareExtension: UIViewController {
return
}
- NCFilesExtensionHandler(items: inputItems) { fileNames in
- self.filesName = fileNames
- DispatchQueue.main.async {
- self.setCommandView()
+ // Keep the Share extension visually hidden until we know whether this is
+ // an Assistant text handoff or a normal file upload flow. This avoids the
+ // visible open-and-close flash when the extension only needs to redirect text.
+ view.alpha = 0
+
+ Task { @MainActor in
+ if await handleAssistantSharedTextIfNeeded(inputItems: inputItems) {
+ return
}
- }
- if NCPreferences().presentPasscode {
- NCPasscode.shared.presentPasscode(viewController: self, delegate: self) {
- NCPasscode.shared.enableTouchFaceID()
+ self.view.alpha = 1
+
+ NCFilesExtensionHandler(items: inputItems) { fileNames in
+ self.filesName = fileNames
+ DispatchQueue.main.async {
+ self.setCommandView()
+ }
}
- }
- self.collectionView.reloadData()
+ if NCPreferences().presentPasscode {
+ NCPasscode.shared.presentPasscode(viewController: self, delegate: self) {
+ NCPasscode.shared.enableTouchFaceID()
+ }
+ }
+
+ self.collectionView.reloadData()
+ }
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
diff --git a/iOSClient/Assistant/Chat/NCAssistantChat.swift b/iOSClient/Assistant/Chat/NCAssistantChat.swift
index b256478acc..40cf01fcdb 100644
--- a/iOSClient/Assistant/Chat/NCAssistantChat.swift
+++ b/iOSClient/Assistant/Chat/NCAssistantChat.swift
@@ -23,7 +23,7 @@ struct NCAssistantChat: View {
}
.safeAreaInset(edge: .bottom) {
- ChatInputField(isLoading: $chatModel.isSending, isDisabled: $chatModel.isSendingDisabled) { input in
+ ChatInputField(text: $chatModel.text, initialText: $chatModel.inputText, isLoading: $chatModel.isSending, isDisabled: $chatModel.isSendingDisabled) { input in
if chatModel.selectedConversation != nil {
chatModel.sendMessage(input: input)
} else {
@@ -209,15 +209,15 @@ struct EmptyChatView: View {
#Preview {
NavigationStack {
NCAssistantChat(conversationsModel: .constant(NCAssistantChatConversationsModel(controller: nil)))
- .environment(NCAssistantChatModel(controller: nil))
- .environment(NCAssistantModel(controller: nil))
+ .environment(NCAssistantChatModel(controller: nil, inputModel: NCAssistantInputModel()))
+ .environment(NCAssistantModel(controller: nil, inputModel: NCAssistantInputModel()))
}
}
#Preview("With Messages") {
NavigationStack {
NCAssistantChat(conversationsModel: .constant(NCAssistantChatConversationsModel(controller: nil)))
- .environment(NCAssistantChatModel.example)
- .environment(NCAssistantModel(controller: nil))
+ .environment(NCAssistantChatModel(controller: nil, inputModel: NCAssistantInputModel()))
+ .environment(NCAssistantModel(controller: nil, inputModel: NCAssistantInputModel()))
}
}
diff --git a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift
index e29e34e8db..bbbabde172 100644
--- a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift
+++ b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift
@@ -15,6 +15,16 @@ class NCAssistantChatModel {
var showRetryResponseGenerationButton = false
var showMessageNotSentError: Bool = false
+ var text: String {
+ get { inputModel.text }
+ set { inputModel.text = newValue }
+ }
+
+ var inputText: String {
+ get { inputModel.initialText }
+ set { inputModel.initialText = newValue }
+ }
+
public private(set) var selectedConversation: AssistantConversation?
var currentSession: AssistantSession?
@@ -23,15 +33,17 @@ class NCAssistantChatModel {
private var pollingTask: Task?
@ObservationIgnored var controller: NCMainTabBarController?
+ @ObservationIgnored let inputModel: NCAssistantInputModel
@ObservationIgnored private var chatMessageTaskId: Int?
@ObservationIgnored var windowScene: UIWindowScene? {
SceneManager.shared.getWindowScene(controller: controller)
}
- init(controller: NCMainTabBarController?, messages: [AssistantChatMessage] = []) {
+ init(controller: NCMainTabBarController?, messages: [AssistantChatMessage] = [], inputModel: NCAssistantInputModel) {
self.controller = controller
self.ncSession = NCSession.shared.getSession(controller: controller)
self.messages = messages
+ self.inputModel = inputModel
}
func startPollingForResponse(interval: TimeInterval = 4.0) {
@@ -186,5 +198,5 @@ extension NCAssistantChatModel {
role: "assistant",
content: "Based on the text you provided, here's a concise summary: The document discusses the classic Lorem Ipsum placeholder text, which has been used in the printing and typesetting industry for centuries as a standard dummy text.",
timestamp: Int(Date().addingTimeInterval(-120).timeIntervalSince1970 * 1000)
- )])
+ )], inputModel: NCAssistantInputModel())
}
diff --git a/iOSClient/Assistant/Components/ChatInputField.swift b/iOSClient/Assistant/Components/ChatInputField.swift
index dd2845da55..c22c880bd0 100644
--- a/iOSClient/Assistant/Components/ChatInputField.swift
+++ b/iOSClient/Assistant/Components/ChatInputField.swift
@@ -6,12 +6,24 @@ import SwiftUI
struct ChatInputField: View {
@FocusState private var isInputFocused: Bool
- @State var text: String = ""
+ @State private var hasAppliedInitialText = false
+
+ @Binding var text: String
+ @Binding var initialText: String
@Binding var isLoading: Bool
@Binding var isDisabled: Bool
+
var onSend: ((_ input: String) -> Void)?
- init(isLoading: Binding = .constant(false), isDisabled: Binding = .constant(false), onSend: ((_: String) -> Void)? = nil) {
+ init(
+ text: Binding = .constant(""),
+ initialText: Binding = .constant(""),
+ isLoading: Binding = .constant(false),
+ isDisabled: Binding = .constant(false),
+ onSend: ((_: String) -> Void)? = nil
+ ) {
+ _text = text
+ _initialText = initialText
_isLoading = isLoading
_isDisabled = isDisabled
self.onSend = onSend
@@ -56,10 +68,42 @@ struct ChatInputField: View {
.padding(.top, 16)
.padding(.bottom, 16)
.background(.background)
+ .task {
+ applyInitialTextIfNeeded()
+ }
+ }
+
+ private func applyInitialTextIfNeeded() {
+ guard !hasAppliedInitialText else {
+ return
+ }
+
+ hasAppliedInitialText = true
+
+ guard text.isEmpty, !initialText.isEmpty else {
+ return
+ }
+
+ text = initialText
+ initialText = ""
}
}
#Preview {
- ChatInputField(isLoading: .constant(false))
- ChatInputField(isLoading: .constant(true))
+ @Previewable @State var text = ""
+ @Previewable @State var initialText = "Text received from outside"
+
+ VStack(spacing: 16) {
+ ChatInputField(
+ text: $text,
+ initialText: $initialText,
+ isLoading: .constant(false)
+ )
+
+ ChatInputField(
+ text: .constant("Loading state"),
+ initialText: .constant(""),
+ isLoading: .constant(true)
+ )
+ }
}
diff --git a/iOSClient/Assistant/Create Task/NCAssistantCreateNewTask.swift b/iOSClient/Assistant/Create Task/NCAssistantCreateNewTask.swift
index 33cb36fa35..caec215dde 100644
--- a/iOSClient/Assistant/Create Task/NCAssistantCreateNewTask.swift
+++ b/iOSClient/Assistant/Create Task/NCAssistantCreateNewTask.swift
@@ -60,7 +60,7 @@ struct NCAssistantCreateNewTask: View {
}
#Preview {
- let model = NCAssistantModel(controller: nil)
+ let model = NCAssistantModel(controller: nil, inputModel: NCAssistantInputModel())
NCAssistantCreateNewTask()
.environment(model)
diff --git a/iOSClient/Assistant/NCAssistant.swift b/iOSClient/Assistant/NCAssistant.swift
index 239c59cb52..b8c5a29c36 100644
--- a/iOSClient/Assistant/NCAssistant.swift
+++ b/iOSClient/Assistant/NCAssistant.swift
@@ -89,14 +89,15 @@ struct NCAssistant: View {
}
#Preview {
- @Previewable @State var chatModel = NCAssistantChatModel(controller: nil)
- let model = NCAssistantModel(controller: nil)
+ @Previewable @State var chatModel = NCAssistantChatModel(controller: nil, inputModel: NCAssistantInputModel())
+
+ let model = NCAssistantModel(controller: nil, inputModel: NCAssistantInputModel())
let conversationsModel = NCAssistantChatConversationsModel(controller: nil)
NCAssistant(assistantModel: model, chatModel: chatModel, conversationsModel: conversationsModel)
- .onAppear {
- model.loadDummyData()
- }
+ .onAppear {
+ model.loadDummyData()
+ }
}
struct TaskList: View {
@@ -182,7 +183,7 @@ struct TaskList: View {
}
}
.safeAreaInset(edge: .bottom) {
- ChatInputField(isLoading: $assistantModel.isLoading) { input in
+ ChatInputField(text: $assistantModel.text, initialText: $assistantModel.inputText, isLoading: $assistantModel.isLoading) { input in
assistantModel.scheduleTask(input: input)
}
}
diff --git a/iOSClient/Assistant/NCAssistantInputModel.swift b/iOSClient/Assistant/NCAssistantInputModel.swift
new file mode 100644
index 0000000000..0ef847c09d
--- /dev/null
+++ b/iOSClient/Assistant/NCAssistantInputModel.swift
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2025 Marino Faggiana
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+import UIKit
+import NextcloudKit
+import SwiftUI
+
+@Observable
+final class NCAssistantInputModel {
+ var text: String = ""
+ var initialText: String
+
+ init(initialText: String = "") {
+ self.initialText = initialText
+ }
+}
diff --git a/iOSClient/Assistant/NCAssistantModel.swift b/iOSClient/Assistant/NCAssistantModel.swift
index 3a6640e376..7b796643ca 100644
--- a/iOSClient/Assistant/NCAssistantModel.swift
+++ b/iOSClient/Assistant/NCAssistantModel.swift
@@ -14,21 +14,33 @@ class NCAssistantModel {
var selectedType: TaskTypeData?
var selectedTask: AssistantTask?
+ var text: String {
+ get { inputModel.text }
+ set { inputModel.text = newValue }
+ }
+
+ var inputText: String {
+ get { inputModel.initialText }
+ set { inputModel.initialText = newValue }
+ }
+
var hasError: Bool = false
var isLoading: Bool = false
var isRefreshing: Bool = false
var scrollTypeListToTop: Bool = false
@ObservationIgnored let controller: NCMainTabBarController?
+ @ObservationIgnored let inputModel: NCAssistantInputModel
@ObservationIgnored private var tasks: [AssistantTask] = []
@ObservationIgnored private let session: NCSession.Session
@ObservationIgnored private let useV2: Bool
@ObservationIgnored private let chatTypeId = "core:text2text:chat"
@ObservationIgnored var isSelectedTypeChat: Bool { selectedType?.id == chatTypeId }
- init(controller: NCMainTabBarController?) {
+ init(controller: NCMainTabBarController?, inputModel: NCAssistantInputModel) {
self.controller = controller
- session = NCSession.shared.getSession(controller: controller)
+ self.inputModel = inputModel
+ self.session = NCSession.shared.getSession(controller: controller)
let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities()
useV2 = capabilities.serverVersionMajor >= NCGlobal.shared.nextcloudVersion30
diff --git a/iOSClient/Assistant/NCAssistantSharedTextStore.swift b/iOSClient/Assistant/NCAssistantSharedTextStore.swift
new file mode 100644
index 0000000000..0a362491c6
--- /dev/null
+++ b/iOSClient/Assistant/NCAssistantSharedTextStore.swift
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2026 Marino Faggiana
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+
+enum NCAssistantSharedTextStore {
+ private static let sharedTextKey = "assistant.sharedText"
+ private static let sharedTextDateKey = "assistant.sharedTextDate"
+ private static var appGroupIdentifier: String {
+ NCBrandOptions.shared.capabilitiesGroup
+ }
+
+ /// Saves text received from the Assistant share extension into the shared App Group container.
+ ///
+ /// - Parameter text: Text selected by the user in another app.
+ static func save(_ text: String) {
+ let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
+
+ guard !trimmedText.isEmpty,
+ let defaults = UserDefaults(suiteName: appGroupIdentifier) else {
+ return
+ }
+
+ defaults.set(trimmedText, forKey: sharedTextKey)
+ defaults.set(Date(), forKey: sharedTextDateKey)
+ defaults.synchronize()
+ }
+
+ /// Loads and removes the latest text received from the Assistant share extension.
+ ///
+ /// - Returns: Previously saved text, or `nil` when no valid text is available.
+ static func loadAndClear() -> String? {
+ guard let defaults = UserDefaults(suiteName: appGroupIdentifier),
+ let text = defaults.string(forKey: sharedTextKey)?
+ .trimmingCharacters(in: .whitespacesAndNewlines),
+ !text.isEmpty else {
+ return nil
+ }
+
+ defaults.removeObject(forKey: sharedTextKey)
+ defaults.removeObject(forKey: sharedTextDateKey)
+ defaults.synchronize()
+
+ return text
+ }
+
+ /// Removes any pending shared text from the App Group container.
+ static func clear() {
+ guard let defaults = UserDefaults(suiteName: appGroupIdentifier) else {
+ return
+ }
+
+ defaults.removeObject(forKey: sharedTextKey)
+ defaults.removeObject(forKey: sharedTextDateKey)
+ defaults.synchronize()
+ }
+}
diff --git a/iOSClient/Assistant/Task Detail/NCAssistantTaskDetail.swift b/iOSClient/Assistant/Task Detail/NCAssistantTaskDetail.swift
index 52370b5a3f..f2dc5cc0de 100644
--- a/iOSClient/Assistant/Task Detail/NCAssistantTaskDetail.swift
+++ b/iOSClient/Assistant/Task Detail/NCAssistantTaskDetail.swift
@@ -35,7 +35,7 @@ struct NCAssistantTaskDetail: View {
}
#Preview {
- let assistantModel = NCAssistantModel(controller: nil)
+ let assistantModel = NCAssistantModel(controller: nil, inputModel: NCAssistantInputModel())
NCAssistantTaskDetail(task: assistantModel.selectedTask!)
.environment(assistantModel)
diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift
index bf9a829c4b..2e44150c79 100644
--- a/iOSClient/Data/NCManageDatabase+Metadata.swift
+++ b/iOSClient/Data/NCManageDatabase+Metadata.swift
@@ -259,9 +259,8 @@ extension tableMetadata {
directEditingEditors.isEmpty {
// RichDocument: Collabora
return true
- } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") {
- // DirectEditing: Nextcloud Text - OnlyOffice
- return true
+ } else if !directEditingEditors.isEmpty {
+ return true
}
return false
}
@@ -282,12 +281,8 @@ extension tableMetadata {
guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else {
return false
}
- let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() }
-
- if editors.contains("nextcloud text") || editors.contains("onlyoffice") {
- return true
- }
- return false
+ let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType)
+ return !editors.isEmpty
}
var isPDF: Bool {
diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift
index 2733be4b87..279de6f3a5 100644
--- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift
+++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import UIKit
+import UniformTypeIdentifiers
import NextcloudKit
import RealmSwift
import LucidBanner
@@ -70,7 +71,14 @@ extension NCCollectionViewCommon: UIEditMenuInteractionDelegate {
for (index, items) in UIPasteboard.general.items.enumerated() {
for item in items {
let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account)
- let results = NKFilePropertyResolver().resolve(inUTI: item.key, capabilities: capabilities)
+ let identifier = item.key
+ let resolvedType = UTType(mimeType: identifier) ?? UTType(identifier)
+ let resolvedMimeType = resolvedType?.preferredMIMEType ?? identifier
+ let resolvedExtension = resolvedType?.preferredFilenameExtension ?? ""
+ let results = NKFilePropertyResolver().resolve(mimeType: resolvedMimeType,
+ fileExtension: resolvedExtension,
+ typeIdentifier: resolvedType?.identifier ?? identifier,
+ capabilities: capabilities)
guard let data = UIPasteboard.general.data(forPasteboardType: item.key,
inItemSet: IndexSet([index]))?.first
else {
diff --git a/iOSClient/Main/Create/NCCreate.swift b/iOSClient/Main/Create/NCCreate.swift
index d4042e2e92..5db2c8818e 100644
--- a/iOSClient/Main/Create/NCCreate.swift
+++ b/iOSClient/Main/Create/NCCreate.swift
@@ -25,12 +25,8 @@ class NCCreate: NSObject {
var options = NKRequestOptions()
let serverUrl = controller.currentServerUrl()
- if let creatorId, editorId == "text" || editorId == "onlyoffice" {
- if editorId == "onlyoffice" {
- options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentOnlyOffice())
- } else if editorId == "text" {
- options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentNCText())
- }
+ if let creatorId, let adapter = NCDirectEditorAdapter.resolve(from: [editorId]) {
+ options = NKRequestOptions(customUserAgent: adapter.userAgent(utility))
let results = await NextcloudKit.shared.textCreateFileAsync(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateId, account: account, options: options) { task in
Task {
let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account,
@@ -94,13 +90,8 @@ class NCCreate: NSObject {
var selectedTemplate = NKEditorTemplate()
var ext: String = ""
- if editorId == "text" || editorId == "onlyoffice" {
- var options = NKRequestOptions()
- if editorId == "onlyoffice" {
- options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentOnlyOffice())
- } else if editorId == "text" {
- options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentNCText())
- }
+ if let adapter = NCDirectEditorAdapter.resolve(from: [editorId]) {
+ let options = NKRequestOptions(customUserAgent: adapter.userAgent(NCUtility()))
let results = await NextcloudKit.shared.textGetListOfTemplatesAsync(account: account, options: options) { task in
Task {
@@ -128,15 +119,7 @@ class NCCreate: NSObject {
if templates.isEmpty {
var temp = NKEditorTemplate()
temp.identifier = ""
- if editorId == "text" {
- temp.ext = "md"
- } else if editorId == "onlyoffice" && templateId == "document" {
- temp.ext = "docx"
- } else if editorId == "onlyoffice" && templateId == "spreadsheet" {
- temp.ext = "xlsx"
- } else if editorId == "onlyoffice" && templateId == "presentation" {
- temp.ext = "pptx"
- }
+ temp.ext = adapter.defaultExt(templateId)
temp.name = "Empty"
temp.preview = ""
templates.append(temp)
diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift
index beb91f757e..ccce146116 100644
--- a/iOSClient/Main/NCMainNavigationController.swift
+++ b/iOSClient/Main/NCMainNavigationController.swift
@@ -83,7 +83,8 @@ class NCMainNavigationController: UINavigationController, UINavigationController
assistantButtonItem.title = NSLocalizedString("_assistant_", comment: "")
assistantButtonItem.tintColor = NCBrandColor.shared.iconImageColor
assistantButtonItem.primaryAction = UIAction(handler: { _ in
- let assistant = NCAssistant(assistantModel: NCAssistantModel(controller: self.controller), chatModel: NCAssistantChatModel(controller: self.controller), conversationsModel: NCAssistantChatConversationsModel(controller: self.controller))
+ let inputModel = NCAssistantInputModel()
+ let assistant = NCAssistant(assistantModel: NCAssistantModel(controller: self.controller, inputModel: inputModel), chatModel: NCAssistantChatModel(controller: self.controller, inputModel: inputModel), conversationsModel: NCAssistantChatConversationsModel(controller: self.controller))
let hostingController = UIHostingController(rootView: assistant)
self.present(hostingController, animated: true, completion: nil)
})
@@ -294,7 +295,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController
!(topViewController is NCViewerMediaPage),
!(topViewController is NCViewerPDF),
!(topViewController is NCViewerRichDocument),
- !(topViewController is NCViewerNextcloudText)
+ !(topViewController is NCViewerDirectEditing)
else {
return
}
diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift
index 2d1cdefdbb..0782aed87d 100644
--- a/iOSClient/Menu/NCContextMenuPlus.swift
+++ b/iOSClient/Menu/NCContextMenuPlus.swift
@@ -8,6 +8,13 @@ import NextcloudKit
@MainActor
class NCContextMenuPlus: NSObject {
+ struct CreatorMenuInfo {
+ let titleKey: String
+ let templateId: String
+ let icon: String
+ let sortOrder: Int
+ }
+
let menuToolbar: UIToolbar?
let controller: NCMainTabBarController?
@@ -20,6 +27,19 @@ class NCContextMenuPlus: NSObject {
self.controller = controller
}
+ nonisolated static func menuInfo(for ext: String) -> CreatorMenuInfo? {
+ switch ext.lowercased() {
+ case "docx":
+ return CreatorMenuInfo(titleKey: "_create_new_document_", templateId: "document", icon: "doc.text", sortOrder: 0)
+ case "xlsx":
+ return CreatorMenuInfo(titleKey: "_create_new_spreadsheet_", templateId: "spreadsheet", icon: "tablecells", sortOrder: 1)
+ case "pptx":
+ return CreatorMenuInfo(titleKey: "_create_new_presentation_", templateId: "presentation", icon: "play.rectangle", sortOrder: 2)
+ default:
+ return nil
+ }
+ }
+
func create(session: NCSession.Session) async {
guard let controller, let menuToolbar else {
return
@@ -38,7 +58,7 @@ class NCContextMenuPlus: NSObject {
var menuActionElement: [UIMenuElement] = []
var menuE2EEElement: [UIMenuElement] = []
var menuTextElement: [UIMenuElement] = []
- var menuOnlyOfficeElement: [UIMenuElement] = []
+ var menuDirectEditingElement: [UIMenuElement] = []
var menuRichDocumentElement: [UIMenuElement] = []
// ------------------------------- ACTION
@@ -214,59 +234,53 @@ class NCContextMenuPlus: NSObject {
})
}
- // ------------------------------- ONLY OFFICE
+ // ------------------------------- DIRECT EDITING CREATORS (onlyoffice, eurooffice, …)
- if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_docx"}) {
- menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_document_", comment: ""),
- image: utility.loadImage(named: "doc.text", colors: [NCBrandColor.shared.documentIconColor])) { _ in
- Task { @MainActor in
- let createDocument = NCCreate()
- let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "document", account: session.account)
- let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl)
- let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session)
+ let creatorsByEditor = Dictionary(grouping: capabilities.directEditingCreators, by: \.editor)
+ for editorId in creatorsByEditor.keys.sorted() {
+ guard NCDirectEditorAdapter.resolve(from: [editorId]) != nil,
+ editorId != "text" else { continue }
- await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account)
+ let sortedCreators = creatorsByEditor[editorId]!
+ .compactMap { creator -> (NKEditorDetailsCreator, CreatorMenuInfo)? in
+ guard let info = NCContextMenuPlus.menuInfo(for: creator.ext) else { return nil }
+ return (creator, info)
}
- })
- }
-
- if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_xlsx"}) {
- menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""),
- image: utility.loadImage(named: "tablecells", colors: [NCBrandColor.shared.spreadsheetIconColor])) { _ in
- Task { @MainActor in
- let createDocument = NCCreate()
- let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "spreadsheet", account: session.account)
- let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl)
- let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session)
-
- await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account)
- }
-
- })
- }
-
- if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_pptx"}) {
- menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_presentation_", comment: ""),
- image: utility.loadImage(named: "play.rectangle", colors: [NCBrandColor.shared.presentationIconColor])) { _ in
- Task { @MainActor in
- let createDocument = NCCreate()
- let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "presentation", account: session.account)
- let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl)
- let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session)
-
- await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account)
- }
- })
+ .sorted { $0.1.sortOrder < $1.1.sortOrder }
+
+ for (creator, info) in sortedCreators {
+ menuDirectEditingElement.append(UIAction(
+ title: NSLocalizedString(info.titleKey, comment: ""),
+ image: utility.loadImage(named: info.icon, colors: [info.iconColor])
+ ) { _ in
+ Task { @MainActor in
+ let createDocument = NCCreate()
+ let fileExt: String
+ let templateIdentifier: String
+ if creator.templates {
+ let result = await createDocument.getTemplate(editorId: editorId, templateId: info.templateId, account: session.account)
+ fileExt = result.ext
+ templateIdentifier = result.selectedTemplate.identifier
+ } else {
+ fileExt = creator.ext
+ templateIdentifier = ""
+ }
+ let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + fileExt, account: session.account, serverUrl: serverUrl)
+ let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session)
+ await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: editorId, creatorId: creator.identifier, templateId: templateIdentifier, account: session.account)
+ }
+ })
+ }
}
}
let menuAction = UIMenu(title: "", options: .displayInline, children: menuActionElement)
let menuText = UIMenu(title: "", options: .displayInline, children: menuTextElement)
let menuE2EE = UIMenu(title: "", options: .displayInline, children: menuE2EEElement)
- let menuOnlyOffice = UIMenu(title: "", options: .displayInline, children: menuOnlyOfficeElement)
+ let menuDirectEditing = UIMenu(title: "", options: .displayInline, children: menuDirectEditingElement)
let menuRichDocument = UIMenu(title: "", options: .displayInline, children: menuRichDocumentElement)
- let plusMenu = UIMenu(children: [menuAction, menuE2EE, menuText, menuRichDocument, menuOnlyOffice])
+ let plusMenu = UIMenu(children: [menuAction, menuE2EE, menuText, menuRichDocument, menuDirectEditing])
let config = UIImage.SymbolConfiguration(pointSize: 25, weight: .thin)
let plusImage = UIImage(systemName: "plus.circle.fill", withConfiguration: config)
@@ -342,3 +356,14 @@ class NCContextMenuPlus: NSObject {
}
}
}
+
+@MainActor
+extension NCContextMenuPlus.CreatorMenuInfo {
+ var iconColor: UIColor {
+ switch templateId {
+ case "spreadsheet": return NCBrandColor.shared.spreadsheetIconColor
+ case "presentation": return NCBrandColor.shared.presentationIconColor
+ default: return NCBrandColor.shared.documentIconColor
+ }
+ }
+}
diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift
index 19c37ca126..d302ad6458 100644
--- a/iOSClient/SceneDelegate.swift
+++ b/iOSClient/SceneDelegate.swift
@@ -335,6 +335,28 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
return nil
}
+ /*
+ Example: nextcloud://assistant/shared-text
+ */
+
+ if scheme == global.appScheme, action == "assistant", url.path == "/shared-text" {
+ guard let text = NCAssistantSharedTextStore.loadAndClear() else {
+ return
+ }
+
+ Task { @MainActor in
+ let capabilities = await NKCapabilities.shared.getCapabilities(for: controller.account)
+ if capabilities.assistantEnabled {
+ let inputModel = NCAssistantInputModel(initialText: text)
+ let assistant = NCAssistant(assistantModel: NCAssistantModel(controller: controller, inputModel: inputModel), chatModel: NCAssistantChatModel(controller: controller, inputModel: inputModel), conversationsModel: NCAssistantChatConversationsModel(controller: controller))
+ let hostingController = UIHostingController(rootView: assistant)
+ controller.present(hostingController, animated: true, completion: nil)
+ }
+ }
+
+ return
+ }
+
/*
Example: nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com
*/
diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift
index d427cc5c86..4c4f747681 100644
--- a/iOSClient/Utility/NCUtility+Image.swift
+++ b/iOSClient/Utility/NCUtility+Image.swift
@@ -33,6 +33,7 @@ extension NCUtility {
case NKTypeIconFile.txt.rawValue: image = UIImage(systemName: "doc.text", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2]))
case NKTypeIconFile.url.rawValue: image = UIImage(systemName: "network", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2]))
case NKTypeIconFile.xls.rawValue: image = UIImage(systemName: "tablecells", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.spreadsheetIconColor]))
+ case NKTypeIconFile.draw.rawValue: image = UIImage(systemName: "pencil.and.scribble", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2]))
default: image = UIImage(systemName: "doc", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2]))
}
}
diff --git a/iOSClient/Utility/NCUtility.swift b/iOSClient/Utility/NCUtility.swift
index 0a36f11662..e64b90735c 100644
--- a/iOSClient/Utility/NCUtility.swift
+++ b/iOSClient/Utility/NCUtility.swift
@@ -39,32 +39,32 @@ final class NCUtility: NSObject, Sendable {
}
func editorsDirectEditing(account: String, contentType: String) -> [String] {
- var names: [String] = []
+ var identifiers: [String] = []
let capabilities = NCNetworking.shared.capabilities[account]
capabilities?.directEditingEditors.forEach { editor in
editor.mimetypes.forEach { mimetype in
if mimetype == contentType {
- names.append(editor.name)
+ identifiers.append(editor.identifier)
}
// HARDCODE
// https://github.com/nextcloud/text/issues/913
if mimetype == "text/markdown" && contentType == "text/x-markdown" {
- names.append(editor.name)
+ identifiers.append(editor.identifier)
}
if contentType == "text/html" {
- names.append(editor.name)
+ identifiers.append(editor.identifier)
}
}
editor.optionalMimetypes.forEach { mimetype in
if mimetype == contentType {
- names.append(editor.name)
+ identifiers.append(editor.identifier)
}
}
}
- return Array(Set(names))
+ return Array(Set(identifiers))
}
func getCustomUserAgentNCText() -> String {
diff --git a/iOSClient/Viewer/NCViewer.swift b/iOSClient/Viewer/NCViewer.swift
index 6689927a39..353787937d 100644
--- a/iOSClient/Viewer/NCViewer.swift
+++ b/iOSClient/Viewer/NCViewer.swift
@@ -58,6 +58,7 @@ class NCViewer: NSObject {
// DOCUMENTS
else if metadata.classFile == NKTypeClassFile.document.rawValue,
!NCUtilityFileSystem().isDirectoryE2EE(serverUrl: metadata.serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) {
+
// PDF
if metadata.isPDF {
let vc = UIStoryboard(name: "NCViewerPDF", bundle: nil).instantiateInitialViewController() as? NCViewerPDF
@@ -68,16 +69,26 @@ class NCViewer: NSObject {
return vc
}
- // RichDocument: Collabora
- if metadata.isAvailableRichDocumentEditorView {
+
+ // DirectEditing
+ if metadata.isAvailableDirectEditingEditorView {
+ let editors = utility.editorsDirectEditing(account: metadata.account, contentType: metadata.contentType).map { $0.lowercased() }
+ guard let editorAdapter = NCDirectEditorAdapter.resolve(from: editors) else {
+ self.QLPreview(metadata: metadata, delegate: delegate)
+ return nil
+ }
+ let editor = editorAdapter.apiKey
+ let editorViewController = editorAdapter.viewControllerEditor
+ let options = NKRequestOptions(customUserAgent: editorAdapter.userAgent(utility))
if metadata.url.isEmpty {
+ let fileNamePath = utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session)
NCActivityIndicator.shared.start(backgroundView: delegate?.view)
- let results = await NextcloudKit.shared.createUrlRichdocumentsAsync(fileID: metadata.fileId, account: metadata.account) { task in
+ let results = await NextcloudKit.shared.textOpenFileAsync(fileNamePath: fileNamePath, editor: editor, account: metadata.account, options: options) { task in
Task {
let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account,
- path: metadata.fileId,
- name: "createUrlRichdocuments")
+ path: fileNamePath,
+ name: "textOpenFile")
await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task)
}
}
@@ -89,19 +100,20 @@ class NCViewer: NSObject {
return nil
}
- let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument
+ let vc = UIStoryboard(name: "NCViewerDirectEditing", bundle: nil).instantiateInitialViewController() as? NCViewerDirectEditing
vc?.metadata = metadata
+ vc?.editor = editorViewController
vc?.link = url
vc?.imageIcon = image
vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView)
return vc
-
} else {
- let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument
+ let vc = UIStoryboard(name: "NCViewerDirectEditing", bundle: nil).instantiateInitialViewController() as? NCViewerDirectEditing
vc?.metadata = metadata
+ vc?.editor = editorViewController
vc?.link = metadata.url
vc?.imageIcon = image
vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView)
@@ -109,30 +121,16 @@ class NCViewer: NSObject {
return vc
}
}
- // DirectEditing: Nextcloud Text - OnlyOffice
- if metadata.isAvailableDirectEditingEditorView {
- var options = NKRequestOptions()
- var editor = ""
- var editorViewController = ""
- let editors = utility.editorsDirectEditing(account: metadata.account, contentType: metadata.contentType).map { $0.lowercased() }
- if editors.contains("nextcloud text") {
- editor = "text"
- editorViewController = "nextcloud text"
- options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText())
- } else if editors.contains("onlyoffice") {
- editor = "onlyoffice"
- editorViewController = "onlyoffice"
- options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice())
- }
- if metadata.url.isEmpty {
- let fileNamePath = utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session)
+ // RichDocument: Collabora
+ if metadata.isAvailableRichDocumentEditorView {
+ if metadata.url.isEmpty {
NCActivityIndicator.shared.start(backgroundView: delegate?.view)
- let results = await NextcloudKit.shared.textOpenFileAsync(fileNamePath: fileNamePath, editor: editor, account: metadata.account, options: options) { task in
+ let results = await NextcloudKit.shared.createUrlRichdocumentsAsync(fileID: metadata.fileId, account: metadata.account) { task in
Task {
let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account,
- path: fileNamePath,
- name: "textOpenFile")
+ path: metadata.fileId,
+ name: "createUrlRichdocuments")
await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task)
}
}
@@ -144,33 +142,31 @@ class NCViewer: NSObject {
return nil
}
- let vc = UIStoryboard(name: "NCViewerNextcloudText", bundle: nil).instantiateInitialViewController() as? NCViewerNextcloudText
+ let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument
vc?.metadata = metadata
- vc?.editor = editorViewController
vc?.link = url
vc?.imageIcon = image
vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView)
return vc
+
} else {
- let vc = UIStoryboard(name: "NCViewerNextcloudText", bundle: nil).instantiateInitialViewController() as? NCViewerNextcloudText
+ let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument
vc?.metadata = metadata
- vc?.editor = editorViewController
vc?.link = metadata.url
vc?.imageIcon = image
vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView)
return vc
}
- } else {
- self.QLPreview(metadata: metadata, delegate: delegate)
}
- } else {
- self.QLPreview(metadata: metadata, delegate: delegate)
}
+ // iOS QL-Preview
+ self.QLPreview(metadata: metadata, delegate: delegate)
+
return nil
}
diff --git a/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift
new file mode 100644
index 0000000000..15cc9a33ac
--- /dev/null
+++ b/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+
+struct NCDirectEditorAdapter {
+ /// Editor ID passed to the textOpenFile API.
+ let apiKey: String
+ /// Value set on NCViewerNextcloudText.editor — controls user agent and JS behaviour.
+ let viewControllerEditor: String
+ /// Resolves the custom user agent string via NCUtility.
+ let userAgent: (NCUtility) -> String
+ /// Returns the fallback file extension for a given templateId when the template API returns no templates.
+ let defaultExt: (_ templateId: String) -> String
+
+ /// Lookup an adapter for the first matching editor ID in the provided list.
+ /// The list should already be lowercased.
+ static func resolve(from editors: [String]) -> NCDirectEditorAdapter? {
+ editors.lazy.compactMap { registry[$0.lowercased()] }.first
+ }
+
+ // MARK: - Registry
+
+ private static func officeDefaultExt(_ templateId: String) -> String {
+ switch templateId {
+ case "spreadsheet": return "xlsx"
+ case "presentation": return "pptx"
+ default: return "docx"
+ }
+ }
+
+ private static let registry: [String: NCDirectEditorAdapter] = [
+ "text": NCDirectEditorAdapter(
+ apiKey: "text",
+ viewControllerEditor: "nextcloud text",
+ userAgent: { $0.getCustomUserAgentNCText() },
+ defaultExt: { _ in "md" }
+ ),
+ "onlyoffice": NCDirectEditorAdapter(
+ apiKey: "onlyoffice",
+ viewControllerEditor: "onlyoffice",
+ userAgent: { $0.getCustomUserAgentOnlyOffice() },
+ defaultExt: officeDefaultExt
+ ),
+ "eurooffice": NCDirectEditorAdapter(
+ apiKey: "eurooffice",
+ viewControllerEditor: "onlyoffice",
+ userAgent: { $0.getCustomUserAgentOnlyOffice() },
+ defaultExt: officeDefaultExt
+ ),
+ "whiteboard": NCDirectEditorAdapter(
+ apiKey: "whiteboard",
+ viewControllerEditor: "onlyoffice",
+ userAgent: { $0.getCustomUserAgentOnlyOffice() },
+ defaultExt: { _ in "whiteboard" }
+ )
+ ]
+}
diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard
similarity index 91%
rename from iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard
rename to iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard
index 959209506b..e90c459511 100644
--- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard
+++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard
@@ -1,17 +1,17 @@
-
+
-
+
-
+
-
+
diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift
similarity index 98%
rename from iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift
rename to iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift
index 577f55ea76..90fef2de6a 100644
--- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift
+++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift
@@ -6,7 +6,7 @@ import UIKit
import NextcloudKit
@preconcurrency import WebKit
-class NCViewerNextcloudText: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate {
+class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate {
var webView = WKWebView()
var bottomConstraint: NSLayoutConstraint?
var link: String = ""
@@ -223,7 +223,7 @@ class NCViewerNextcloudText: UIViewController, WKNavigationDelegate, WKScriptMes
}
}
-extension NCViewerNextcloudText: UINavigationControllerDelegate {
+extension NCViewerDirectEditing: UINavigationControllerDelegate {
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
@@ -237,7 +237,7 @@ extension NCViewerNextcloudText: UINavigationControllerDelegate {
}
}
-extension NCViewerNextcloudText: NCTransferDelegate {
+extension NCViewerDirectEditing: NCTransferDelegate {
func transferReloadData(serverUrl: String?) { }
func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { }