diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 5bf775fb4a..8a253100c7 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -113,6 +113,12 @@ C0DECA022F65000100C0D001 /* NCCameraRollTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DECA012F65000100C0D001 /* NCCameraRollTests.swift */; }; CA1700013000000000000001 /* NCUploadedAssetsCleanupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1700013000000000000002 /* NCUploadedAssetsCleanupTests.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 */; }; + B5D45E6D2DA510E000773929 /* NCRenameFile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5D45E6A2DA510E000773929 /* NCRenameFile.storyboard */; }; + B5D45E6E2DA510E000773929 /* NCRenameFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D45E6B2DA510E000773929 /* NCRenameFile.swift */; }; + B5D45E702DA5119A00773929 /* RenameFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D45E6F2DA5119A00773929 /* RenameFileTests.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 */; }; F0A1B2C430B5000100D4E5F6 /* NCMediaViewerFloatingTitleViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A1B2C330B5000100D4E5F6 /* NCMediaViewerFloatingTitleViewTests.swift */; }; F0A1B2C630B6000100D4E5F6 /* NCImageZoomViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A1B2C530B6000100D4E5F6 /* NCImageZoomViewTests.swift */; }; @@ -1448,6 +1454,10 @@ AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; B601AA002F10000100C0D3E5 /* NCLogFilesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCLogFilesView.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 = ""; }; + B5D45E6A2DA510E000773929 /* NCRenameFile.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCRenameFile.storyboard; sourceTree = ""; }; + B5D45E6B2DA510E000773929 /* NCRenameFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCRenameFile.swift; sourceTree = ""; }; + B5D45E6F2DA5119A00773929 /* RenameFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameFileTests.swift; 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; }; C0DECA012F65000100C0D001 /* NCCameraRollTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCameraRollTests.swift; sourceTree = ""; }; @@ -2350,6 +2360,9 @@ 439616AAA50F4360A761E5EF /* NCAlbumsPersistenceTests.swift */, C0DECA012F65000100C0D001 /* NCCameraRollTests.swift */, CA1700013000000000000002 /* NCUploadedAssetsCleanupTests.swift */, + AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B5D45E6F2DA5119A00773929 /* RenameFileTests.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2388,6 +2401,15 @@ path = Advanced; sourceTree = ""; }; + B5D45E6C2DA510E000773929 /* Rename file */ = { + isa = PBXGroup; + children = ( + B5D45E6A2DA510E000773929 /* NCRenameFile.storyboard */, + B5D45E6B2DA510E000773929 /* NCRenameFile.swift */, + ); + path = "Rename file"; + sourceTree = ""; + }; C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = { isa = PBXGroup; children = ( @@ -3894,6 +3916,7 @@ F7381ED9218218A4000B1560 /* Offline */, F713418B2597513800768D21 /* PushNotification */, F765F72E25237E3F00391DBE /* Recent */, + B5D45E6C2DA510E000773929 /* Rename file */, F7CADB3D23CCDDA1000EEC78 /* RichWorkspace */, F76882042C0DD1E7001CF441 /* Settings */, F758B41E212C516300515F55 /* Scan document */, @@ -4596,6 +4619,8 @@ F7381EE5218218C9000B1560 /* NCOffline.storyboard in Resources */, F768822D2C0DD1E7001CF441 /* Acknowledgements.rtf in Resources */, F76D3CF32428B94E005DFA87 /* NCViewerPDFSearchCell.xib in Resources */, + F7CA212E25F1333300826ABB /* NCAccountRequest.storyboard in Resources */, + B5D45E6D2DA510E000773929 /* NCRenameFile.storyboard in Resources */, F717402D24F699A5000C87D5 /* NCFavorite.storyboard in Resources */, F77A6B31301112C5007103E6 /* NCMediaSectionHeader.xib in Resources */, F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */, @@ -4784,6 +4809,7 @@ C0DECA022F65000100C0D001 /* NCCameraRollTests.swift in Sources */, CA1700013000000000000001 /* NCUploadedAssetsCleanupTests.swift in Sources */, F372087D2BAB4C0F006B5430 /* TestConstants.swift in Sources */, + B5D45E702DA5119A00773929 /* RenameFileTests.swift in Sources */, F78E2D6C29AF02DB0024D4F3 /* Database.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5532,6 +5558,7 @@ F763D29D2A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, F76882252C0DD1E7001CF441 /* NCSettingsAdvancedModel.swift in Sources */, F7C7B489245EBA4100D93E60 /* NCViewerQuickLook.swift in Sources */, + B5D45E6E2DA510E000773929 /* NCRenameFile.swift in Sources */, F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */, F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */, F76882272C0DD1E7001CF441 /* NCManageE2EEView.swift in Sources */, diff --git a/Tests/NextcloudUnitTests/RenameFileTests.swift b/Tests/NextcloudUnitTests/RenameFileTests.swift new file mode 100644 index 0000000000..c0aa06f029 --- /dev/null +++ b/Tests/NextcloudUnitTests/RenameFileTests.swift @@ -0,0 +1,108 @@ +// +// RenameFileTests.swift +// NextcloudTests +// +// Created by A200073704 on 14/06/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit + +class RenameFileTests: 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 testStoryboardPresence() { + + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + XCTAssertNotNil(storyboard, "Storyboard 'NCRenameFile' should be present") + + } + + func testRenameButtonPresence() { + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCRenameFile else { + XCTFail("Failed to instantiate view controller from storyboard") + return + } + + _ = viewController.view // Load the view + + let renameButton = viewController.renameButton + XCTAssertNotNil(renameButton, "Rename button should be present") + } + + func testRenameButtonBackgroundColor() { + + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCRenameFile else { + XCTFail("Failed to instantiate view controller from storyboard") + return + } + + _ = viewController.view // Load the view + + let color = NCBrandColor.shared.brand.cgColor + let renameButton = viewController.renameButton.layer.backgroundColor + + XCTAssertEqual(renameButton,color, "Rename Button Bcakground Color should be brand") + } + + func testCancelButtonPresence() { + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCRenameFile else { + XCTFail("Failed to instantiate view controller from storyboard") + return + } + + _ = viewController.view // Load the view + + let cancelButton = viewController.cancelButton + XCTAssertNotNil(cancelButton, "Cancel button should be present") + } + + func testImageViewPresence() { + + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCRenameFile else { + XCTFail("Failed to instantiate view controller from storyboard") + return + } + + _ = viewController.view // Load the view + + let imageView = viewController.previewFile + XCTAssertNotNil(imageView, "UIImageView should be present on the storyboard") + } + + func testTextFiledPresence() { + + let storyboard = UIStoryboard(name: "NCRenameFile", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCRenameFile else { + XCTFail("Failed to instantiate view controller from storyboard") + return + } + + _ = viewController.view // Load the view + + let textField = viewController.fileNameNoExtension + let textFieldExt = viewController.ext + + XCTAssertNotNil(textField, "FileNameNoExtention TextFiled should be present on the storyboard") + XCTAssertNotNil(textFieldExt, "Extension TextFiled should be present on the storyboard") + + } + + + +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 881e56f788..5c73b3e972 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -4,10 +4,12 @@ import UIKit import SwiftUI +import Realm import RealmSwift import NextcloudKit import EasyTipView import LucidBanner +import MoEngageInApps class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @@ -106,6 +108,10 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, internal let heightHeaderRecommendations: CGFloat = 160 internal let heightHeaderSection: CGFloat = 30 + var isTransitioning: Bool = false + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + var pushed: Bool = false + var emptyDataSet: NCEmptyDataSet? internal var isLayoutPhoto: Bool { layoutForView?.layout == global.layoutPhotoRatio || layoutForView?.layout == global.layoutPhotoSquare @@ -157,7 +163,17 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) } - internal var isNumberOfItemsInAllSectionsNull: Bool { + var defaultPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, NCGlobal.shared.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) + return predicate + } + + var personalFilesOnlyPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) + return predicate + } + + var isNumberOfItemsInAllSectionsNull: Bool { var totalItems = 0 for section in 0.. Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as? UINavigationController + newViewController?.modalPresentationStyle = .fullScreen + self.present(newViewController!, animated: true, completion: nil) + } + + func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { + let viewController = presentationController.presentedViewController + + if viewController is NCViewerRichWorkspaceWebView { + closeRichWorkspaceWebView() + } + } + // MARK: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { @@ -609,6 +675,20 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, // MARK: - TAP EVENT + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -621,6 +701,115 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, }) } + func openMenuItems(with objectId: String?, gestureRecognizer: UILongPressGestureRecognizer) { + if gestureRecognizer.state != .began { return } + + var listMenuItems: [UIMenuItem] = [] + let touchPoint = gestureRecognizer.location(in: collectionView) + + becomeFirstResponder() + + if !serverUrl.isEmpty { + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) + } + + if !listMenuItems.isEmpty { + UIMenuController.shared.menuItems = listMenuItems + UIMenuController.shared.showMenu(from: collectionView, rect: CGRect(x: touchPoint.x, y: touchPoint.y, width: 0, height: 0)) + } + } + + // MARK: - Menu Item + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if #selector(pasteFilesMenu(_:)) == action { + if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { + return true + } + } else if #selector(copyMenuFile(_:)) == action { + return true + } else if #selector(moveMenuFile(_:)) == action { + return true + } + + return false + } + + @objc func pasteFilesMenu(_ sender: Any?) { + Task {@MainActor in + guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(account: session.account) else { + return + } + let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + let token = showHudBanner( + scene: scene, + title: NSLocalizedString("_upload_in_progress_", comment: "")) + + 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) + guard let data = UIPasteboard.general.data(forPasteboardType: item.key, + inItemSet: IndexSet([index]))?.first + else { + continue + } + let fileName = results.name + "_" + NCPreferences().incrementalNumber + "." + results.ext + let serverUrlFileName = utilityFileSystem.createServerUrl(serverUrl: serverUrl, fileName: fileName) + let ocIdUpload = UUID().uuidString + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId( + ocIdUpload, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase + ) + do { + try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) + } catch { + continue + } + + let resultsUpload = await NCNetworking.shared.uploadFile(account: session.account, + fileNameLocalPath: fileNameLocalPath, + serverUrlFileName: serverUrlFileName) { _ in + } progressHandler: { _, _, fractionCompleted in + Task {@MainActor in + LucidBanner.shared.update(progress: fractionCompleted, for: token) + } + } + + if resultsUpload.error == .success, + let etag = resultsUpload.etag, + let ocId = resultsUpload.ocId { + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId( + ocId, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + self.utilityFileSystem.moveFile(atPath: fileNameLocalPath, toPath: toPath) + NCManageDatabase.shared.addLocalFile( + account: session.account, + etag: etag, + ocId: ocId, + fileName: fileName) + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadData(serverUrl: serverUrl, requestData: true, status: nil) + } + } + } else { + Task {@MainActor in + await showErrorBanner(scene: scene, + errorDescription: resultsUpload.error.errorDescription, + errorCode: resultsUpload.error.errorCode) + } + } + } + } + LucidBanner.shared.dismiss() + } + } + // MARK: - DataSource @MainActor diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 6d830a914b..7c861bafcc 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -40,6 +40,26 @@ final class NCGlobal: Sendable { let twoFactorNotificatioName = "twofactor_nextcloud_notification" let termsOfServiceName = "terms_of_service" + // Nextcloud version + // + let nextcloudVersion18: Int = 18 + let nextcloudVersion20: Int = 20 + let nextcloudVersion23: Int = 23 + let nextcloudVersion24: Int = 24 + let nextcloudVersion25: Int = 25 + let nextcloudVersion26: Int = 26 + let nextcloudVersion27: Int = 27 + let nextcloudVersion28: Int = 28 + let nextcloudVersion30: Int = 30 + let nextcloudVersion31: Int = 31 + let nextcloudVersion32: Int = 32 + let nextcloudVersion33: Int = 33 + let nextcloudVersionFuture: Int = 99999 + + // Nextcloud unsupported + // + let nextcloud_unsupported_version: Int = 20 + // OFFICE // let editorText: String = "text" @@ -51,13 +71,14 @@ final class NCGlobal: Sendable { // Intro selector // let introLogin: Int = 0 + let introSignup: Int = 1 let introSignUpWithProvider: Int = 1 // Avatar // let avatarSize: Int = 128 * Int(UIScreen.main.scale) let avatarSizeRounded: Int = 128 - + // Preview size // let size1024: CGSize = CGSize(width: 1024, height: 1024) @@ -88,11 +109,27 @@ final class NCGlobal: Sendable { // E2EE // let e2eePassphraseTest = "more over television factory tendency independence international intellectual impress interest sentence pony" + let e2eeVersions = ["1.1", "1.2", "2.0"] + let e2eeVersionV11 = "1.1" + let e2eeVersionV12 = "1.2" + let e2eeVersionV20 = "2.0" + + func isE2eeVersion2(_ version: String) -> Bool { + if version == "2.0" || version == "2.1" { + return true + } + return false + } // CHUNK let chunkSizeMBCellular = 10000000 let chunkSizeMBEthernetOrWiFi = 100000000 + // Video + // + let maxHTTPCache: Int64 = 10000000000 // 10 GB + let fileNameVideoEncoded: String = "video_encoded.mp4" + // NCViewerProviderContextMenu // let maxAutoDownload: UInt64 = 50000000 // 50MB @@ -109,6 +146,7 @@ final class NCGlobal: Sendable { let layoutViewOffline = "LayoutOffline" let layoutViewFavorite = "LayoutFavorite" let layoutViewFiles = "LayoutFiles" + let layoutViewTransfers = "LayoutTransfers" let layoutViewRecent = "LayoutRecent" let layoutViewShares = "LayoutShares" let layoutViewShareExtension = "LayoutShareExtension" @@ -119,7 +157,35 @@ final class NCGlobal: Sendable { // let buttonMoreMore = "more" let buttonMoreLock = "moreLock" - + let buttonMoreStop = "stop" + + // Standard height sections header/footer + // + let heightButtonsView: CGFloat = 50 + let heightHeaderTransfer: CGFloat = 50 + let heightSection: CGFloat = 30 + let heightFooter: CGFloat = 1 + let heightFooterButton: CGFloat = 30 + let endHeightFooter: CGFloat = 85 + + + // Text - OnlyOffice - Collabora - QuickLook + // +// let editorText = "text" + let editorOnlyoffice = "onlyoffice" +// let editorCollabora = "collabora" + let editorQuickLook = "quicklook" + + let onlyofficeDocx = "onlyoffice_docx" + let onlyofficeXlsx = "onlyoffice_xlsx" + let onlyofficePptx = "onlyoffice_pptx" + + // Template + // + let templateDocument = "document" + let templateSpreadsheet = "spreadsheet" + let templatePresentation = "presentation" + // Rich Workspace // let fileNameRichWorkspace = "Readme.md" @@ -131,6 +197,7 @@ final class NCGlobal: Sendable { // Error // + let errorRequestExplicityCancelled: Int = 15 let errorNotModified: Int = 304 let errorBadRequest: Int = 400 let errorUnauthorized: Int = 401 @@ -141,6 +208,7 @@ final class NCGlobal: Sendable { let errorPreconditionFailed: Int = 412 let errorUnsupportedMediaType: Int = 415 let errorExpectationFailed: Int = 417 + let errorWebDAVLocked: Int = 423 let errorInternalServerError: Int = 500 let errorMaintenance: Int = 503 let errorQuota: Int = 507 @@ -190,9 +258,10 @@ final class NCGlobal: Sendable { let errorE2EEReadOnly: Int = -98021 let errorE2EEServerKeyChanged: Int = -98022 - // Selector // + let selectorReadFile = "readFile" + let selectorListingFavorite = "listingFavorite" let selectorLoadFileView = "loadFileView" let selectorLoadFileQuickLook = "loadFileQuickLook" let selectorOpenIn = "openIn" @@ -202,11 +271,14 @@ final class NCGlobal: Sendable { let selectorUploadAutoUpload = "uploadAutoUpload" let selectorUploadFile = "uploadFile" let selectorUploadFileShareExtension = "uploadFileShareExtension" + let selectorUploadFileNODelete = "UploadFileNODelete" let selectorSaveAlbum = "saveAlbum" let selectorSaveAsScan = "saveAsScan" let selectorOpenDetail = "openDetail" let selectorSynchronizationOffline = "synchronizationOffline" + let selectorPrint = "print" + let selectorDeleteFile = "deleteFile" // Metadata : Status // @@ -235,14 +307,27 @@ final class NCGlobal: Sendable { let metadataStatusUploadingAllMode = [1,2,3] let metadataStatusDownloadingAllMode = [-1, -2, -3] let metadataStatusForScreenAwake = [-1, -2, 1, 2] + let metadataStatusInTransfer = [-1, -2, 1, 2] + let metadataStatusFileDown = [-1, -2, -3] let metadataStatusHideInView = [1, 2, 3, 11] + let metadataStatusHideInFileExtension = [1, 2, 3, 10, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] let metadatasStatusInError = [-3, 3] + let metadatasStatusInProgress = [-2, 2] let metadatasStatusInWaitingDownloadUpload = [-1, 1] let metadatasStatusDownloadingUploading = [-2, 2] + let metadataStatusObserveNetworkingProcess = [-1, 1, 10, 11, 12, 13, 14, 15] + let metadataStatusObserveTrasfers = [-2, 2, 10, 11, 12, 13, 14, 15] + + let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] + + // Hidden files included in the read + // + let includeHiddenFiles: [String] = [".LivePhoto"] + // Auto upload subfolder granularity // let subfolderGranularityDaily = 2 @@ -255,11 +340,44 @@ final class NCGlobal: Sendable { let notificationCenterChangeTheming = "changeTheming" // userInfo: account let notificationCenterRichdocumentGrabFocus = "richdocumentGrabFocus" let notificationCenterReloadDataNCShare = "reloadDataNCShare" + let notificationCenterDidCreateShareLink = "didCreateShareLink" + let notificationCenterCloseRichWorkspaceWebView = "closeRichWorkspaceWebView" let notificationCenterReloadAvatar = "reloadAvatar" + let notificationCenterClearCache = "clearCache" let notificationCenterCheckUserDelaultErrorDone = "checkUserDelaultErrorDone" // userInfo: account, controller let notificationCenterServerDidUpdate = "serverDidUpdate" // userInfo: account let notificationCenterNetworkReachability = "networkReachability" + let notificationCenterClearCache = "clearCache" + let notificationCenterCreateMediaCacheEnded = "createMediaCacheEnded" + let notificationCenterUpdateNotification = "updateNotification" + + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error + let notificationCenterDeleteFile = "deleteFile" // userInfo: [ocId], error + let notificationCenterCopyMoveFile = "copyMoveFile" // userInfo: [ocId] serverUrl, account, dragdrop, type (copy, move) + let notificationCenterMoveFile = "moveFile" // userInfo: [ocId], [indexPath], error + let notificationCenterCopyFile = "copyFile" // userInfo: [ocId], [indexPath], error + let notificationCenterFavoriteFile = "favoriteFile" // userInfo: ocId, serverUrl + let notificationCenterFileExists = "fileExists" // userInfo: ocId, fileExists + let notificationCenterReloadDataSource = "reloadDataSource" // userInfo: serverUrl?, clearDataSource + let notificationCenterGetServerData = "getServerData" // userInfo: serverUrl? + + let notificationCenterChangeStatusFolderE2EE = "changeStatusFolderE2EE" // userInfo: serverUrl + + let notificationCenterDownloadStartFile = "downloadStartFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + let notificationCenterDownloadedFile = "downloadedFile" // userInfo: ocId, ocIdTransfer, session, session, serverUrl, account, selector, error + let notificationCenterDownloadCancelFile = "downloadCancelFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + + let notificationCenterUploadStartFile = "uploadStartFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, sessionSelector + let notificationCenterUploadedFile = "uploadedFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, ocIdTransfer, error + let notificationCenterUploadedLivePhoto = "uploadedLivePhoto" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, ocIdTransfer, error + let notificationCenterUploadCancelFile = "uploadCancelFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + + let notificationCenterProgressTask = "progressTask" // userInfo: account, ocId, ocIdTransfer, session, serverUrl, status, chunk, e2eEncrypted, progress, totalBytes, totalBytesExpected + + let notificationCenterUpdateBadgeNumber = "updateBadgeNumber" // userInfo: counterDownload, counterUpload + + let notificationCenterCreateFolder = "createFolder" // userInfo: ocId, serverUrl, account, withPush, sceneIdentifier let notificationCenterMenuSearchTextPDF = "menuSearchTextPDF" let notificationCenterMenuGotToPageInPDF = "menuGotToPageInPDF" @@ -267,6 +385,7 @@ final class NCGlobal: Sendable { let notificationCenterOpenMediaDetail = "openMediaDetail" // userInfo: ocId let notificationCenterDismissScanDocument = "dismissScanDocument" + let notificationCenterDismissUploadAssets = "dismissUploadAssets" let notificationCenterEnableSwipeGesture = "enableSwipeGesture" let notificationCenterDisableSwipeGesture = "disableSwipeGesture" @@ -274,6 +393,8 @@ final class NCGlobal: Sendable { let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" + let notificationCenterFavoriteStatusChanged = "favoriteStatusChanged" + let notificationCenterUserInteractionMonitor = "serInteractionMonitor" let notificationCenterNetworkingProcess = "networkingProcess" @@ -294,6 +415,7 @@ final class NCGlobal: Sendable { let networkingStatusUploaded = "statusUploaded" let networkingStatusReloadAvatar = "statusReloadAvatar" + let notificationCenterUpdateIcons = "updateIcons" // TIP // @@ -302,6 +424,7 @@ final class NCGlobal: Sendable { let tipScanAddImage = "tipScanAddImage" let tipMediaDetailView = "tipMediaDetailView" let tipAutoUploadButton = "tipAutoUploadButton" + let tipAutoUpload = "tipAutoUpload" // ACTION // @@ -376,6 +499,20 @@ final class NCGlobal: Sendable { // let taskDescriptionRetrievesProperties = "retrievesProperties" let taskDescriptionSynchronization = "synchronization" + let taskDescriptionDeleteFileOrFolder = "deleteFileOrFolder" + + // MoEngage App Version + // + let moEngageAppVersion = 854 + + // Filename Mask and Type + // + let keyFileNameMask = "fileNameMask" + let keyFileNameType = "fileNameType" + let keyFileNameAutoUploadMask = "fileNameAutoUploadMask" + let keyFileNameAutoUploadType = "fileNameAutoUploadType" + let keyFileNameOriginal = "fileNameOriginal" + let keyFileNameOriginalAutoUpload = "fileNameOriginalAutoUpload" // LOG TAG // @@ -395,11 +532,19 @@ final class NCGlobal: Sendable { let logTagMediaPlaceholder = "MEDIA PLACEHOLDER" let logTagMediaPreview = "MEDIA PREVIEW" let logTagBackgroundUpload = "BACKGROUND UPLOAD" + let logTagMetadataTransfers = "METADATA TRANSFERS" + let logSpeedUpSyncMetadata = "SYNC METADATA" + let logNetworkingTasks = "NETWORKING TASKS" // USER DEFAULTS // let udMigrationMultiDomains = "migrationMultiDomains" let udLastVersion = "lastVersion" + + // Album + // + let selectedTabIndexAlbum: Int = 3 + } /** diff --git a/iOSClient/Rename file/NCRenameFile.storyboard b/iOSClient/Rename file/NCRenameFile.storyboard new file mode 100644 index 0000000000..6e98f850cc --- /dev/null +++ b/iOSClient/Rename file/NCRenameFile.storyboard @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Rename file/NCRenameFile.swift b/iOSClient/Rename file/NCRenameFile.swift new file mode 100644 index 0000000000..404afeedba --- /dev/null +++ b/iOSClient/Rename file/NCRenameFile.swift @@ -0,0 +1,275 @@ +// +// NCRenameFile.swift +// Nextcloud +// +// Created by Marino Faggiana on 26/02/21. +// Copyright © 2021 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 + +public protocol NCRenameFileDelegate: AnyObject { + func rename(fileName: String, fileNameNew: String) +} + +// optional func +public extension NCRenameFileDelegate { + func rename(fileName: String, fileNameNew: String) {} +} + +class NCRenameFile: UIViewController, UITextFieldDelegate { + + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var separatorHeightContraint: NSLayoutConstraint! + @IBOutlet weak var previewFile: UIImageView! + @IBOutlet weak var fileNameNoExtension: UITextField! + @IBOutlet weak var point: UILabel! + @IBOutlet weak var ext: UITextField! + @IBOutlet weak var fileNameNoExtensionTrailingContraint: NSLayoutConstraint! + @IBOutlet weak var cancelButton: UIButton! + @IBOutlet weak var renameButton: UIButton! + @IBOutlet weak var seperator: UIView! + let imageCache = NCImageCache.shared + + let width: CGFloat = 300 + let height: CGFloat = 350 + + var metadata: tableMetadata? + var indexPath: IndexPath = IndexPath() + var fileName: String? + var imagePreview: UIImage? + var disableChangeExt: Bool = false + weak var delegate: NCRenameFileDelegate? + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + if let metadata = self.metadata { + + if metadata.directory { + titleLabel.text = NSLocalizedString("_rename_folder_", comment: "") + } else { + titleLabel.text = NSLocalizedString("_rename_file_", comment: "") + } + separatorHeightContraint.constant = 0.3 + seperator.backgroundColor = NCBrandColor.shared.seperatorRename + fileNameNoExtension.backgroundColor = .secondarySystemGroupedBackground + fileNameNoExtension.text = (metadata.fileNameView as NSString).deletingPathExtension + fileNameNoExtension.delegate = self + fileNameNoExtension.becomeFirstResponder() + + ext.text = metadata.fileExtension + ext.delegate = self + if disableChangeExt { + ext.isEnabled = false + ext.textColor = .lightGray + } + NCUtility().createImageFileFrom(metadata: metadata) + if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) { + Task { @MainActor in + previewFile.image = image + } + } + + previewFile.layer.cornerRadius = 10 + previewFile.layer.masksToBounds = true + + if metadata.directory { + + if imagePreview == nil { + previewFile.image = imageCache.getFolder() + } + + ext.isHidden = true + point.isHidden = true + fileNameNoExtensionTrailingContraint.constant = 20 + + } else { + + if imagePreview == nil { + previewFile.image = imageCache.getImageFile() + } + + fileNameNoExtensionTrailingContraint.constant = 90 + } + + } else if let fileName = self.fileName { + + titleLabel.text = NSLocalizedString("_rename_file_", comment: "") + + fileNameNoExtension.text = (fileName as NSString).deletingPathExtension + fileNameNoExtension.delegate = self + fileNameNoExtension.becomeFirstResponder() + fileNameNoExtensionTrailingContraint.constant = 90 + + ext.text = (fileName as NSString).pathExtension + ext.delegate = self + + if imagePreview == nil { + previewFile.image = imageCache.getImageFile() + } else { + previewFile.image = imagePreview + } + previewFile.layer.cornerRadius = 10 + previewFile.layer.masksToBounds = true + } + + cancelButton.setTitle(NSLocalizedString("_cancel_", comment: ""), for: .normal) + cancelButton.setTitleColor(NCBrandColor.shared.iconImageColor, for: .normal) + cancelButton.layer.cornerRadius = 5 + cancelButton.layer.masksToBounds = true + cancelButton.layer.borderWidth = 0.3 + cancelButton.layer.borderColor = NCBrandColor.shared.iconImageColor.cgColor + + renameButton.setTitle(NSLocalizedString("_rename_", comment: ""), for: .normal) + renameButton.setTitleColor(NCBrandColor.shared.brandText, for: .normal) + renameButton.layer.cornerRadius = 5 + renameButton.layer.masksToBounds = true + renameButton.layer.backgroundColor = NCBrandColor.shared.brand.cgColor + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Re-evaluate in-app messages after viewDidAppear + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "viewDidAppear") + + if metadata == nil && fileName == nil { + dismiss(animated: true) + } + + fileNameNoExtension.selectAll(nil) + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + renameFile(textField) + return true + } + + // MARK: - Action + + @IBAction func cancel(_ sender: Any) { + + dismiss(animated: true) + } + + @IBAction func renameFile(_ sender: Any) { + + var fileNameNoExtensionNew = "" + var extNew = "" + var fileNameNew = "" + + if let metadata = self.metadata { + + let extCurrent = (metadata.fileNameView as NSString).pathExtension + + if fileNameNoExtension.text == nil || fileNameNoExtension.text?.count == 0 { + return self.fileNameNoExtension.text = (metadata.fileNameView as NSString).deletingPathExtension + } else { + fileNameNoExtensionNew = fileNameNoExtension.text! + } + + if metadata.directory { + + fileNameNew = fileNameNoExtensionNew + renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath) + + } else { + + if ext.text == nil || ext.text?.count == 0 { + return self.ext.text = metadata.fileExtension + } else { + extNew = ext.text! + } + + if extNew != extCurrent { + + let message = String(format: NSLocalizedString("_rename_ext_message_", comment: ""), extNew, extCurrent) + let alertController = UIAlertController(title: NSLocalizedString("_rename_ext_title_", comment: ""), message: message, preferredStyle: .alert) + + var title = NSLocalizedString("_use_", comment: "") + " ." + extNew + alertController.addAction(UIAlertAction(title: title, style: .default, handler: { _ in + + fileNameNew = fileNameNoExtensionNew + "." + extNew + self.renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: self.indexPath) + })) + + title = NSLocalizedString("_keep_", comment: "") + " ." + extCurrent + alertController.addAction(UIAlertAction(title: title, style: .default, handler: { _ in + self.ext.text = metadata.fileExtension + })) + + self.present(alertController, animated: true) + + } else { + + fileNameNew = fileNameNoExtensionNew + "." + extNew + renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath) + } + } + + } else if let fileName = self.fileName { + + if fileNameNoExtension.text == nil || fileNameNoExtension.text?.count == 0 { + return fileNameNoExtension.text = (fileName as NSString).deletingPathExtension + } else if ext.text == nil || ext.text?.count == 0 { + return ext.text = (fileName as NSString).pathExtension + } + + fileNameNew = (fileNameNoExtension.text ?? "") + "." + (ext.text ?? "") + self.dismiss(animated: true) { + self.delegate?.rename(fileName: fileName, fileNameNew: fileNameNew) + } + } + } + + // MARK: - Networking + + func renameMetadata(_ metadata: tableMetadata, fileNameNew: String, indexPath: IndexPath) { + + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, fileNameNew)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return + } + + NCActivityIndicator.shared.start() + +// NCNetworking.shared.renameMetadata(metadata, fileNameNew: fileNameNew) + NCNetworking.shared.renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath, viewController: self) { error in + + NCActivityIndicator.shared.stop() + + if error == .success { + + DispatchQueue.main.async { + self.dismiss(animated: true) + } + + } else { + + NCContentPresenter().showError(error: error) + } + } + } +}