diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index b24d42ac75..be9b4fe371 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -90,6 +90,16 @@ AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B52FAE902DA8D9E1001AB1BD /* CollaboraTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */; }; + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */; }; + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */; }; + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */; }; + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */; }; + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */; }; + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; + D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; F31165022F9674A1009A1E37 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = F31165012F9674A1009A1E37 /* AppIcon.icon */; }; @@ -1280,6 +1290,14 @@ AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuPlayerTracks.swift; sourceTree = ""; }; BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollaboraTestCase.swift; sourceTree = ""; }; + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCCreateFormUploadDocuments.storyboard; sourceTree = ""; }; + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateFormUploadDocuments.swift; sourceTree = ""; }; + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPathCustomCell.swift; sourceTree = ""; }; + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FolderPathCustomCell.xib; sourceTree = ""; }; + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateDocumentCustomTextField.swift; sourceTree = ""; }; + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCCreateDocumentCustomTextField.xib; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2104,6 +2122,8 @@ children = ( F34BDB3B2F574A58007A222C /* BidiSafeFilenameTests.swift */, AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2142,6 +2162,17 @@ path = Advanced; sourceTree = ""; }; + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */ = { + isa = PBXGroup; + children = ( + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */, + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */, + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */, + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */, + ); + path = "NMC Custom Views"; + sourceTree = ""; + }; C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = { isa = PBXGroup; children = ( @@ -3224,6 +3255,8 @@ F7DFB7E9219C5A0500680748 /* Create */ = { isa = PBXGroup; children = ( + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */, + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */, F7FA7FFD2C0F4F3B0072FC60 /* Upload Assets */, F7A509242C26BD5D00326106 /* NCCreate.swift */, F704B5E22430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard */, @@ -3351,6 +3384,7 @@ isa = PBXGroup; children = ( AA517BB42D66149900F8D37C /* .tx */, + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, F7110ADF2F9773210095AA5C /* AppDelegate+AppRefresh.swift */, F7110AE32F9774130095AA5C /* AppDelegate+AppProcessing.swift */, @@ -4085,6 +4119,8 @@ F717402D24F699A5000C87D5 /* NCFavorite.storyboard in Resources */, F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */, F78ACD4B21903F850088454D /* NCTrashListCell.xib in Resources */, + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */, + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */, AF93471927E2361E002537EE /* NCShareAdvancePermissionFooter.xib in Resources */, F7725A61251F33BB00D125E0 /* NCFiles.storyboard in Resources */, F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */, @@ -4104,6 +4140,7 @@ F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */, F751247E2C42919C00E63DB8 /* NCPhotoCell.xib in Resources */, F704B5E32430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard in Resources */, + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */, F7EDE509262DA9D600414FE6 /* NCSelectCommandViewSelect.xib in Resources */, F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */, F73D11FA253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard in Resources */, @@ -4580,6 +4617,7 @@ F7E402332BA89551007E5609 /* NCTrash+Networking.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */, + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */, F39170AD2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */, F799DF882C4B83CC003410B5 /* NCCollectionViewCommon+EasyTipView.swift in Sources */, F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */, @@ -4720,6 +4758,11 @@ F3A047972BD2668800658E7B /* NCAssistantEmptyView.swift in Sources */, F757CC8D29E82D0500F31428 /* NCGroupfolders.swift in Sources */, F34BDB3A2F5744EC007A222C /* UINavigationItem+Extension.swift in Sources */, + F760329F252F0F8E0015A421 /* NCTransferCell.swift in Sources */, + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */, + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */, + F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */, + AF68326A27BE65A90010BF0B /* NCMenuAction.swift in Sources */, F7F3E58E2D3BB65600A32B14 /* NCNetworking+Recommendations.swift in Sources */, F7A0D1352591FBC5008F8A13 /* String+Extension.swift in Sources */, F7CEE6012BA9A5C9003EFD89 /* NCTrashGridCell.swift in Sources */, diff --git a/Tests/NextcloudUnitTests/CollaboraTestCase.swift b/Tests/NextcloudUnitTests/CollaboraTestCase.swift new file mode 100644 index 0000000000..51b3ecce1f --- /dev/null +++ b/Tests/NextcloudUnitTests/CollaboraTestCase.swift @@ -0,0 +1,142 @@ +// +// CollaboraTestCase.swift +// NextcloudTests +// +// Created by A200073704 on 06/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit + +class CollaboraTestCase: XCTestCase { + + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCollaboraDocumentIsPresent() { + + var viewForDocument: NCMenuAction? + + if let image = UIImage(named: "create_file_document") { + viewForDocument = NCMenuAction(title: NSLocalizedString("_create_new_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_document_", comment: "") + }) + } + + XCTAssertNotNil(viewForDocument) + + } + + func testCollaboraPresentationIsPresent() { + + var viewForPresentation: NCMenuAction? + + if let image = UIImage(named: "create_file_ppt") { + viewForPresentation = NCMenuAction(title: NSLocalizedString("_create_new_presentation_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_presentation_", comment: "") + }) + } + + XCTAssertNotNil(viewForPresentation) + + } + + func testCollaboraSpreadsheetIsPresent() { + + var viewForSpreadsheet: NCMenuAction? + + if let image = UIImage(named: "create_file_xls") { + viewForSpreadsheet = NCMenuAction(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_spreadsheet_", comment: "") + }) + } + + XCTAssertNotNil(viewForSpreadsheet) + + } + + func testTextDocumentIsPresent() { + + var textMenu: NCMenuAction? + + if let image = UIImage(named: "file_txt_menu") { + textMenu = NCMenuAction(title: NSLocalizedString("_create_nextcloudtext_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "") + }) + } + + XCTAssertNotNil(textMenu) + + } + + func testTextDocumentAction() { + + let text = NCGlobal.shared.actionTextDocument + XCTAssertNotNil(text, "Text Editor Should be opened") + } + + func testTextFieldIsPresent() { + + let storyboard = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCCreateFormUploadDocuments else { + return + } + + // Verify that a text field is present in the view controller + let textFields = viewController.view.subviews.filter { $0 is UITextField } + XCTAssertFalse(textFields.isEmpty, "No text field found in NCCreateFormUploadDocuments") + } + + func testSavePathFolder() { + + let viewController = NCCreateFormUploadDocuments() + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var row : XLFormRowDescriptor + + // the section with the title "Folder Destination" + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(viewController.changeDestinationFolder(_:)) + + // Verify that section was found + XCTAssertNotNil(row, "Expected save path section to exist in form.") + + } + + + + + + +} diff --git a/iOSClient/Data/NCManageDatabase+CreateMetadata.swift b/iOSClient/Data/NCManageDatabase+CreateMetadata.swift index 5c2fb757e9..8e334c16f3 100644 --- a/iOSClient/Data/NCManageDatabase+CreateMetadata.swift +++ b/iOSClient/Data/NCManageDatabase+CreateMetadata.swift @@ -202,7 +202,9 @@ final class NCManageDatabaseCreateMetadata { for element in file.shareType { metadata.shareType.append(element) } - metadata.tags.append(objectsIn: file.tags, account: metadata.account) + for element in file.tags { + metadata.tags.append(element) + } metadata.size = file.size metadata.classFile = file.classFile // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 2892c7cae0..7946d21186 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -59,19 +59,15 @@ class tableMetadata: Object { let exifPhotos = List() @objc dynamic var favorite: Bool = false @objc dynamic var fileId = "" - /// The file name as it exists on the server. `fileName` is the same as `fileNameView`. The only exception is when E2EE is enabled, in which case the `fileName` is obfuscated, while the `fileNameView` shows the human-readable name. @objc dynamic var fileName = "" - /// The human readable file name . `fileName` is the same as `fileNameView`. The only exception is when E2EE is enabled, in which case the `fileName` is obfuscated, while the `fileNameView` shows the human-readable name. @objc dynamic var fileNameView = "" @objc dynamic var hasPreview: Bool = false @objc dynamic var hidden: Bool = false @objc dynamic var iconName = "" @objc dynamic var iconUrl = "" - /// Indicating if the file is sent as a live photo from the server, or if we should detect it as such and convert it client-side - @objc dynamic var isFlaggedAsLivePhotoByServer: Bool = false + @objc dynamic var isFlaggedAsLivePhotoByServer: Bool = false // Indicating if the file is sent as a live photo from the server, or if we should detect it as such and convert it client-side @objc dynamic var isExtractFile: Bool = false - /// If this is not empty, the media is a live photo. New media gets this straight from server, but old media needs to be detected as live photo (look isFlaggedAsLivePhotoByServer) - @objc dynamic var livePhotoFile = "" + @objc dynamic var livePhotoFile = "" // If this is not empty, the media is a live photo. New media gets this straight from server, but old media needs to be detected as live photo (look isFlaggedAsLivePhotoByServer) @objc dynamic var mountType = "" @objc dynamic var name = "" // for unifiedSearch is the provider.id @objc dynamic var note = "" @@ -110,7 +106,7 @@ class tableMetadata: Object { @objc dynamic var status: Int = 0 @objc dynamic var storeFlag: String? @objc dynamic var subline: String? - let tags = List() + let tags = List() @objc dynamic var trashbinFileName = "" @objc dynamic var trashbinOriginalLocation = "" @objc dynamic var trashbinDeletionTime = NSDate() @@ -227,6 +223,7 @@ extension tableMetadata { return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } + // Return if is sharable func isSharable() -> Bool { guard let capabilities = NCNetworking.shared.capabilities[account] else { return false @@ -234,7 +231,47 @@ extension tableMetadata { if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { return false } - return true + return !e2eEncrypted + } + + var canShare: Bool { + return session.isEmpty && !directory && !NCBrandOptions.shared.disable_openin_file + } + + var canSetDirectoryAsE2EE: Bool { + return directory && size == 0 && !e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + var canUnsetDirectoryAsE2EE: Bool { + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + var isDownload: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading + } + + var isUpload: Bool { + status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploading + } + + var isDirectory: Bool { + directory + } + + @objc var isDirectoryE2EE: Bool { + return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) + } + + var isLivePhoto: Bool { + !livePhotoFile.isEmpty + } + + var isNotFlaggedAsLivePhotoByServer: Bool { + !isFlaggedAsLivePhotoByServer + } + + var imageSize: CGSize { + CGSize(width: width, height: height) } var hasPreviewBorder: Bool { @@ -258,8 +295,9 @@ extension tableMetadata { directEditingEditors.isEmpty { // RichDocument: Collabora return true - } else if !directEditingEditors.isEmpty { - return true + } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") { + // DirectEditing: Nextcloud Text - OnlyOffice + return true } return false } @@ -280,8 +318,12 @@ extension tableMetadata { guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { return false } - let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType) - return !editors.isEmpty + let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } + + if editors.contains("nextcloud text") || editors.contains("onlyoffice") { + return true + } + return false } var isPDF: Bool { @@ -334,10 +376,6 @@ extension tableMetadata { CGSize(width: width, height: height) } - var tagNames: [String] { - tags.map(\.name) - } - /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else func canUnlock(as user: String) -> Bool { return !lock || (lockOwner == user && lockOwnerType == 0) @@ -345,36 +383,18 @@ extension tableMetadata { /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. /// - /// - Note: Primitive properties and lists of primitive values (for example `shareType`) - /// are copied automatically by `init(value:)`. - /// For `List` properties containing Realm objects (for example `exifPhotos` and `tags`), - /// this method recreates each element explicitly to ensure the resulting copy is fully - /// detached and safe to use across Realm contexts. + /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically + /// by the Realm initializer `init(value:)`. For `List` containing Realm objects (e.g., `exifPhotos`), this method + /// creates new instances to ensure the copy is fully detached and safe to use outside of a Realm context. /// /// - Returns: A new `tableMetadata` instance fully detached from Realm. func detachedCopy() -> tableMetadata { - // Use Realm's built-in copy constructor for primitive properties and lists of primitive values. + // Use Realm's built-in copy constructor for primitive properties and List of primitives let detached = tableMetadata(value: self) - // Deep copy of List of Realm objects + // Deep copy of List of Realm objects (exifPhotos) detached.exifPhotos.removeAll() - detached.exifPhotos.append(objectsIn: self.exifPhotos.map { - let copy = NCKeyValue() - copy.key = $0.key - copy.value = $0.value - return copy - }) - - detached.tags.removeAll() - detached.tags.append(objectsIn: self.tags.map { - let copy = tableMetadataTag() - copy.primaryKey = $0.primaryKey - copy.account = $0.account - copy.id = $0.id - copy.name = $0.name - copy.color = $0.color - return copy - }) + detached.exifPhotos.append(objectsIn: self.exifPhotos.map { NCKeyValue(value: $0) }) return detached } @@ -484,6 +504,16 @@ extension NCManageDatabase { } } + func addMetadataIfNotExistsAsync(_ metadata: tableMetadata) async { + let detached = metadata.detachedCopy() + + await core.performRealmWriteAsync { realm in + if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { + realm.add(detached) + } + } + } + func deleteMetadataAsync(predicate: NSPredicate) async { await core.performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) @@ -509,6 +539,19 @@ extension NCManageDatabase { } } } + + func deleteMetadataOcIds(_ ocIds: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId IN %@", ocIds) + realm.delete(results) + } + } catch let error as NSError { + nkLog(error: "Could not access database: \(error)") + + } + } func replaceMetadataAsync(ocId: String, metadata: tableMetadata) async { let detached = metadata.detachedCopy() @@ -556,17 +599,6 @@ extension NCManageDatabase { } } - func deleteMetadatasAsync(ocIds: [String]) async { - guard !ocIds.isEmpty else { - return - } - await core.performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocIds) - realm.delete(results) - } - } - func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) async { await core.performRealmWriteAsync { realm in guard let metadata = realm.objects(tableMetadata.self) @@ -751,19 +783,6 @@ extension NCManageDatabase { } } - func setMetadataTagsAsync(ocId: String, account: String, tags: [NKTag]) async { - await core.performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND ocId == %@", account, ocId) - .first else { - return - } - - result.tags.removeAll() - result.tags.append(objectsIn: tags, account: account) - } - } - func setMetadataFileNameViewAsync(serverUrl: String, fileName: String, newFileNameView: String, account: String) async { await core.performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) @@ -948,21 +967,6 @@ extension NCManageDatabase { } } - func getMetadatasAsync(predicate: NSPredicate, - limit: Int? = nil) async -> [tableMetadata]? { - return await core.performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - - if let limit { - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } else { - return results.map { $0.detachedCopy() } - } - } - } - func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, @@ -998,22 +1002,6 @@ extension NCManageDatabase { } } - /// Returns detached (unmanaged) copies of `tableMetadata` objects matching the provided ocIds. - /// - /// - Parameter ocIds: Array of ocId strings used to fetch corresponding metadata. - /// - Returns: An array of detached `tableMetadata` objects. Empty if no matches are found. - func getMetadatasFromOcIdsAsync(_ ocIds: [String]) async -> [tableMetadata] { - guard !ocIds.isEmpty else { return [] } - - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .where { - $0.ocId.in(ocIds) - } - .map { $0.detachedCopy() } - } ?? [] - } - func getMetadataFromOcIdAndocIdTransferAsync(_ ocId: String?) async -> tableMetadata? { guard let ocId else { return nil @@ -1027,22 +1015,6 @@ extension NCManageDatabase { } } - func getOwnerDisplayName(account: String?, ownerId: String?) async -> String? { - guard let account = account.isNotEmpty, - let ownerId = ownerId.isNotEmpty else { - return nil - } - - return await core.performRealmReadAsync { realm in - let ownerDisplayName = realm.objects(tableMetadata.self) - .filter("account == %@ AND ownerId == %@", account, ownerId) - .first? - .ownerDisplayName - - return ownerDisplayName.isNotEmpty - } - } - /// Asynchronously retrieves the metadata for a folder, based on its session and serverUrl. /// Handles the home directory case rootFileName) and detaches the Realm object before returning. func getMetadataFolderAsync(session: NCSession.Session, serverUrl: String) async -> tableMetadata? { @@ -1363,14 +1335,6 @@ extension NCManageDatabase { } ?? [] } - func getMetadatasStatusCountAsync(status: [Int]) async -> Int { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("status IN %@", status) - .count - } ?? 0 - } - func metadataExistsAsync(predicate: NSPredicate) async -> Bool { await core.performRealmReadAsync { realm in realm.objects(tableMetadata.self) @@ -1378,6 +1342,42 @@ extension NCManageDatabase { .first != nil } ?? false } + + func getMetadatasInWaitingCountAsync() async -> Int { + await core.performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("status IN %@", NCGlobal.shared.metadatasStatusInWaiting) + .count + } ?? 0 + } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch NCKeychain().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + return nil + } func countMetadatasFor(serverUrl: String) -> Int { core.performRealmRead { realm in @@ -1480,4 +1480,45 @@ extension List where Element == tableMetadataTag { append(tag, account: account) } } + + func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] { + + var metadatas: [tableMetadata] = [] + + do { + let realm = try Realm() + realm.refresh() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) + if !results.isEmpty { + if page == 0 || limit == 0 { + return Array(results.map { tableMetadata.init(value: $0) }) + } else { + let nFrom = (page - 1) * limit + let nTo = nFrom + (limit - 1) + for n in nFrom...nTo { + if n == results.count { + break + } + metadatas.append(tableMetadata.init(value: results[n])) + } + } + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + + return metadatas + } + + func getResultMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { return nil } + + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } } diff --git a/iOSClient/Main/Create/NCCreateDocument.swift b/iOSClient/Main/Create/NCCreateDocument.swift new file mode 100644 index 0000000000..6cc78c4669 --- /dev/null +++ b/iOSClient/Main/Create/NCCreateDocument.swift @@ -0,0 +1,200 @@ +// +// NCCreateDocument.swift +// Nextcloud +// +// Created by Marino Faggiana on 22/06/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import Foundation +import UIKit +import NextcloudKit + +class NCCreateDocument: NSObject { + let utility = NCUtility() + let database = NCManageDatabase.shared + let global = NCGlobal.shared +// var fileNameExtension = "" + + @MainActor + func createDocument(controller: NCMainTabBarController, fileNamePath: String, fileName: String, fileNameExtension: String = "", editorId: String, creatorId: String? = nil, templateId: String, account: String) async { + let session = NCSession.shared.getSession(account: account) + guard let viewController = controller.currentViewController() else { + return + } + var UUID = NSUUID().uuidString + UUID = "TEMP" + UUID.replacingOccurrences(of: "-", with: "") + 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()) + } + 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, + path: fileNamePath, + name: "textCreateFile") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + + var result = await NKTypeIdentifiers.shared.getInternalType(fileName: fileName, mimeType: "", directory: false, account: session.account) + + controller.dismiss(animated: true, completion: { + + Task { + let metadata = await NCManageDatabaseCreateMetadata().createMetadataAsync( + fileName: fileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + } + }) + + } else if editorId == "collabora" { + + let results = await NextcloudKit.shared.createRichdocumentsAsync(path: fileNamePath, templateId: templateId, account: account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: fileNamePath, + name: "CreateRichdocuments") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + + controller.dismiss(animated: true, completion: { + + Task { + let createFileName = (fileName as NSString).deletingPathExtension + "." + fileNameExtension + let metadata = await NCManageDatabaseCreateMetadata().createMetadataAsync( + fileName: createFileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + AnalyticsHelper.shared.trackCreateFile(metadata: metadata) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + } + }) + + } + } + + func getTemplate(editorId: String, templateId: String, account: String) async -> (templates: [NKEditorTemplate], selectedTemplate: NKEditorTemplate, ext: String) { + var templates: [NKEditorTemplate] = [] + 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()) + } + + let results = await NextcloudKit.shared.textGetListOfTemplatesAsync(account: account, options: options) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + name: "textGetListOfTemplates") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + if results.error == .success, let resultTemplates = results.templates { + for template in resultTemplates { + var temp = NKEditorTemplate() + temp.identifier = template.identifier + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + templates.append(temp) + // default: template empty + if temp.preview.isEmpty { + selectedTemplate = temp + ext = template.ext + } + } + } + + 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.name = "Empty" + temp.preview = "" + templates.append(temp) + selectedTemplate = temp + ext = temp.ext + } + } + + if editorId == "collabora" { + let results = await NextcloudKit.shared.getTemplatesRichdocumentsAsync(typeTemplate: templateId, account: account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: templateId, + name: "getTemplatesRichdocuments") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + if results.error == .success { + for template in results.templates! { + var temp = NKEditorTemplate() + temp.identifier = "\(template.templateId)" + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + templates.append(temp) + // default: template empty + if temp.preview.isEmpty { + selectedTemplate = temp + ext = temp.ext + } + } + } + } + + return (templates, selectedTemplate, ext) + } +} diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard b/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard new file mode 100644 index 0000000000..6995ac52d9 --- /dev/null +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift new file mode 100644 index 0000000000..99c1194d9b --- /dev/null +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -0,0 +1,597 @@ +// +// NCCreateFormUploadDocuments.swift +// Nextcloud +// +// Created by Marino Faggiana on 14/11/18. +// Copyright © 2018 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import NextcloudKit +import XLForm + +// MARK: - + +@objc class NCCreateFormUploadDocuments: XLFormViewController, NCSelectDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NCCreateFormUploadConflictDelegate { + + @IBOutlet weak var indicator: UIActivityIndicatorView! + @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet weak var collectionViewHeigth: NSLayoutConstraint! + + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + var editorId = "" + var creatorId = "" + var typeTemplate = "" + var templateIdentifier = "" + var serverUrl = "" + var fileNameFolder = "" + var fileName = "" + var fileNameExtension = "" + var titleForm = "" + var listOfTemplate: [NKEditorTemplate] = [] + var selectTemplate: NKEditorTemplate? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + + // Layout + let numItems = 2 + let sectionInsets: CGFloat = 10 + let highLabelName: CGFloat = 20 + + var controller: NCMainTabBarController! + var session: NCSession.Session { + NCSession.shared.getSession(controller: controller) + } + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + if serverUrl == utilityFileSystem.getHomeServer(session: session) { + fileNameFolder = "/" + } else { + fileNameFolder = utilityFileSystem.getTextServerUrl(session: session, serverUrl: serverUrl)//(serverUrl as NSString).lastPathComponent + } + + self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none + + view.backgroundColor = .systemGroupedBackground + collectionView.backgroundColor = .systemGroupedBackground + tableView.backgroundColor = .secondarySystemGroupedBackground + + let cancelButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(cancel)) + let saveButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_save_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(save)) + cancelButton.tintColor = NCBrandColor.shared.brand + saveButton.tintColor = NCBrandColor.shared.brand + + self.navigationItem.leftBarButtonItem = cancelButton + self.navigationItem.rightBarButtonItem = saveButton + self.navigationItem.rightBarButtonItem?.isEnabled = false + + // title + self.title = titleForm + + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + + initializeForm() + getTemplate() + } + + // MARK: - Tableview (XLForm) + + func initializeForm() { + + let form: XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section: XLFormSectionDescriptor + var row: XLFormRowDescriptor + + // Section: Destination Folder + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_save_path_", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFolderCustomCellType"] = FolderPathCustomCell.self + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(changeDestinationFolder(_:)) + row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.withTintColor(NCBrandColor.shared.customer) + row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["photoLabel.textColor"] = UIColor.label //photos + if(self.fileNameFolder == "/"){ + row.cellConfig["photoLabel.text"] = NSLocalizedString("_prefix_upload_path_", comment: "") + }else{ + row.cellConfig["photoLabel.text"] = self.fileNameFolder + } + row.cellConfig["textLabel.text"] = "" + + section.addFormRow(row) + + // Section: File Name + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_filename_", comment: "").uppercased()) + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kMyAppCustomCellType"] = NCCreateDocumentCustomTextField.self + + row = XLFormRowDescriptor(tag: "fileName", rowType: "kMyAppCustomCellType", title: NSLocalizedString("_filename_", comment: "")) + row.cellClass = NCCreateDocumentCustomTextField.self + + row.cellConfigAtConfigure["backgroundColor"] = UIColor.secondarySystemGroupedBackground; + row.cellConfig["fileNameTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["fileNameTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["fileNameTextField.textColor"] = UIColor.label + row.cellConfig["fileNameTextField.placeholder"] = self.fileName + + section.addFormRow(row) + + self.form = form + // tableView.reloadData() + // collectionView.reloadData() + } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + // MARK: - CollectionView + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return listOfTemplate.count + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + let itemWidth: CGFloat = (collectionView.frame.width - (sectionInsets * 4) - CGFloat(numItems)) / CGFloat(numItems) + let itemHeight: CGFloat = itemWidth + highLabelName + + collectionViewHeigth.constant = itemHeight + sectionInsets + + return CGSize(width: itemWidth, height: itemHeight) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) + + let template = listOfTemplate[indexPath.row] + + // image + let imagePreview = cell.viewWithTag(100) as? UIImageView + if !template.preview.isEmpty { + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + template.name + ".png" + if FileManager.default.fileExists(atPath: fileNameLocalPath) { + let imageURL = URL(fileURLWithPath: fileNameLocalPath) + if let image = UIImage(contentsOfFile: imageURL.path) { + imagePreview?.image = image + } + } else { + getImageFromTemplate(name: template.name, preview: template.preview, indexPath: indexPath) + } + } + + // name + let name = cell.viewWithTag(200) as? UILabel + name?.text = template.name + name?.textColor = .secondarySystemGroupedBackground + + // select + let imageSelect = cell.viewWithTag(300) as? UIImageView + if selectTemplate != nil && selectTemplate?.name == template.name { + cell.backgroundColor = .label + imageSelect?.image = UIImage(named: "plus100") + imageSelect?.isHidden = false + } else { + cell.backgroundColor = .secondarySystemGroupedBackground + imageSelect?.isHidden = true + } + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + let template = listOfTemplate[indexPath.row] + + selectTemplate = template + fileNameExtension = template.ext + + collectionView.reloadData() + } + + // MARK: - Action + + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) { + + guard let serverUrl = serverUrl else { return } + + self.serverUrl = serverUrl + if serverUrl == utilityFileSystem.getHomeServer(session: session) { + fileNameFolder = "/" + } else { + fileNameFolder = (serverUrl as NSString).lastPathComponent + } + + let buttonDestinationFolder: XLFormRowDescriptor = self.form.formRow(withTag: "ButtonDestinationFolder")! + buttonDestinationFolder.cellConfig["photoLabel.text"] = fileNameFolder + + self.tableView.reloadData() + } + +// override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { +// super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) +//// if formRow.tag == "fileName" { +//// self.form.delegate = nil +//// if let fileNameNew = formRow.value { +//// self.fileName = CCUtility.removeForbiddenCharactersServer(fileNameNew as? String) +//// } +//// formRow.value = self.fileName +//// self.form.delegate = self +//// } +// } + + @objc func changeDestinationFolder(_ sender: XLFormRowDescriptor) { + + self.deselectFormRow(sender) + + let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) + if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect { + + viewController.delegate = self + viewController.typeOfCommandView = .selectCreateFolder + + self.present(navigationController, animated: true, completion: nil) + } + } + + @objc func save() { + + Task { + guard let selectTemplate = self.selectTemplate else { return } + templateIdentifier = selectTemplate.identifier + + let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")! + var fileName = rowFileName.value as? String + if fileName?.isEmpty ?? false || fileName == nil { + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + } else if fileName?.trimmingCharacters(in: .whitespaces).isEmpty ?? false { + let alert = UIAlertController(title: "", message: NSLocalizedString("_please_enter_file_name_", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .cancel, handler: nil)) + self.present(alert, animated: true) + return + } + + // Ensure fileName is not nil or empty + guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } + + // Trim whitespaces and newlines + fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + +// let fileAutoRenamer = FileAutoRenamer() + let session = NCSession.shared.getSession(controller: self.controller) + let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) + fileName = FileAutoRenamer.rename(fileNameForm, isFolderPath: true, capabilities: capabilities) + + let result = await NKTypeIdentifiers.shared.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account) + + if utility.editorsDirectEditing(account: session.account, contentType: result.mimeType).isEmpty { + fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension + } + + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", session.account, self.serverUrl, fileNameForm)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return + // } + // + // if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { + // + // let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) + // + // guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } + // + // conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") + // conflict.alwaysNewFileNameNumber = true + // conflict.serverUrl = serverUrl + // conflict.metadatasUploadInConflict = [metadataForUpload] + // conflict.delegate = self + // + // self.present(conflict, animated: true, completion: nil) + + } else { + + let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileNameForm), serverUrl: serverUrl, session: session) + await NCCreateDocument().createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileNameForm), fileNameExtension: self.fileNameExtension, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account) + + } + } + } + + func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { + + if let metadatas, metadatas.count > 0 { + let fileName = metadatas[0].fileName + let fileNamePath = utilityFileSystem.getRelativeFilePath(fileName, serverUrl: serverUrl, session: session) +// createDocument(fileNamePath: fileNamePath, fileName: fileName) + Task { + await NCCreateDocument().createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account) + } + + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.cancel() + } + } + } + + func createDocument(fileNamePath: String, fileName: String) { + + self.navigationItem.rightBarButtonItem?.isEnabled = false + var UUID = NSUUID().uuidString + UUID = "TEMP" + UUID.replacingOccurrences(of: "-", with: "") + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + Task { + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + let results = await NextcloudKit.shared.textCreateFileAsync(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.session.account, + path: fileNamePath, + name: "textCreateFile") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + let metadata = await NCManageDatabaseCreateMetadata().createMetadataAsync( + fileName: fileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + + AnalyticsHelper.shared.trackCreateFile(metadata: metadata) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: controller) { + controller.navigationController?.pushViewController(vc, animated: true) + } + } + } + + if self.editorId == NCGlobal.shared.editorCollabora { + Task { + + let results = await NextcloudKit.shared.createRichdocumentsAsync(path: fileNamePath, templateId: templateIdentifier, account: session.account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.session.account, + path: fileNamePath, + name: "CreateRichdocuments") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + + let metadata = await NCManageDatabaseCreateMetadata().createMetadataAsync( + fileName: fileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: controller) { + controller.navigationController?.pushViewController(vc, animated: true) + } + } + } + } + + @objc func cancel() { + + self.dismiss(animated: true, completion: nil) + } + + // MARK: NC API + + func getTemplate() { + + indicator.color = NCBrandColor.shared.brandElement + indicator.startAnimating() + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + NextcloudKit.shared.textGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.session.account, let templates = templates { + + for template in templates { + + var temp = NKEditorTemplate() + + temp.identifier = template.identifier + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = template.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + var temp = NKEditorTemplate() + + temp.identifier = "" + if self.editorId == NCGlobal.shared.editorText { + temp.ext = "md" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + + } + + if self.editorId == NCGlobal.shared.editorCollabora { + + NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate, account: session.account) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.session.account { + + for template in templates! { + + var temp = NKEditorTemplate() + + temp.identifier = "\(template.templateId)" +// temp.delete = template.delete + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview +// temp.type = template.type + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + var temp = NKEditorTemplate() + + temp.identifier = "" + if self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + } + } + + func getImageFromTemplate(name: String, preview: String, indexPath: IndexPath) { + + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png" + + NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, account: session.account, requestHandler: { _ in + + }, taskHandler: { _ in + + }, progressHandler: { _ in + + }) { account, _, _, _, _, _, error in + + if error == .success && account == self.session.account { + self.collectionView.reloadItems(at: [indexPath]) + } else if error != .success { + print("\(error.errorCode)") + } else { + print("[ERROR] It has been changed user during networking process, error.") + } + } + } + + func getFileExtension() -> String { + switch typeTemplate { + case NCGlobal.shared.editorText: + return "md" + case NCGlobal.shared.templateDocument: + return "docx" + case NCGlobal.shared.templateSpreadsheet: + return "xlsx" + case NCGlobal.shared.templatePresentation: + return "pptx" + default: + return "" + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift new file mode 100644 index 0000000000..cb7553eda7 --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -0,0 +1,34 @@ +// +// FolderPathCustomCell.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +class FolderPathCustomCell: XLFormButtonCell{ + + @IBOutlet weak var photoLabel: UILabel! + @IBOutlet weak var folderImage: UIImageView! + @IBOutlet weak var bottomLineView: UIView! + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + if (rowDescriptor.tag == "PhotoButtonDestinationFolder"){ + bottomLineView.isHidden = true + }else{ + bottomLineView.isHidden = false + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib new file mode 100644 index 0000000000..caca063abf --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift new file mode 100644 index 0000000000..3983e413a6 --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -0,0 +1,71 @@ +// +// NCCreateDocumentCustomTextField.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +class NCCreateDocumentCustomTextField: XLFormBaseCell,UITextFieldDelegate { + + @IBOutlet weak var fileNameTextField: UITextField! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + fileNameTextField.delegate = self + + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if fileNameTextField == textField { + if let rowDescriptor = rowDescriptor { + if let text = textField.text{ + if (text + " ").isEmpty == false { + rowDescriptor.value = self.fileNameTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + + + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(fileNameTextField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(fileNameTextField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 45 + } +} diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib new file mode 100644 index 0000000000..3fd86bb2d6 --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +