diff --git a/File Provider Extension/FileProviderData.swift b/File Provider Extension/FileProviderData.swift index 9c36bb5bf0..a19d3197b2 100644 --- a/File Provider Extension/FileProviderData.swift +++ b/File Provider Extension/FileProviderData.swift @@ -32,7 +32,7 @@ class FileProviderData: NSObject { case workingSet } - // MARK: - + // MARK: - @discardableResult func setupAccount(domain: NSFileProviderDomain? = nil, diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8dd4c5545f..25a0172e9a 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,20 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B54315362DA64EB100981E7E /* PrivacyPolicyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */; }; + B54315412DA669C700981E7E /* InitialPrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */; }; + B54315422DA669C700981E7E /* RequiredDataCollectionSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */; }; + B54315432DA669C700981E7E /* PrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */; }; + B54315442DA669C700981E7E /* AnalysisDataCollectionSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */; }; + B54315452DA669C700981E7E /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */; }; + B54315462DA669C700981E7E /* SaveSettingsCustomButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */; }; + B54315472DA669C700981E7E /* AnalysisDataCollectionSwitch.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */; }; + B54315482DA669C700981E7E /* NCSettings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B543153A2DA669C700981E7E /* NCSettings.storyboard */; }; + B54315492DA669C700981E7E /* SaveSettingsCustomButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */; }; + B543154A2DA669C700981E7E /* RequiredDataCollectionSwitch.xib in Resources */ = {isa = PBXBuildFile; fileRef = B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */; }; + 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 */; }; F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; }; @@ -1221,6 +1235,18 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyTest.swift; sourceTree = ""; }; + B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisDataCollectionSwitch.swift; sourceTree = ""; }; + B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AnalysisDataCollectionSwitch.xib; sourceTree = ""; }; + B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPrivacySettingsViewController.swift; sourceTree = ""; }; + B543153A2DA669C700981E7E /* NCSettings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCSettings.storyboard; sourceTree = ""; }; + B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyViewController.swift; sourceTree = ""; }; + B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewController.swift; sourceTree = ""; }; + B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredDataCollectionSwitch.swift; sourceTree = ""; }; + B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RequiredDataCollectionSwitch.xib; sourceTree = ""; }; + B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSettingsCustomButtonCell.swift; sourceTree = ""; }; + B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SaveSettingsCustomButtonCell.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 = ""; }; @@ -2024,6 +2050,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2518,6 +2546,16 @@ F76882042C0DD1E7001CF441 /* Settings */ = { isa = PBXGroup; children = ( + B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */, + B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */, + B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */, + B543153A2DA669C700981E7E /* NCSettings.storyboard */, + B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */, + B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */, + B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */, + B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */, + B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */, + B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */, F768820B2C0DD1E7001CF441 /* Settings */, F76882162C0DD1E7001CF441 /* AutoUpload */, F389C9F32CEE381E00049762 /* SelectAlbum */, @@ -3941,6 +3979,11 @@ 3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */, AF93471C27E2361E002537EE /* NCShareHeader.xib in Resources */, F75D901F2D2BE12E003E740B /* NCRecommendationsCell.xib in Resources */, + B54315472DA669C700981E7E /* AnalysisDataCollectionSwitch.xib in Resources */, + B54315482DA669C700981E7E /* NCSettings.storyboard in Resources */, + B54315492DA669C700981E7E /* SaveSettingsCustomButtonCell.xib in Resources */, + B543154A2DA669C700981E7E /* RequiredDataCollectionSwitch.xib in Resources */, + F76032A0252F0F8E0015A421 /* NCTransferCell.xib in Resources */, F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */, F7A48415297028FC00BD1B49 /* Nextcloud Hub.png in Resources */, F74C0437253F1CDC009762AB /* NCShares.storyboard in Resources */, @@ -4457,6 +4500,12 @@ F72408332B8A27C900F128E2 /* NCMedia+Command.swift in Sources */, F755CB402B8CB13C00CE27E9 /* NCMediaLayout.swift in Sources */, F73EF7B72B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */, + B54315412DA669C700981E7E /* InitialPrivacySettingsViewController.swift in Sources */, + B54315422DA669C700981E7E /* RequiredDataCollectionSwitch.swift in Sources */, + B54315432DA669C700981E7E /* PrivacySettingsViewController.swift in Sources */, + B54315442DA669C700981E7E /* AnalysisDataCollectionSwitch.swift in Sources */, + B54315452DA669C700981E7E /* PrivacyPolicyViewController.swift in Sources */, + B54315462DA669C700981E7E /* SaveSettingsCustomButtonCell.swift in Sources */, AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, F78A18B623CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift in Sources */, AFA2AC8527849604008E1EA7 /* NCActivityCommentView.swift in Sources */, diff --git a/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift b/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift new file mode 100644 index 0000000000..f6265f48e7 --- /dev/null +++ b/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift @@ -0,0 +1,154 @@ +// +// PrivacyPolicyTest.swift +// NextcloudTests +// +// Created by A200073704 on 27/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit +import XLForm + + + class PrivacyPolicyTest: XCTestCase { + + var viewController: InitialPrivacySettingsViewController? + var privacySettingsView = PrivacySettingsViewController() + + override func setUpWithError() throws { + + // To Create an instance of UIStoryboard + let storyboard = UIStoryboard(name: "NCSettings", bundle: nil) + + // To Instantiate UIViewController with Storyboard ID + viewController = storyboard.instantiateViewController(withIdentifier: "privacyPolicyViewController") as? InitialPrivacySettingsViewController + + // Outlets are connected + let _ = viewController?.view + + // Make the viewDidLoad() execute. + viewController?.loadViewIfNeeded() + + } + + override func tearDownWithError() throws { + viewController = nil + } + + func testPrivacyPolicyViewControllerIsOpen() { + + // Check that the InitialPrivacyPolicyViewController gets opened + let storyboard = UIStoryboard(name: "NCSettings", bundle: nil) + if let privacyPolicyViewController = storyboard.instantiateViewController(withIdentifier: "privacyPolicyViewController") as? InitialPrivacySettingsViewController { + let navigationController = UINavigationController(rootViewController: privacyPolicyViewController) + + privacyPolicyViewController.loadViewIfNeeded() + + XCTAssertTrue(navigationController.topViewController is InitialPrivacySettingsViewController, "Privacy policy view controller should be open") + } + } + + func testTextViewHasCorrectText() { + + //Check that the text displayed is correct + let expectedText = NSLocalizedString("_privacy_help_text_after_login_", comment: "") + viewController?.privacySettingsHelpText?.text = expectedText + + let actualText = viewController?.privacySettingsHelpText?.text + XCTAssertEqual(actualText, expectedText, "The text view does not have the expected text") + } + + func testHasAcceptButton() { + + // Check that view has the accept button + let acceptButton = viewController?.acceptButton + + XCTAssertNotNil(acceptButton, "View controller does not have an accept button") + + } + + func testSettingsLinkTypeNavigatesToPrivacySettingsViewController() { + + // Simulate tapping the "Settings" link type + let linkType = LinkType.settings + + UserDefaults.standard.set(true, forKey: "showSettingsButton") + viewController?.privacySettingsHelpText.hyperLink(originalText: viewController?.privacyHelpText ?? "", linkTextsAndTypes: [NSLocalizedString("_key_settings_help_", comment: ""): linkType.rawValue]) + + // Check that the correct view controller was pushed onto the navigation stack + XCTAssertNotNil(viewController?.navigationController?.visibleViewController is PrivacySettingsViewController) + } + + func testPrivacyPolicyLinkType_NavigatesToPrivacyPolicyViewController() { + + // Simulate tapping the "Privacy Policy" link type + let linkType = LinkType.privacyPolicy + + viewController?.privacySettingsHelpText.hyperLink(originalText: viewController?.privacyHelpText ?? "", linkTextsAndTypes: [NSLocalizedString("_key_privacy_help_", comment: ""): linkType.rawValue]) + + // Check that the correct view controller was pushed onto the navigation + XCTAssertNotNil(viewController?.navigationController?.visibleViewController is PrivacyPolicyViewController) + } + + func testCorrectImagePresentOnInitialPrivacySettingsViewController() { + + // Check that the image view has the correct image + let expectedImage = UIImage(named: "dataPrivacy") + XCTAssertNotNil(expectedImage) + } + + func testAcceptButtonHasBackgroundColor() { + + // Check that the accept button has the correct background color + let expectedColor = NCBrandColor.shared.brand + XCTAssertEqual(viewController?.acceptButton.backgroundColor, expectedColor) + + } + + func testShowSaveSettingsButton() { + + privacySettingsView.isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") + + XCTAssertTrue(privacySettingsView.isShowSettingsButton) + + } + + func testRequiredDataCollectionSectionExists() { + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // the section with the title "Required Data Collection" + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") + + // Verify that section was found + XCTAssertNotNil(row, "Expected 'Required Data Collection' section to exist in form.") + } + + func testAnalysisDataCollectionSection() { + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // row with tag "AnalysisDataCollectionSwitch" + row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") + + // Assert that the row exists + XCTAssertNotNil(row, "Expected row with tag 'AnalysisDataCollectionSwitch' to exist in form.") + + // Verify the switch is off + XCTAssertFalse(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn"), "Expected isAnalysisDataCollectionSwitchOn to be false.") + } + + +} diff --git a/iOSClient/Data/NCManageDatabase+LocalFile.swift b/iOSClient/Data/NCManageDatabase+LocalFile.swift index a4308b0ec3..c0c9205813 100644 --- a/iOSClient/Data/NCManageDatabase+LocalFile.swift +++ b/iOSClient/Data/NCManageDatabase+LocalFile.swift @@ -29,14 +29,17 @@ extension NCManageDatabase { // MARK: - Realm Write - /// Adds or updates multiple local file entries corresponding to the given metadata array. - /// Uses async Realm read + single write transaction. Assumes `tableLocalFile` has a primary key. /// - Parameters: - /// - metadatas: Array of `tableMetadata` to map into `tableLocalFile`. - /// - offline: Optional override for the `offline` flag applied to all items. - func addLocalFilesAsync(metadatas: [tableMetadata], offline: Bool? = nil) async { - guard !metadatas.isEmpty else { - return + /// - metadata: The `tableMetadata` containing file details. + /// - offline: Optional flag to mark the file as available offline. + /// - Returns: Nothing. Realm write is performed asynchronously. + func addLocalFileAsync(metadata: tableMetadata, offline: Bool? = nil) async { + // Read (non-blocking): safely detach from Realm thread + let existing: tableLocalFile? = performRealmRead { realm in + realm.objects(tableLocalFile.self) + .filter(NSPredicate(format: "ocId == %@", metadata.ocId)) + .first + .map { tableLocalFile(value: $0) } } // Extract ocIds for efficient lookup @@ -59,20 +62,19 @@ extension NCManageDatabase { // Reuse existing object or create a new one let local = existingMap[metadata.ocId] ?? tableLocalFile() - local.account = metadata.account - local.etag = metadata.etag - local.exifDate = NSDate() - local.exifLatitude = "-1" - local.exifLongitude = "-1" - local.ocId = metadata.ocId - local.fileName = metadata.fileName + addObject.account = metadata.account + addObject.etag = metadata.etag + addObject.exifDate = NSDate() + addObject.exifLatitude = "-1" + addObject.exifLongitude = "-1" + addObject.ocId = metadata.ocId + addObject.fileName = metadata.fileName - if let offline { - local.offline = offline - } - - realm.add(local, update: .all) + if let offline { + addObject.offline = offline } + + realm.add(addObject, update: .all) } } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..448c2a8143 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -139,10 +139,37 @@ extension tableMetadata { (fileNameView as NSString).deletingPathExtension } + var isRenameable: Bool { + if !NCMetadataPermissions.canRename(self) { + return false + } + if lock { + return false + } + if !isDirectoryE2EE && e2eEncrypted { + return false + } + return true + } + + var isPrintable: Bool { + if isDocumentViewableOnly { + return false + } + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKTypeClassFile.image.rawValue { + return true + } + return false + } + var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue + } + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -333,6 +360,17 @@ extension tableMetadata { return !lock || (lockOwner == user && lockOwnerType == 0) } + // Return if is sharable + func isSharable() -> Bool { + guard let capabilities = NCNetworking.shared.capabilities[account] else { + return false + } + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + return false + } + return !e2eEncrypted + } + /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. /// /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically @@ -875,6 +913,34 @@ extension NCManageDatabase { .map { $0.detachedCopy() } } ?? [] } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch sorted {//NCPreferences().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 getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json b/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json new file mode 100644 index 0000000000..4fb0d4c09c --- /dev/null +++ b/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "default copy@2x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "default copy@3x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "default copy@4x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png new file mode 100644 index 0000000000..40c3e7d47f Binary files /dev/null and b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png differ diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png new file mode 100644 index 0000000000..74d9dbb7dd Binary files /dev/null and b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png differ diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png new file mode 100644 index 0000000000..682122c3f2 Binary files /dev/null and b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png differ diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift index 365f38d923..d054be3f44 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift @@ -21,7 +21,7 @@ extension NCCollectionViewCommon { // If a sync task is already running, do not start a new one if let task = syncMetadatasTask, !task.isCancelled { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) return } @@ -45,9 +45,9 @@ extension NCCollectionViewCommon { func stopSyncMetadata() { if let task = syncMetadatasTask { if task.isCancelled { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) } else { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) } } @@ -74,7 +74,7 @@ extension NCCollectionViewCommon { return } let identifier = self.serverUrl + "_syncMetadata" - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") // Always cancel and clear all tracked URLSessionTask on any exit path defer { @@ -85,7 +85,7 @@ extension NCCollectionViewCommon { // If a readFile for this serverUrl is already in-flight, do nothing if await networking.networkingTasks.isReading(identifier: identifier) { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) return } @@ -151,9 +151,9 @@ extension NCCollectionViewCommon { // If this folder failed, skip it but keep processing others if resultsReadFolder.error == .success { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) } else { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index ad09d5b6f2..2595fb1348 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, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @@ -50,14 +52,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var tabBarSelect: NCCollectionViewCommonSelectTabBar? var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] - var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? + let maxImageGrid: CGFloat = 7 +// var headerMenu: NCSectionFirstHeader? + + var tipViewAccounts: EasyTipView? + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } // DECLARE var layoutKey = "" var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false + var groupByField = "name" var headerRichWorkspaceDisable: Bool = false var emptyImageName: String? @@ -227,6 +234,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ + redirectToPrivacyViewController() + + //set current app version + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String + UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") + } + + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in + guard let self else { return } + + self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in + guard let self else { return } + self.collectionView.reloadData() + } + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in guard let self else { return } @@ -237,6 +263,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard let self else { return } self.collectionView.reloadData() } + + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -340,11 +368,205 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override var canBecomeFirstResponder: Bool { return true } + + @objc func updateIcons() { + collectionView.reloadData() + } + + func isApplicationUpdated() -> 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) + } // MARK: - Transfer Delegate func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, metadatasError: [tableMetadata: NKError]) { + switch status { + // DELETE + case self.global.networkingStatusDelete: + let errorForThisServer = metadatasError.first { entry in + let (key, value) = entry + return key.serverUrl == self.serverUrl && value != .success + }?.value + + let needLoadDataSource = metadatasError.contains { entry in + let (key, value) = entry + return key.serverUrl == self.serverUrl && value == .success + } + + if let error = errorForThisServer { + NCContentPresenter().showError(error: error) + } + + if self.isSearchingMode { + self.networkSearch() + } else if needLoadDataSource { + Task { + await self.reloadDataSource() + } + } else { + Task.detached { + if await self.isRecommendationActived() { + await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) + } + } + } + default: + break + } + } + + func transferChange(status: String, metadata: tableMetadata, error: NKError) { + guard session.account == metadata.account else { return } + + if error != .success { + NCContentPresenter().showError(error: error) + } + + DispatchQueue.main.async { + switch status { + // UPLOADED, UPLOADED LIVEPHOTO + case self.global.networkingStatusUploaded, self.global.networkingStatusUploadedLivePhoto: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + // DOWNLOAD + case self.global.networkingStatusDownloading: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + case self.global.networkingStatusDownloaded: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + case self.global.networkingStatusDownloadCancel: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + // CREATE FOLDER + case self.global.networkingStatusCreateFolder: + if metadata.serverUrl == self.serverUrl, metadata.sessionSelector != self.global.selectorUploadAutoUpload { + self.pushMetadata(metadata) + } + // RENAME + case self.global.networkingStatusRename: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + // FAVORITE + case self.global.networkingStatusFavorite: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self is NCFavorite { + Task { + await self.reloadDataSource() + } + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + default: + break + } + } + } + + func transferReloadData(serverUrl: String?, status: Int?) { + self.debouncer.call { + if self.isSearchingMode { + guard status != self.global.metadataStatusWaitDelete, + status != self.global.metadataStatusWaitRename, + status != self.global.metadataStatusWaitMove, + status != self.global.metadataStatusWaitCopy, + status != self.global.metadataStatusWaitFavorite else { + return + } + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.reloadDataSource() + } + } + } + } + + func transferRequestData(serverUrl: String?) { + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.getServerData() + } + } + } + } + + func transferCopy(metadata: tableMetadata, destination: String, error: NKError) { + if error != .success { + NCContentPresenter().showError(error: error) + } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl == self.serverUrl || destination == self.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + + func transferMove(metadata: tableMetadata, destination: String, error: NKError) { + if error != .success { + NCContentPresenter().showError(error: error) + } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl == self.serverUrl || destination == self.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, @@ -556,6 +778,61 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.navigationItem.titleView = nil self.navigationItem.title = self.titleCurrentFolder } + + func resetPlusButtonAlpha(animated: Bool = true) { } + + func isHiddenPlusButton(_ isHidden: Bool) { } + + // MARK: - Empty + + func emptyDataSetView(_ view: NCEmptyView) { + + self.emptyDataSet?.setOffset(getHeaderHeight()) + if isSearchingMode { + view.emptyImage.image = UIImage(named: "search")?.image(color: .gray, size: UIScreen.main.bounds.width) + if self.dataSourceTask?.state == .running { + view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "") + } else { + view.emptyTitle.text = NSLocalizedString("_search_no_record_found_", comment: "") + } + view.emptyDescription.text = NSLocalizedString("_search_instruction_", comment: "") + } else if self.dataSourceTask?.state == .running { + view.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "") + view.emptyDescription.text = "" + } else { + if serverUrl.isEmpty { + view.emptyImage.image = emptyImage + view.emptyTitle.text = NSLocalizedString(emptyTitle, comment: "") + view.emptyDescription.text = NSLocalizedString(emptyDescription, comment: "") + } else { + view.emptyImage.image = UIImage(named: "folder_nmcloud") + view.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "") + view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") + } + } + + let spinner = UIActivityIndicatorView(style: .medium) + spinner.startAnimating() + + let container = UIView() + container.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(spinner) + + spinner.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor) + ]) + + self.navigationItem.titleView = container + } + + @MainActor + func restoreDefaultTitle() { + self.navigationItem.titleView = nil + self.navigationItem.title = self.titleCurrentFolder + } // MARK: - SEARCH @@ -621,6 +898,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) +// NCDownloadAction.shared.openShare(viewController: self, metadata: metadata, page: .sharing) + TealiumHelper.shared.trackEvent(title: "magentacloud-app.filebrowser.sharing", data: ["": ""]) + appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) } func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { @@ -786,23 +1066,52 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @MainActor - func reloadDataSource() async { - if !isSearchingMode { - Task.detached { - if await self.isRecommendationActived() { + @objc func reloadDataSource() { + + // get auto upload folder + autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() + autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) + // get layout for view + layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + // set GroupField for Grid + if !isSearchingMode && layoutForView?.layout == NCGlobal.shared.layoutGrid { + groupByField = "classFile" + } else { + groupByField = "name" + } + if isSearchingMode { + isDirectoryEncrypted = false + } else { + isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + } + + DispatchQueue.main.async { +// UIView.transition(with: self.collectionView, +// duration: 0.20, +// options: .transitionCrossDissolve, +// animations: { self.collectionView.reloadData() }, +// completion: nil) +// +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + if isRecommendationActived { + Task.detached { await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) } } } - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) - - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + DispatchQueue.main.async { +// UIView.transition(with: self.collectionView, +// duration: 0.20, +// options: .transitionCrossDissolve, +// animations: { self.collectionView.reloadData() }, +// completion: nil) +// +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + self.refreshControl.endRefreshing() + self.collectionView.reloadData() + self.setNavigationRightItems() + } } func getServerData(forced: Bool = false) async { } @@ -940,38 +1249,21 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - // MARK: - Header size - - func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, - heightHeaderRecommendations: CGFloat, - heightHeaderSection: CGFloat) { - var heightHeaderRichWorkspace: CGFloat = 0 - var heightHeaderRecommendations: CGFloat = 0 - var heightHeaderSection: CGFloat = 0 + func pushViewController(viewController: UIViewController) { + if pushed { return } - if showDescription, - !isSearchingMode, - let richWorkspaceText = self.richWorkspaceText, - !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { - heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 - } - - if isRecommendationActived, - !isSearchingMode, - NCPreferences().showRecommendedFiles, - !self.database.getRecommendedFiles(account: self.session.account).isEmpty { - heightHeaderRecommendations = self.heightHeaderRecommendations - heightHeaderSection = self.heightHeaderSection - } + pushed = true + navigationController?.pushViewController(viewController, animated: true) + } - if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { - if section == 0 { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) - } else { - return (0, 0, self.heightHeaderSection) - } - } else { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) + // MARK: - Header size + + func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { + if headerMenuTransferView, + NCNetworking.shared.isOnline, + let results = database.getResultsMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitUpload, global.metadataStatusUploading])), + !results.isEmpty { + return Array(results) } } diff --git a/iOSClient/NCBackgroundLocationUploadManager.swift b/iOSClient/NCBackgroundLocationUploadManager.swift index 3bea5f4204..5fd5caf392 100644 --- a/iOSClient/NCBackgroundLocationUploadManager.swift +++ b/iOSClient/NCBackgroundLocationUploadManager.swift @@ -1,6 +1,10 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2025 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCBackgroundLocationUploadManager.swift +// Nextcloud +// +// Created by Marino Faggiana on 06/06/25. +// Copyright © 2025 Marino Faggiana. All rights reserved. +// import CoreLocation import NextcloudKit @@ -102,23 +106,21 @@ class NCBackgroundLocationUploadManager: NSObject, CLLocationManagerDelegate { } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - // Must work only in background guard isAppInBackground else { return } // Open Realm - guard NCManageDatabase.shared.openRealmBackground() else { - nkLog(tag: self.global.logTagLocation, emoji: .error, message: "Failed to open Realm in Location Manager") - return - } - - let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! - let location = locations.last - nkLog(tag: self.global.logTagLocation, emoji: .start, message: "Triggered by location change: \(location?.coordinate.latitude ?? 0), \(location?.coordinate.longitude ?? 0)") - - Task.detached { - await appDelegate.backgroundSync() + if database.openRealmBackground() { + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + let location = locations.last + nkLog(tag: self.global.logTagLocation, emoji: .start, message: "Triggered by location change: \(location?.coordinate.latitude ?? 0), \(location?.coordinate.longitude ?? 0)") + + Task.detached { + if let tblAccount = await self.database.getActiveTableAccountAsync() { + await appDelegate.backgroundSync(tblAccount: tblAccount) + } + } } } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 51df24b68c..b445f506ab 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -60,13 +60,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) @@ -134,7 +135,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" @@ -217,6 +246,8 @@ final class NCGlobal: Sendable { let selectorSaveAsScan = "saveAsScan" let selectorOpenDetail = "openDetail" let selectorSynchronizationOffline = "synchronizationOffline" + let selectorPrint = "print" + let selectorDeleteFile = "deleteFile" // Metadata : Status // @@ -247,7 +278,6 @@ final class NCGlobal: Sendable { let metadataStatusForScreenAwake = [-1, -2, 1, 2] let metadataStatusHideInView = [1, 2, 3, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] - let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] let metadatasStatusInProgress = [-2, 2] @@ -264,6 +294,8 @@ 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" @@ -271,6 +303,8 @@ final class NCGlobal: Sendable { let notificationCenterServerDidUpdate = "serverDidUpdate" // userInfo: account let notificationCenterNetworkReachability = "networkReachability" + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error + let notificationCenterMenuSearchTextPDF = "menuSearchTextPDF" let notificationCenterMenuGotToPageInPDF = "menuGotToPageInPDF" @@ -285,6 +319,8 @@ final class NCGlobal: Sendable { let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" + let notificationCenterFavoriteStatusChanged = "favoriteStatusChanged" + // Networking Status let networkingStatusCreateFolder = "statusCreateFolder" let networkingStatusDelete = "statusDelete" @@ -299,8 +335,9 @@ final class NCGlobal: Sendable { let networkingStatusUploading = "statusUploading" let networkingStatusUploaded = "statusUploaded" - let networkingStatusReloadAvatar = "statusReloadAvatar" + let networkingStatusReloadAvatar = "statusReloadAvatar" + let notificationCenterUpdateIcons = "updateIcons" // TIP // @@ -383,6 +420,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 // diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index fc10b27b2a..d16f3e9f16 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -196,7 +196,7 @@ class NCNetworkingE2EEUpload: NSObject { metadata.status = NCGlobal.shared.metadataStatusNormal await self.database.addMetadataAsync(metadata) - await self.database.addLocalFilesAsync(metadatas: [metadata]) + await self.database.addLocalFileAsync(metadata: metadata) utility.createImageFileFrom(metadata: metadata) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in diff --git a/iOSClient/Networking/NCConfigServer.swift b/iOSClient/Networking/NCConfigServer.swift index 2214a85b0a..0d466c370c 100644 --- a/iOSClient/Networking/NCConfigServer.swift +++ b/iOSClient/Networking/NCConfigServer.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2022 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCConfigServer.swift +// Nextcloud +// +// Created by Marino Faggiana on 05/12/22. +// Copyright © 2022 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 diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index 9f459e484c..5178cf296e 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+Download.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/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 UIKit import NextcloudKit @@ -329,3 +348,40 @@ extension NCNetworking { } } } + +class NCOperationDownload: ConcurrentOperation, @unchecked Sendable { + var metadata: tableMetadata + var selector: String + + init(metadata: tableMetadata, selector: String) { + self.metadata = tableMetadata.init(value: metadata) + self.selector = selector + } + + override func start() { + guard !isCancelled else { return self.finish() } + + metadata.session = NCNetworking.shared.sessionDownload + metadata.sessionError = "" + metadata.sessionSelector = selector + metadata.sessionTaskIdentifier = 0 + metadata.status = NCGlobal.shared.metadataStatusWaitDownload + +// let metadata = NCManageDatabase.shared.addMetadata(metadata) + +// NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) { +// } completion: { _, _ in +// self.finish() +// } + Task { + await download(withSelector: self.selector) + } + } + + private func download(withSelector selector: String = "") async { + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + self.finish() + } taskHandler: { _ in } + + } +} diff --git a/iOSClient/Networking/NCNetworking+LivePhoto.swift b/iOSClient/Networking/NCNetworking+LivePhoto.swift index e861b76b81..072b3b087f 100644 --- a/iOSClient/Networking/NCNetworking+LivePhoto.swift +++ b/iOSClient/Networking/NCNetworking+LivePhoto.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+LivePhoto.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/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 UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking+Synchronization.swift b/iOSClient/Networking/NCNetworking+Synchronization.swift new file mode 100644 index 0000000000..270c19864b --- /dev/null +++ b/iOSClient/Networking/NCNetworking+Synchronization.swift @@ -0,0 +1,93 @@ +// +// NCNetworking+Synchronization.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/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 UIKit +import NextcloudKit + +extension NCNetworking { + internal func synchronization(account: String, serverUrl: String, userId: String, urlBase: String, metadatasInDownload: [tableMetadata]?) async { + let showHiddenFiles = NCPreferences().getShowHiddenFiles(account: account) + let options = NKRequestOptions(timeout: 300, taskDescription: NCGlobal.shared.taskDescriptionSynchronization, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + nkLog(tag: self.global.logTagSync, emoji: .start, message: "Start read infinite folder: \(serverUrl)") + + let results = await NextcloudKit.shared.readFileOrFolderAsync(serverUrlFileName: serverUrl, depth: "infinity", showHiddenFiles: showHiddenFiles, account: account, options: options) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: serverUrl, + name: "readFileOrFolder") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + + if results.error == .success, let files = results.files { + nkLog(tag: self.global.logTagSync, emoji: .success, message: "Read infinite folder: \(serverUrl)") + + for file in files { + if file.directory { + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + await NCManageDatabase.shared.createDirectory(metadata: metadata) + } else { + if await isFileDifferent(ocId: file.ocId, fileName: file.fileName, etag: file.etag, metadatasInDownload: metadatasInDownload, userId: userId, urlBase: urlBase) { + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + metadata.session = self.sessionDownloadBackground + metadata.sessionSelector = NCGlobal.shared.selectorSynchronizationOffline + metadata.sessionTaskIdentifier = 0 + metadata.sessionError = "" + metadata.status = NCGlobal.shared.metadataStatusWaitDownload + metadata.sessionDate = Date() + + await NCManageDatabase.shared.addMetadataAsync(metadata) + + nkLog(tag: self.global.logTagSync, emoji: .start, message: "File download: \(file.serverUrl)/\(file.fileName)") + } + } + } + } else { + nkLog(tag: self.global.logTagSync, emoji: .error, message: "Read infinite folder: \(serverUrl), error: \(results.error.errorCode)") + } + + nkLog(tag: self.global.logTagSync, emoji: .stop, message: "Stop read infinite folder: \(serverUrl)") + } + + internal func isFileDifferent(ocId: String, + fileName: String, + etag: String, + metadatasInDownload: [tableMetadata]?, + userId: String, + urlBase: String) async -> Bool { + let match = metadatasInDownload?.contains { $0.ocId == ocId } ?? false + if match { + return false + } + + guard let localFile = await NCManageDatabase.shared.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", ocId)) else { + return true + } + let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileName: fileName, userId: userId, urlBase: urlBase) + let size = await self.utilityFileSystem.fileSizeAsync(atPath: fileNamePath) + let isDifferent = (localFile.etag != etag) || size == 0 + + return isDifferent + } +} diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index 2887838214..4ab593f620 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+Task.swift +// Nextcloud +// +// Created by Marino Faggiana on 24/08/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 diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 93ea6b9597..f7cb42f268 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later - import UIKit import NextcloudKit import Alamofire @@ -39,6 +35,22 @@ extension NCNetworking { } taskHandler(task) } progressHandler: { progress in + Task { + guard await self.progressQuantizer.shouldEmit(serverUrlFileName: serverUrlFileName, fraction: progress.fractionCompleted) else { + return + } + + if let metadata { + await NCManageDatabase.shared.setMetadataProgress(ocId: metadata.ocId, progress: progress.fractionCompleted) + await self.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferProgressDidUpdate(progress: Float(progress.fractionCompleted), + totalBytes: progress.totalUnitCount, + totalBytesExpected: progress.completedUnitCount, + fileName: metadata.fileName, + serverUrl: metadata.serverUrl) + } + } + } progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted) } @@ -332,6 +344,7 @@ extension NCNetworking { error: self.global.diagnosticProblemsUploadServerError) } } + await NCManageDatabase.shared.updateBadge() } // MARK: - diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 2d3d19b5e7..ba4b604f31 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+WebDAV.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/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 UIKit import NextcloudKit @@ -538,6 +557,50 @@ extension NCNetworking { } } } + + func renameMetadata(_ metadata: tableMetadata, + fileNameNew: String, + indexPath: IndexPath, + viewController: UIViewController?, + completion: @escaping (_ error: NKError) -> Void) { + + let permission = NCMetadataPermissions.permissionsContainsString(metadata.permissions, permissions: NCMetadataPermissions.permissionCanRename) + if (!metadata.permissions.isEmpty && permission == false) || + (metadata.status != global.metadataStatusNormal && metadata.status != global.metadataStatusWaitRename) { +// return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")) +// let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) +// DispatchQueue.main.async { completion(error) } + completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")) + } + + if metadata.isDirectoryE2EE { +#if !EXTENSION + if isOffline { +// return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } + Task { + let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) + if error != .success { +// NCContentPresenter().showError(error: error) + completion(error) + } + } +#endif + } else { + Task { + await self.transferDispatcher.notifyAllDelegatesAsync { delegate in + let status = self.global.metadataStatusWaitRename + await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: metadata.ocId, status: status) + delegate.transferReloadData(serverUrl: metadata.serverUrl, status: status) + } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "error": NKError(errorCode: 0, errorDescription: ""), "ocId": metadata.ocId, "indexPath": indexPath]) + + } + + completion(NKError(errorCode: 0, errorDescription: "")) + } + } func renameFileOrFolder(metadata: tableMetadata) async -> NKError { let serverUrlFileNameSource = metadata.serverUrlFileName diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 3f6cffe649..55268f9665 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking.swift +// Nextcloud +// +// Created by Marino Faggiana on 23/10/19. +// Copyright © 2019 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 . +// #if !EXTENSION_FILE_PROVIDER_EXTENSION import OpenSSL @@ -151,11 +170,11 @@ actor NetworkingTasks { $0.identifier == identifier && $0.task.state == .running } active.append((identifier, task)) - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) } /// create a Identifier - /// + /// func createIdentifier(account: String? = nil, path: String? = nil, name: String) -> String { if let account, let path { @@ -176,7 +195,7 @@ actor NetworkingTasks { for element in active where element.identifier == identifier { element.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) } active.removeAll { $0.identifier == identifier @@ -189,7 +208,7 @@ actor NetworkingTasks { func cancelAll() { active.forEach { $0.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) } active.removeAll() } @@ -289,6 +308,7 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default) let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) #endif + let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCBrandOptions.shared.httpMaximumConnectionsPerHostInDownload, qualityOfService: .default) // MARK: - init diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index aa94633a71..ff91b2b1b8 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -245,7 +245,6 @@ actor NCNetworkingProcess { await startTimer(interval: minInterval) } } else { - // Remove upload asset await removeUploadedAssetsIfNeeded() // Set Live Photo diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index ebc3aa58ff..ac657cf195 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2018 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCService.swift +// Nextcloud +// +// Created by Marino Faggiana on 14/03/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 @preconcurrency import NextcloudKit diff --git a/iOSClient/Settings/AnalysisDataCollectionSwitch.swift b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift new file mode 100644 index 0000000000..58f2d55d8d --- /dev/null +++ b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift @@ -0,0 +1,35 @@ +// +// AnalysisDataCollectionSwitch.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +class AnalysisDataCollectionSwitch: XLFormBaseCell { + + @IBOutlet weak var cellLabel: UILabel! + @IBOutlet weak var analysisDataCollectionSwitchControl: UISwitch! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + analysisDataCollectionSwitchControl.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged) + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + } + + @objc func switchChanged(mySwitch: UISwitch) { + self.rowDescriptor.value = mySwitch.isOn + } +} + diff --git a/iOSClient/Settings/AnalysisDataCollectionSwitch.xib b/iOSClient/Settings/AnalysisDataCollectionSwitch.xib new file mode 100644 index 0000000000..45530193f6 --- /dev/null +++ b/iOSClient/Settings/AnalysisDataCollectionSwitch.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/InitialPrivacySettingsViewController.swift b/iOSClient/Settings/InitialPrivacySettingsViewController.swift new file mode 100644 index 0000000000..8ae7b2e186 --- /dev/null +++ b/iOSClient/Settings/InitialPrivacySettingsViewController.swift @@ -0,0 +1,171 @@ +// +// InitialPrivacySettingsViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import AppTrackingTransparency +import AdSupport +import UIKit +import XLForm + +class InitialPrivacySettingsViewController: UIViewController { + + @IBOutlet weak var dataPrivacyImage: UIImageView! + @IBOutlet weak var acceptButton: UIButton! + @IBOutlet weak var privacySettingsHelpText: UITextView! + @IBOutlet weak var privacySettingsTitle: UILabel! + @IBOutlet weak var widthPriavacyHelpView: NSLayoutConstraint! + var privacyHelpText = "" + + override func viewDidLoad() { + super.viewDidLoad() + + privacySettingsTitle.text = NSLocalizedString("_privacy_settings_title_", comment: "") + privacyHelpText = NSLocalizedString("_privacy_help_text_after_login_", comment: "") + privacySettingsHelpText.text = privacyHelpText + dataPrivacyImage.image = UIImage(named: "dataPrivacy")!.image(color: NCBrandColor.shared.brand, size: 60) + privacySettingsHelpText.delegate = self + privacySettingsHelpText.textColor = .label + privacySettingsHelpText.hyperLink(originalText: privacyHelpText, + linkTextsAndTypes: [NSLocalizedString("_key_privacy_help_", comment: ""): LinkType.privacyPolicy.rawValue, + NSLocalizedString("_key_reject_help_", comment: ""): LinkType.reject.rawValue, + NSLocalizedString("_key_settings_help_", comment: ""): LinkType.settings.rawValue]) + + acceptButton.backgroundColor = NCBrandColor.shared.brand + acceptButton.tintColor = UIColor.white + acceptButton.layer.cornerRadius = 5 + acceptButton.layer.borderWidth = 1 + acceptButton.layer.borderColor = NCBrandColor.shared.brand.cgColor + acceptButton.setTitle(NSLocalizedString("_accept_button_title_", comment: ""), for: .normal) + privacySettingsHelpText.centerText() + privacySettingsHelpText.font = UIFont(name: privacySettingsHelpText.font!.fontName, size: 16) + self.navigationItem.leftBarButtonItem?.tintColor = NCBrandColor.shared.brand + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.navigationBar.isHidden = true + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.navigationController?.navigationBar.isHidden = false + } + + override func viewDidLayoutSubviews(){ + if UIDevice.current.userInterfaceIdiom == .pad { + widthPriavacyHelpView.constant = UIScreen.main.bounds.width - 100 + } + } + + @IBAction func onAcceptButtonClicked(_ sender: Any) { + requestPermission() + } + + //NEWLY ADDED PERMISSIONS FOR iOS 14 + func requestPermission() { + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(true, forKey: "isAnalysisDataCollectionSwitchOn") + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization { status in + switch status { + case .authorized: + // Tracking authorization dialog was shown + // and we are authorized + print("Authorized") + // Now that we are authorized we can get the IDFA + print(ASIdentifierManager.shared().advertisingIdentifier) + case .denied: + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(false, forKey: "isAnalysisDataCollectionSwitchOn") + print("Denied") + case .notDetermined: + // Tracking authorization dialog has not been shown + print("Not Determined") + case .restricted: + print("Restricted") + @unknown default: + print("Unknown") + } + } + } else { + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(true, forKey: "isAnalysisDataCollectionSwitchOn") + } + self.dismiss(animated: true, completion: nil) + } +} +// MARK: - UITextViewDelegate +extension InitialPrivacySettingsViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + if let linkType = LinkType(rawValue: URL.absoluteString) { + // TODO: handle linktype here with switch or similar. + switch linkType { + case LinkType.privacyPolicy: + //let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let privacyViewController = PrivacyPolicyViewController() + self.navigationController?.pushViewController(privacyViewController, animated: true) + case LinkType.reject: + UserDefaults.standard.set(false, forKey: "isAnalysisDataCollectionSwitchOn") + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + self.dismiss(animated: true, completion: nil) + case LinkType.settings: + let privacySettingsViewController = PrivacySettingsViewController() + UserDefaults.standard.set(true, forKey: "showSettingsButton") + self.navigationController?.pushViewController(privacySettingsViewController, animated: true) + } + print("handle link:: \(linkType)") + } + return false + } +} + +public extension UITextView { + + func hyperLink(originalText: String, linkTextsAndTypes: [String: String]) { + + let style = NSMutableParagraphStyle() + style.alignment = .left + + let attributedOriginalText = NSMutableAttributedString(string: originalText) + + let fullRange = NSRange(location: 0, length: attributedOriginalText.length) + attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.label, range: fullRange) + for linkTextAndType in linkTextsAndTypes { + let linkRange = attributedOriginalText.mutableString.range(of: linkTextAndType.key) + attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: linkTextAndType.value, range: linkRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: NCBrandColor.shared.brand, range: linkRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 10), range: fullRange) + } + + self.linkTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.brand] + self.attributedText = attributedOriginalText + } + + func centerText() { + self.textAlignment = .justified + let fittingSize = CGSize(width: 300, height: CGFloat.greatestFiniteMagnitude) + let size = sizeThatFits(fittingSize) + let topOffset = (bounds.size.height - size.height * zoomScale) / 2 + let positiveTopOffset = max(1, topOffset) + contentOffset.y = -positiveTopOffset + } +} + +enum LinkType: String { + case reject + case privacyPolicy + case settings +} + + diff --git a/iOSClient/Settings/NCSettings.storyboard b/iOSClient/Settings/NCSettings.storyboard new file mode 100644 index 0000000000..19a320515b --- /dev/null +++ b/iOSClient/Settings/NCSettings.storyboard @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/PrivacyPolicyViewController.swift b/iOSClient/Settings/PrivacyPolicyViewController.swift new file mode 100644 index 0000000000..141c444fca --- /dev/null +++ b/iOSClient/Settings/PrivacyPolicyViewController.swift @@ -0,0 +1,62 @@ +// +// PrivacyPolicyViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import UIKit +import WebKit + +class PrivacyPolicyViewController: UIViewController, WKNavigationDelegate, WKUIDelegate { + + var myWebView = WKWebView() + + override func viewDidLoad() { + super.viewDidLoad() + + self.title = NSLocalizedString("_privacy_policy_", comment: "") + + myWebView = WKWebView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)) + myWebView.uiDelegate = self + myWebView.navigationDelegate = self + myWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.view.addSubview(myWebView) + + //1. Load web site into my web view + let myURL = URL(string: "https://static.magentacloud.de/privacy/datenschutzhinweise_app.htm") + let myURLRequest:URLRequest = URLRequest(url: myURL!) + NCActivityIndicator.shared.start() + myWebView.load(myURLRequest) + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + myWebView = WKWebView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)) + } + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + NCActivityIndicator.shared.stop() + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + if let url = navigationAction.request.url, + UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } else { + decisionHandler(.allow) + } + } +} diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift new file mode 100644 index 0000000000..b3993620ba --- /dev/null +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -0,0 +1,141 @@ +// +// PrivacySettingsViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import AppTrackingTransparency +import AdSupport +import XLForm + +class PrivacySettingsViewController: XLFormViewController{ + + @objc public var isShowSettingsButton: Bool = false + + override func viewDidLoad() { + super.viewDidLoad() + self.title = NSLocalizedString("_privacy_settings_title_", comment: "") + + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + + let nib = UINib(nibName: "CustomSectionHeader", bundle: nil) + self.tableView.register(nib, forHeaderFooterViewReuseIdentifier: "customSectionHeader") + isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand + changeTheming() + } + + @objc func changeTheming() { + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorColor = .none + tableView.separatorColor = .clear + tableView.reloadData() + initializeForm() + } + + //MARK: 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("", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_privacy_settings_help_text_", comment: "") + form.addFormSection(section) + + //custom cell + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") + form.addFormSection(section) + + + XLFormViewController.cellClassesForRowDescriptorTypes()["RequiredDataCollectionCustomCellType"] = RequiredDataCollectionSwitch.self + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") + row.cellConfig["requiredDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_required_data_collection_", comment: "") + section.addFormRow(row) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["AnalysisDataCollectionCustomCellType"] = AnalysisDataCollectionSwitch.self + + row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") + row.cellConfig["analysisDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_analysis_data_acqusition_", comment: "") + if(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn")){ + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 1 + }else { + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 0 + } + + section.addFormRow(row) + + XLFormViewController.cellClassesForRowDescriptorTypes()["SaveSettingsButton"] = SaveSettingsCustomButtonCell.self + + section = XLFormSectionDescriptor.formSection(withTitle: "") + form.addFormSection(section) + + row = XLFormRowDescriptor(tag: "SaveSettingsButton", rowType: "SaveSettingsButton", title: "") + row.cellConfig["backgroundColor"] = UIColor.clear + + if(isShowSettingsButton){ + section.addFormRow(row) + } + + + self.form = form + } + + + override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { + super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) + + if formRow.tag == "SaveSettingsButton" { + print("save settings clicked") + //TODO save button state and leave the page + self.navigationController?.popViewController(animated: true) + + } + if formRow.tag == "AnalysisDataCollectionSwitch"{ + if (formRow.value! as AnyObject).boolValue { + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { (status) in + if status == .denied { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } + }) + } + } + UserDefaults.standard.set((formRow.value! as AnyObject).boolValue, forKey: "isAnalysisDataCollectionSwitchOn") + } + + } + +} diff --git a/iOSClient/Settings/RequiredDataCollectionSwitch.swift b/iOSClient/Settings/RequiredDataCollectionSwitch.swift new file mode 100644 index 0000000000..0d5a34732f --- /dev/null +++ b/iOSClient/Settings/RequiredDataCollectionSwitch.swift @@ -0,0 +1,34 @@ +// +// RequiredDataCollectionSwitch.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +class RequiredDataCollectionSwitch: XLFormBaseCell { + + @IBOutlet weak var cellLabel: UILabel! + @IBOutlet weak var requiredDataCollectionSwitchControl: UISwitch! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + //requiredDataCollectionSwitchControl.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged) + + } + + override func configure() { + super.configure() + + requiredDataCollectionSwitchControl.isOn = true + requiredDataCollectionSwitchControl.isEnabled = false + } + + override func update() { + super.update() + } +} diff --git a/iOSClient/Settings/RequiredDataCollectionSwitch.xib b/iOSClient/Settings/RequiredDataCollectionSwitch.xib new file mode 100644 index 0000000000..66156c6e06 --- /dev/null +++ b/iOSClient/Settings/RequiredDataCollectionSwitch.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/SaveSettingsCustomButtonCell.swift b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift new file mode 100644 index 0000000000..1332b1f92a --- /dev/null +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift @@ -0,0 +1,47 @@ +// +// SaveSettingsCustomButtonCell.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + + +class SaveSettingsCustomButtonCell: XLFormButtonCell { + + @IBOutlet weak var saveSettingsButton: UIButton! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + self.selectionStyle = .none + self.separatorInset = UIEdgeInsets(top: 0, left: .greatestFiniteMagnitude, bottom: 0, right: .greatestFiniteMagnitude) + saveSettingsButton.setTitle(NSLocalizedString("_save_settings_", comment: ""), for: .normal) + saveSettingsButton.addTarget(self, action: #selector(saveButtonClicked), for: .touchUpInside) + + } + + override func configure() { + super.configure() + saveSettingsButton.backgroundColor = NCBrandColor.shared.brand + saveSettingsButton.tintColor = UIColor.white + saveSettingsButton.layer.cornerRadius = 5 + saveSettingsButton.layer.borderWidth = 1 + saveSettingsButton.layer.borderColor = NCBrandColor.shared.brand.cgColor + + } + + override func update() { + super.update() + + } + + @objc func saveButtonClicked(sender: UIButton) { + self.rowDescriptor.value = sender + + } + +} diff --git a/iOSClient/Settings/SaveSettingsCustomButtonCell.xib b/iOSClient/Settings/SaveSettingsCustomButtonCell.xib new file mode 100644 index 0000000000..0dd197da31 --- /dev/null +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 46bf73d7a3..9b4708e1f5 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -20,6 +20,7 @@ // along with this program. If not, see . // +"_itunes_" = "iTunes"; "_cancel_" = "Cancel"; "_edit_" = "Edit"; "_tap_to_cancel_" = "Tap to cancel"; @@ -27,66 +28,123 @@ "_cancel_request_" = "Do you want to cancel?"; "_upload_file_" = "Upload file"; "_download_file_" = "Download file"; +"_loading_" = "Loading"; +"_loading_with_points_" = "Loading …"; +"_loading_num_" = "Loading file %i"; +"_loading_autoupload_" = "Auto uploading"; +"_uploading_" = "Uploading"; +"_synchronization_" = "Synchronization"; "_delete_" = "Delete"; +"_delete_file_n_" = "Delete file %i of %i"; "_rename_" = "Rename"; "_rename_file_" = "Rename file"; "_rename_folder_" = "Rename folder"; "_move_" = "Move"; +"_move_file_n_" = "Move file %i of %i"; +"_creating_sharing_" = "Creating share"; +"_updating_sharing_" = "Updating share"; +"_removing_sharing_" = "Removing share"; "_add_" = "Add"; +"_login_" = "Log in"; "_save_" = "Save"; "_warning_" = "Warning"; "_error_" = "Error"; "_no_" = "No"; "_yes_" = "Yes"; "_select_" = "Select"; +"_deselect_" = "Deselect"; "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; "_files_" = "Files"; +//"_files_" = "Files"; +"_home_dir_" = "Home"; +"_file_to_upload_" = "File to upload"; +"_destination_" = "Destination"; "_ok_" = "OK"; +"_beta_version_" = "Beta version"; +"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; +"_passcode_too_short_" = "Passcode too short, at least 4 characters required"; "_selected_" = "Selected"; +"_scan_fingerprint_" = "Scan fingerprint to authenticate"; "_no_active_account_" = "No account found"; "_info_" = "Info"; "_warning_" = "Warning"; +"_email_" = "Email"; +"_save_exit_" = "Do you want to exit without saving?"; "_video_" = "Video"; "_overwrite_" = "Overwrite"; +"_transfers_in_queue_" = "Transfers in progress, please wait …"; +"_too_errors_upload_" = "Too many errors, please verify the problem"; "_create_" = "Create"; "_create_folder_" = "Create folder"; "_create_folder_e2ee_" = "Create encrypted folder"; +"_create_folder_on_" = "Create folder on"; "_close_" = "Close"; +"_postpone_" = "Postpone"; "_remove_" = "Remove"; "_file_not_found_" = "File not found"; "_continue_" = "Continue"; "_continue_editing_" = "Continue editing"; +"_continue_request_" = "Do you want to continue?"; "_auto_upload_folder_" = "Auto upload"; +"_gallery_" = "Gallery"; "_photo_" = "Photo"; "_audio_" = "Audio"; "_unknown_" = "Unknown"; +"_additional_view_options_" = "Additional view options"; +"_next_" = "Next"; "_success_" = "Success"; +"_initialization_" = "Initialization"; +"_experimental_" = "Experimental"; +"_select_dir_media_tab_" = "Select as folder \"Media\""; +"_error_creation_file_" = "Oops! Could not create the file"; +"_save_path_" = "Storage path"; +"_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; +"_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; "_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; "_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; +"_start_" = "Start"; "_force_start_" = "Force the start"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; +"_purchase_" = "Purchase"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; +"_error_parameter_schema_" = "Wrong parameters, impossible to continue"; +"_comments_" = "Comments"; "_sharing_" = "Sharing"; +//"_details_" = "Share"; "_details_" = "Details"; +"_sub_details_" = "Subscription Details"; +"_subscriptions_" = "Subscriptions"; +"_dark_mode_" = "Dark mode"; +"_dark_mode_detect_" = "Detect iOS dark mode"; +"_screen_" = "Screen"; +"_wipe_account_" = "Account is wiped from server"; +"_appconfig_view_title_" = "Account configuration in progress …"; "_no_permission_add_file_" = "You don't have permission to add files."; "_no_permission_delete_file_" = "You don't have permission to delete files."; "_no_permission_modify_file_" = "You don't have permission to modify files."; "_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites."; +"_request_upload_new_ver_" = "The file has been modified, do you want to upload the new version?"; "_add_folder_info_" = "Add folder info"; "_back_" = "Back"; "_search_" = "Search"; "_of_" = "of"; +"_internal_modify_" = "Edit with internal editor"; +"_database_corrupt_" = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure"; "_livephoto_save_" = "Save Live Photo to Photo Album"; "_livephoto_save_error_" = "Error during save of Live Photo."; "_livephoto_no_" = "Disable Live Photo"; @@ -102,9 +160,14 @@ "_copy_" = "Copy"; "_now_" = "Now"; "_wait_" = "Please wait …"; +"_attention_" = "Attention"; "_recent_" = "Recent"; "_view_in_folder_" = "View in folder"; "_leave_share_" = "Leave this share"; +"_premium_" = "Premium"; +"_professional_" = "Professional"; +"_current_" = "Current"; +"_buy_" = "Buy"; "_disabled_" = "Disabled"; "_compact_" = "Compact"; "_normal_" = "Normal"; @@ -134,24 +197,50 @@ "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; "_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; +/* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; + +/* Delete file and put it into the trash */ "_delete_file_" = "Delete file"; "_delete_folder_" = "Delete folder"; +"_delete_photo_" = "Delete photo"; +"_delete_video_" = "Delete video"; +"_automatic_Download_Image_" = "Use images in full resolution"; +"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; -"_set_user_status_" = "Online status"; +//"_set_user_status_" = "Online status"; "_set_user_status_message_" = "Status message"; +"_file_size_" = "Exported file size"; +"_dimension_" = "Dimension"; +"_duration_" = "Duration"; +"_model_" = "Model"; +"_set_user_status_" = "Set user status"; "_open_settings_" = "Open settings"; +"_rename_ext_title_" = "Change file type?"; +"_rename_ext_message_" = "This file may behave differently if you change it from .%@ to %@"; +"_use_" = "Use"; +"_keep_" = "Keep"; +"_account_request_" = "Request account"; "_settings_account_request_" = "Request account at startup"; +"_print_" = "Print"; "_alias_" = "Alias"; -"_alias_placeholder_" = "Write alias"; -"_alias_footer_" = "Give your accounts a descriptive name such as Home, Office, School…"; +"_alias_placeholder_" = "Write the alias"; +"_alias_footer_" = "Give your account names a descriptive name such as Home, Office, School …"; +"_chunk_size_mb_" = "Chunk size in MB"; +"_chunk_footer_title_" = "Chunked file upload (0 is disabled)\nImportant: the chunked upload works only when the app is \"active\"."; "_privacy_legal_" = "Privacy and Legal Policy"; "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; +"_host_insert_" = "Insert the host name, for example:"; +"_certificate_not_found_" = "File %@ in documents directory not found."; +"_copy_failed_" = "Copy failed"; +"_certificate_installed_" = "Certificate installed"; "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; +"_pdf_vertical_" = "PDF vertical display"; +"_pdf_horizontal_" = "PDF horizontal display"; "_single_file_conflict_title_" = "File conflict"; "_multi_file_conflict_title_" = "%@ File conflicts"; "_replace_action_title_" = "Replace"; @@ -169,6 +258,33 @@ "_change_lock_passcode_" = "Change passcode"; "_lock_cannot_disable_mdm_" = "Disabling the passcode lock is not permitted by your configuration profile."; +//TOPasscodeSettingsViewController.m +"_enter_passcode_" = "Enter Passcode"; +"_passcodes_didnt_match_try_again_" = "Passcodes didn't match. Try again."; +"_passcode_options_" = "Passcode Options"; +//"_next_" = "Next"; +"_enter_your_passcode_" = "Enter your passcode"; +"_enter_a_new_passcode_" = "Enter a new passcode"; +"_confirm_new_passcode_" = "Confirm new passcode"; +"_4_digit_numeric_code_" = "4-Digit Numeric Code"; +"_6_digit_numeric_code_" = "6-Digit Numeric Code"; +"_custom_numeric_code_" = "Custom Numeric Code"; +"_custom_alphanumeric_code_" = "Custom Alphanumeric Code"; + +//TOPasscodeSettingsWarningLabel.m +"_1_failed_passcode_attempt" = "1 Failed Passcode Attempt"; +"_failed_passcode_attempts" = "%d Failed Passcode Attempts"; + +/* Background of the file listing view */ +"_use_as_background_" = "Use it as a background"; + +/* Background of the file listing view */ +"_background_" = "Background"; + +"_dark_mode_" = "Dark mode"; +"_default_color_" = "Use the default color"; +"_as_default_color_" = "Use as default color"; + // MARK: User Status /* User status */ @@ -215,50 +331,149 @@ "_hours_" = "Hours"; "_minutes_" = "Minutes"; - +"_network_available_" = "Network available."; "_network_not_available_" = "Network unavailable."; +"_file_too_big_" = "File too large to be encrypted/decrypted"; +"_file_too_big_max_100_" = "File too large (max 100 kb.)"; +"_...loading..._" = "Loading …"; +"_download_plist_" = " "; +"_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; "_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +//"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +"_read_file_error_" = "Could not read the file"; +"_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; "_more_" = "More"; "_notifications_" = "Notifications"; +"_logout_" = "Log out"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; "_quota_using_" = "You are using %@ of %@"; +//"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; +"_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; "_lock_active_" = "Lock: On"; "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; +"_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_face_id_" = "Face ID"; +"_touch_id_" = "Touch ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; +"_your_secure_cloud_storage_" = "Your secure cloud storage"; +"_url_" = "URL"; +"_username_" = "Username"; +"_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload photos"; +//"_settings_autoupload_" = "Auto upload"; +"_app_version_" = "Application version"; +"_app_in_use_" = "Application in use"; +"_contact_by_email_" = "Contact us by email"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; -"_exit_" = "Reset application"; +"_exit_" = "Logout"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_funct_not_enabled_" = "Functionality not enabled"; +"_passcode_activate_" = "Password lock on"; +"_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; +"_proceed_" = "Proceed"; +"_delete_cache_" = "Delete cache"; "_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; +"_want_delete_thumbnails_" = "Do you want to delete all thumbnails too?"; +"_mail_deleted_" = "Email deleted"; +"_mail_saved_" = "Email saved"; +"_mail_sent_" = "Email sent"; +"_mail_failure_" = "Could not send email: %@"; +"_information_req_" = "Information request"; +"_write_in_english_" = "Kindly write to us in English"; +"_credentials_" = "Credentials"; +"_manage_account_" = "Manage account"; +"_change_password_" = "Change password"; "_add_account_" = "Add account"; -"_want_delete_" = "You will delete the following: "; +//"_want_delete_" = "You will delete the following: "; +//"_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +"_want_delete_" = "Do you really want to delete?"; +"_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +"_want_delete_" = "Do you really want to delete?"; "_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; +"_remove_cache_" = "Deleting cache, please wait …"; +"_optimizations_" = "Optimizations"; +"_synchronizations_" = "Synchronized folders"; +"_version_server_" = "Server version"; +"_help_" = "Help"; +"_change_simply_passcode_" = "Change password type"; +"_quota_" = "Quota"; +"_available_" = "Available"; +"_not_available_" = "Not available"; +"_accounts_" = "Accounts"; "_information_" = "Information"; +"_personal_information_" = "Personal info"; +"_user_full_name_" = "Full name"; +"_user_address_" = "Address"; +"_user_phone_" = "Phone number"; +"_user_email_" = "Email"; +"_user_web_" = "Website"; +"_user_twitter_" = "Twitter"; +"_user_job_" = "Job"; +"_user_businesssize_" = "Business size"; +"_user_businesstype_" = "Business type"; +"_user_city_" = "City"; +"_user_country_" = "Country"; +"_user_company_" = "Company"; +"_user_role_" = "Role"; +"_user_zip_" = "Zip"; +"_user_owner_" = "Owner"; +"_user_employee_" = "Employee"; +"_user_contractor_" = "Contractor"; +"_user_editprofile_" = "Edit profile"; "_select_offline_warning_" = "Making multiple files and folders available offline may take a while and use a lot of memory while doing so."; "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; "_custom_permissions_" = "Custom permissions"; "_disable_files_app_" = "Disable Files App integration"; "_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; +"_trial_" = "Trial"; +"_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; +"_autoupload_error_select_folder_" = "Select a valid folder for the \"Auto upload\""; +"_autoupload_background_" = "Auto upload in the background"; "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; -"_autoupload_description_" = "Choose whether new photos or videos will be automatically uploaded to your server."; +//"_autoupload_description_" = "New photos/videos will be automatically uploaded to your MagentaCLOUD"; +"_autoupload_description_" = "Choose whether new photos or videos will be automatically uploaded to your MagentaCLOUD."; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_background_title_" = "Limitations"; +"_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; +"_autoupload_change_location_" = "Change folder"; +"_autoupload_location_now_" = "Folder"; +"_autoupload_location_default_" = "Restore default folder"; +"_autoupload_change_location_footer_" = "Change folder used for \"Automatic upload of camera photos\" (if the option is enabled)"; +"_autoupload_not_select_home_" = "Select a folder"; +"_autoupload_save_album_" = "Copy photo or video into the photo album"; "_autoupload_fullphotos_" = "Upload the whole camera roll"; "_start_autoupload_" = "Turn on auto upload"; "_stop_autoupload_" = "Turn off auto upload"; @@ -268,47 +483,82 @@ "_autoupload_subfolder_granularity_" = "Subfolder Granularity"; "_filenamemask_" = "Change filename mask"; "_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: yy-mm-dd hh-mm-ss plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_autoupload_filenamemask_" = "Change filename mask"; +"_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; +"_help_tutorial_" = "Tutorial"; +"_help_intro_" = "Introduction to Nextcloud"; +"_help_activity_verbose_" = "Detailed Activity feed"; +"_help_activity_mail_" = "Send activity via email"; +"_help_activity_clear_" = "Clear activity"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; "_format_compatibility_footer_" = "\"Most compatible\" will save photos as JPEG, if possible."; +"_terms_" = "Terms of Service"; "_privacy_" = "Privacy"; +"_privacy_policy_" = "Privacy Policy"; "_privacy_footer_" = "This app uses a service for the analysis of a crash. Your personal information is not sent with the report. If you want disable it, please change the setting \"Disable crash reporter\" to ON."; "_crashservice_title_" = "Disable crash reporter"; "_crashservice_alert_" = "This option requires a restart of the app to take effect."; "_upload_mov_livephoto_" = "Live Photo"; "_upload_mov_livephoto_footer_" = "\"Live Photo\" will save the selected photo in \"Live Photo\" format, if possible."; +"_view_capabilities_" = "View the capabilities"; "_capabilities_" = "Capabilities"; +"_no_capabilities_found_" = "Capabilities not found"; "_capabilities_footer_" = "Display the packages used by the app if they are installed and available on the server."; "_diagnostics_" = "Diagnostics"; "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; "_view_log_" = "View log file"; "_clear_log_" = "Clear log file"; +"_level_log_" = "Set Log level (disabled, standard, maximum)"; "_set_log_level_" = "Set Log level"; "_log_file_clear_alert_" = "Log file cleared \n successfully!"; "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; "_server_is_trusted_" = "Do you consider this server trusted?"; "_connection_error_" = "Connection error"; +"_serverstatus_error_" = "Connection to server failure, verify your server address or network status"; +"_add_your_nextcloud_" = "Add your account"; "_login_url_" = "Server address https:// …"; +"_login_bottom_label_" = "Don't have a server yet?\nChoose one of the providers."; +"_error_multidomain_" = "Address not allowed, only the following domains are valid:"; +"_account_already_exists_" = "The account %@ already exists"; +"_traditional_login_" = "Revert to old login method"; +"_web_login_" = "Revert to web login method"; "_login_url_error_" = "URL error, please verify your server URL"; +"_webflow_not_available_" = "Web login not available, use the old login method"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; +"_favorite_" = "Favorite"; +"_unfavorite_" = "Unfavorite"; +"_no_files_uploaded_" = "No files uploaded"; "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; +"_tutorial_local_view_" = "You'll find the unpacked files from your cloud.\n\nConnect to iTunes to share these files."; "_more_" = "More"; "_favorite_no_files_" = "No favorites yet"; +"_pull_down_" = "Pull down to refresh"; +"_no_photo_load_" = "No photo or video"; +"_tutorial_autoupload_view_" = "You can enable auto uploads from \"Settings\""; "_no_date_information_" = "No date information"; "_no_camera_information_" = "No camera information"; "_no_lens_information_" = "No lens information"; "_today_" = "Today"; "_yesterday_" = "Yesterday"; +"_time_" = "Time: %@\n\n%@"; +"_location_not_enabled_" = "Location Services not enabled"; +"_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos is not enabled"; -"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; +//"_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; +"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → MagentaCLOUD → Photos and enable Photo Library Access"; "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; +"_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; +"_activate_autoupload_" = "Enable auto upload"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; "_never_" = "never"; @@ -319,25 +569,88 @@ "_hours_ago_" = "%d hours ago"; "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; +"_over_30_days_" = "over 30 days"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_insert_password_" = "Enter password"; +//"_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; "_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action."; +"_photo_camera_" = "Photos"; "_media_" = "Media"; +"_unzip_in_progress_" = "Extraction in progress on local storage …"; +"_file_unpacked_" = "File unpacked on local storage"; +"_file_saved_local_" = "File saved on local storage."; +"_file_not_present_" = "Error: File not present, please reload."; "_order_by_" = "Sort by"; "_name_" = "Name"; "_date_" = "Date"; "_size_" = "Size"; +"_order_by_name_a_z_" = "Sort by name (from A to Z)"; +"_sorted_by_name_a_z_" = "Sorted by name (from A to Z)"; +"_order_by_name_z_a_" = "Sort by name (from Z to A)"; +"_sorted_by_name_z_a_" = "Sorted by name (from Z to A)"; +"_order_by_date_more_recent_" = "Sort by newest"; +"_sorted_by_date_more_recent_" = "Sorted by newest"; +"_order_by_date_less_recent_" = "Sort by oldest"; +"_sorted_by_date_less_recent_" = "Sorted by oldest"; +"_order_by_size_smallest_" = "Sort by smallest"; +"_sorted_by_size_smallest_" = "Sorted by smallest"; +"_order_by_size_largest_" = "Sort by largest"; +"_sorted_by_size_largest_" = "Sorted by largest"; "_delete_selected_files_" = "Delete files"; +"_move_selected_files_" = "Move files"; +"_move_or_copy_selected_files_" = "Move or copy files"; +"_download_selected_files_" = "Download files"; +"_download_selected_files_folders_" = "Download files and folders"; +"_error_operation_canc_" = "Error: Operation canceled."; +"_only_lock_passcode_" = "Available only with Lock password activated. Activate it in the \"Settings\"."; +"_go_to_app_settings_" = "Go to app settings"; +"_passcode_protection_" = "Password protection"; "_remove_favorites_" = "Remove from favorites"; +"_remove_offline_" = "Remove from offline"; "_add_favorites_" = "Add to favorites"; +"_add_offline_" = "Add to offline"; +"_remove_passcode_" = "Remove password protection"; +"_protect_passcode_" = "Protect with password"; "_share_" = "Share"; +"_reload_" = "Reload"; +"_open_in_" = "Open in …"; +"_open_" = "Open …"; "_remove_local_file_" = "Remove locally"; +"_add_local_" = "Add to local storage"; +"_comm_erro_pull_down_" = "Attention: Communication error with the server. Pull down to refresh."; +"_file_not_downloaded_" = "file not downloaded"; +"_file_not_uploaded_" = "file not uploaded"; "_folders_" = "folders"; "_folder_" = "folder"; "_files_" = "files"; "_file_" = "file"; +"_folder_blocked_" = "Folder blocked"; +"_downloading_progress_" = "Initiating download of files …"; "_no_file_pull_down_" = "Upload a file or pull down to refresh"; "_no_file_no_permission_to_create_" = "You don't have permission to create or upload files in this folder."; +"_browse_images_" = "Browse images"; +"_synchronized_folder_" = "Keep the folder synchronized"; +"_remove_synchronized_folder_" = "Turn off the synchronization"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_file_not_found_reload_" = "File not found, pull down to refresh"; +"_title_section_download_" = "DOWNLOAD"; +"_download_" = "Download"; +"_title_section_upload_" = "UPLOAD"; +"_group_alphabetic_yes_" = "✓ Group alphabetically"; +"_group_alphabetic_no_" = "Group alphabetically"; +"_group_typefile_yes_" = "✓ Group by file type"; +"_group_typefile_no_" = "Group by file type"; +"_group_date_yes_" = "✓ Group by date"; +"_group_date_no_" = "Group by date"; +"_element_" = "element"; +"_elements_" = "elements"; +"_tite_footer_upload_wwan_" = " Wi-Fi network required, %lu %@ to upload"; +"_tite_footer_upload_" = "%lu %@ to upload"; +"_tite_footer_download_wwan_" = " Wi-Fi network required, %lu %@ to download"; +"_tite_footer_download_" = "%lu %@ to download"; "_limited_dimension_" = "Maximum size reached"; "_save_selected_files_" = "Save to photo gallery"; "_file_not_saved_cameraroll_" = "Error: File not saved in photo album"; @@ -346,24 +659,36 @@ "_directory_on_top_" = "Sort folders before files"; "_show_description_" = "Show folder description"; "_show_recommended_files_" = "Show recommendations"; +"_no_description_available_" = "No description available for this folder"; +"_folder_automatic_upload_" = "Folder for \"Auto upload\""; "_search_no_record_found_" = "No result"; "_search_in_progress_" = "Search in progress …"; "_search_instruction_" = "Search for file (minimum 2 characters)"; "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; -"_personal_files_only_" = "Personal files only"; +"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; +"compress" = "COMPRESS"; "directory" = "FOLDERS"; "document" = "DOCUMENTS"; "image" = "IMAGES"; "template" = "TEMPLATES"; +"unknow" = "UNKNOWN"; "video" = "VIDEO"; +"_file_del_only_local_" = "File not present on server"; +"_copy_file_" = "Copy"; "_paste_file_" = "Paste"; +"_open_quicklook_" = "Open with Quick Look"; +"_search_this_folder_" = "Search in this folder"; +"_search_all_folders_" = "Search in all folders"; +"_search_sub_folder_" = "Search here and in subfolders"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; +"_status_in_download_" = "In download"; "_status_downloading_" = "Downloading"; "_status_wait_upload_" = "Waiting to upload"; "_status_wait_create_folder_" = "Waiting to create the folder"; @@ -372,12 +697,19 @@ "_status_wait_favorite_" = "Waiting to change favorite"; "_status_wait_copy_" = "Waiting to copy"; "_status_wait_move_" = "Waiting to move"; +"_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting to upload"; "_select_media_folder_" = "Set Media folder"; "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_by_created_date_" = "Sort by created date"; +"_media_by_upload_date_" = "Sort by upload date"; +"_media_by_modified_date_" = "Sort by modified date"; +"_insert_password_pfd_" = "Secured PDF. Enter password"; +"_password_pdf_error_" = "Wrong password"; +"_error_download_photobrowser_" = "Error: Unable to download photo"; "_good_morning_" = "Good morning"; "_good_day_" = "Good day"; "_good_afternoon_" = "Good afternoon"; @@ -389,78 +721,197 @@ // MARK: Share "_share_link_" = "Share link"; +//"_share_link_" = "Share link"; +"_share_link_" = "Link"; +"_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; "_share_password_" = "Password protected link"; +"_share_expirationdate_" = "Set expiration date for link"; "_date_" = "Date"; +"_share_title_" = "Share"; +"_add_sharee_" = "Add users or groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_find_sharee_title_" = "Search"; +"_find_sharee_" = "Search for user or group …"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_user_is_group_" = "(Group)"; +"_direct_sharee_title_" = "Share"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; +"_share_type_title_" = "Type of share"; +"_share_type_user_" = "User"; +"_share_type_group_" = "Group"; +"_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_expiration_date_placeholder_"= "Expiration date for this share"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_download_limit_alert_invalid_" = "Please enter a valid number for the download limit."; +"_share_remaining_download_" = "Downloads:"; +"_share_read_only_" = "View only"; +"_share_editing_" = "Can edit"; +"_share_file_drop_" = "Filedrop only"; +"_share_hide_download_" = "Prevent download"; +//"_share_note_recipient_" = "YOUR MESSAGE"; +"_share_note_recipient_" = "Note to recipient"; +"_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; -"_share_read_only_" = "View only"; -"_share_editing_" = "Can edit"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_allow_editing_" = "Allow editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File request"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; -"_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; "_share_password_protect_" = "Password protection"; "_share_expiration_date_" = "Set expiration date"; -"_share_note_recipient_" = "Note to recipient"; +"_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; -"_share_can_reshare_" = "Share"; -"_share_can_create_" = "Create"; -"_share_can_change_" = "Edit"; -"_share_can_delete_" = "Delete"; -"_share_can_download_" = "Allow download and sync"; -"_share_unshare_" = "Delete share"; +"_share_can_reshare_" = "Allow resharing"; +"_share_can_create_" = "Allow creating"; +"_share_can_change_" = "Allow editing"; +"_share_can_delete_" = "Allow deleting"; +"_share_can_download_" = "Allow download"; +"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; +"_share_internal_link_des_" = "Only works for users with access to this file/folder"; +"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder"; +"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder"; +"_share_can_download_" = "Allow download"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; "_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; +"_create_new_link_" = "Create new link"; +"_share_send_link_by_mail_" = "Send link by mail"; +"_share_or_" = "or"; +"_share_copy_link_" = "Copy link"; +"_share_shared_with_" = "Shared with"; +"_share_quick_permission_everyone_can_just_upload_" = "Everyone can just upload"; +"_share_quick_permission_everyone_can_edit_" = "Everyone can edit"; +"_share_quick_permission_everyone_can_only_view_" = "Everyone can only view"; +"_share_quick_permission_everyone_can_just_upload_short_" = "Just upload"; +"_share_quick_permission_everyone_can_edit_short_" = "Can edit"; +"_share_quick_permission_everyone_can_only_view_short_" = "Only view"; +"_share_received_shares_text_" = "This file / folder was shared with you by"; +"_share_no_shares_text_" = "You have not yet shared your file/folder. Share to give others access"; +"_share_size_" = "Size: "; +"_share_modified_" = "Modified: "; +"_share_created_" = "Created: "; +"_share_uploaded_" = "Uploaded: "; +"_share_details_" = "Details"; +"_share_via_link_menu_password_label_" = "Password protect (%1$s)"; +"_share_link_empty_exp_date_" = "You must select expiration date."; +"_share_link_empty_note_message_" = "Please enter note."; "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; +//"_share_can_download_" = "Allow download"; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_activity_" = "No activity yet"; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; +"_activity_file_not_present_" = "File no longer present"; "_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; +"_create_synchronization_" = "Create synchronization"; "_offline_" = "Offline"; +"_local_storage_" = "Local storage"; +"_local_storage_no_record_" = "No files yet"; "_upload_photos_videos_" = "Upload photos or videos"; "_upload_file_" = "Upload file"; +"_upload_file_text_" = "Create text file"; "_create_nextcloudtext_document_" = "Create text document"; +"_save_document_picker_" = "Save here"; +"_destination_folder_" = "Destination folder"; "_use_folder_auto_upload_" = "Use the \"Auto upload\" folder as destination"; +"_rename_filename_" = "Rename"; "_filename_" = "Filename"; "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; "_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; "_add_filenametype_" = "Specify type in filename"; +"_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; +"_modify_photo_" = "Modify photo"; "_notifications_" = "Notifications"; +"_no_notification_" = "No notifications yet"; +"_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_untitled_txt_" = "Untitled.txt"; +"_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; "_e2e_settings_start_" = "Start end-to-end encryption"; +"_e2e_settings_not_available_" = "End-to-end encryption not available"; "_e2e_settings_activated_" = "End-to-end encryption activated"; "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; "_e2e_settings_lock_not_active_" = "Lock not active. Go to \"Settings\" and activate it."; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; -"_e2e_passphrase_request_title_" = "Request passphrase"; +"_e2e_passphrase_request_title_" = "Enter passphrase"; //"Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; "_e2e_settings_remove_" = "Remove the encryption locally"; "_e2e_settings_remove_message_" = "Confirm removal of encryption along with the passphrase."; @@ -470,23 +921,79 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred."; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; -"_scanned_images_" = "Scanned images"; +//"_scanned_images_" = "Scanned images"; +"_scanned_images_" = "Scanned documents"; "_scan_document_pdf_page_" = "Page"; "_scan_label_document_zone_" = "Tap or drag images down for document creation"; "_filter_document_" = "Document"; "_filter_original_" = "Original"; +"_filter_bn_" = "Black & White"; +"_filter_grayscale_" = "Grayscale"; "_quality_image_title_" = "Preview image quality"; +"_quality_high_" = "Large file size of high quality"; +"_quality_medium_" = "Average file size of medium quality"; +"_quality_low_" = "Small file size of low quality"; +"_file_type_" = "File type"; +"_pdf_password_" = "PDF Password"; "_file_creation_" = "File creation"; "_delete_all_scanned_images_" = "Delete all scanned images"; "_text_recognition_" = "Text recognition"; "_all_files_" = "All files"; "_personal_files_" = "Personal Files"; +/* The title on the navigation bar of the Scanning screen. */ +"wescan.scanning.title" = "Scanning"; +/* The "Next" button on the right side of the navigation bar on the Edit screen. */ +"wescan.edit.button.next" = "Next"; +/* The title on the navigation bar of the Edit screen. */ +"wescan.edit.title" = "Edit Scan"; +/* The "Done" button on the right side of the navigation bar on the Review screen. */ +"wescan.review.button.done" = "Done"; +/* The title on the navigation bar of the Review screen. */ +"wescan.review.title" = "Review"; + "_trash_view_" = "Deleted files"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?";"_trash_no_trash_" = "No files deleted"; "_empty_trash_" = "Empty trash"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; +"_trash_restore_selected_" = "Restore selected files"; +"_trash_delete_selected_" = "Delete selected files"; +"_recover_" = "Recover"; "_confirm_delete_selected_" = "Are you sure you want to delete the selected items?"; "_manage_file_offline_" = "Manage offline files"; "_set_available_offline_" = "Set as available offline"; @@ -502,7 +1009,62 @@ "_log_in_" = "Log in"; "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; +"_unauthorized_" = "Unauthorized"; +"_bad_username_password_" = "Wrong username or password"; +"_cancelled_by_user" = "Transfer canceled"; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; +"_error_not_permission_" = "You don't have permission to complete the operation"; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; +"_error_not_modified_" = "Resource not modified"; +"_not_connected_internet_" = "Server connection error"; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; +"_not_possible_create_folder_" = "Folder could not be created"; +"_server_down_" = "Could not establish contact with server"; +"_time_out_" = "Timeout, try again"; +"_unknow_response_server_" = "Unexpected response from server"; +"_user_authentication_required_" = "User authentication required"; +"_file_directory_locked_" = "File or directory locked"; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; +"_internal_server_" = "Internal server error"; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; +"_folder_contents_nochanged_" = "The folder contents have not changed"; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; +"_method_not_expected_" = "Unexpected request method"; +"_reauthenticate_user_" = "Access expired, log in again"; +"_server_error_retry_" = "The server is temporarily unavailable"; +"_too_many_files_" = "Too many files would be involved in this operation"; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; +"_user_over_quota_" = "Storage quota is reached"; +"_ssl_connection_error_" = "Connection SSL error, try again"; +"_bad_request_" = "Bad request"; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; +"_error_user_not_available_" = "The user is no longer available"; +"_server_response_error_" = "Server response content error"; +"_no_nextcloud_found_" = "Server not found"; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; +"_error_check_remote_user_" = "Server responded with an error. Please log in again"; +"_request_entity_too_large_" = "The file is too large"; +"_not_possible_download_" = "It is not possible to download the file"; +"_not_possible_upload_" = "It is not possible to upload the file"; +"_error_files_upload_" = "Error uploading files"; +"_method_not_allowed_" = "The requested method is not supported"; "_invalid_url_" = "Invalid server URL"; +"_invalid_literal_" = "Invalid search string"; +"_invalid_date_format_" = "Invalid date format"; +"_invalid_data_format_" = "Invalid data format"; +"_error_decode_xml_" = "Invalid response, error decode XML"; +"_internal_generic_error_" = "internal error"; +"_editor_unknown_" = "Failed to open file: Editor is unknown"; +"_err_file_not_found_" = "File not found, removed"; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; +"_err_permission_photolibrary_" = "Please allow Photos from Settings"; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; "_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; "_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; "_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; @@ -510,11 +1072,20 @@ "_internal_generic_error_" = "internal error."; "_err_permission_microphone_" = "Please allow Microphone usage from Settings."; "_err_permission_photolibrary_" = "Please allow Photos from Settings."; +//"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +//"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +//"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +//"_error_files_upload_" = "Error uploading files."; +//"_internal_generic_error_" = "internal error."; +//"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +//"_err_permission_photolibrary_" = "Please allow Photos from Settings."; "_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; "_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; +"_voice_memo_filename_" = "Voice memo"; +"_voice_memo_title_" = "Upload voice memo"; "Enter Passcode" = "Enter Passcode"; "Enter a new passcode" = "Enter a new passcode"; "Confirm new passcode" = "Confirm new passcode"; @@ -542,10 +1113,15 @@ "_3_months_" = "3 months"; "_1_month_" = "1 month"; "_1_week_" = "1 week"; +"_1_day_" = "1 day"; "_monthly_" = "Monthly"; "_yearly_" = "Yearly"; +"_weekly_" = "Weekly"; "_daily_" = "Daily"; -"_used_space_" = "Used space:"; +"_day_" = "Day"; +"_used_space_" = "Used space"; +"_open_in_onlyoffice_" = "Open in ONLYOFFICE"; +"_open_in_collabora_" = "Open with Collabora Online"; "_login_address_detail_" = "The link to your %@ web interface when you open it in the browser."; "_go_to_page_" = "Go to page"; "_page_" = "Page"; @@ -553,16 +1129,26 @@ "_invalid_page_" = "Invalid Page"; "_the_entered_page_number_does_not_exist_" = "The entered page number does not exist."; "_error_something_wrong_" = "Something went wrong"; +"_resolution_" = "Resolution"; "_try_download_full_resolution_" = "Download full resolution image"; "_full_resolution_image_info_" = "This may reveal more information about the photo."; "_download_audio_" = "Download the audio locally"; "_copied_path_" = "Copied path"; +"_copy_path_" = "Copy path"; +"_certificates_" = "Certificates"; "_privacy_screen_" = "Splash screen when app inactive"; +"_saving_" = "Saving …"; +"_video_not_streamed_" = "The server does not allow video streaming, do you want to download it?"; +"_video_not_streamed_e2ee_" = "The server does not allow video streaming because it is encrypted, do you want to download it?"; +"_scan_" = "Scan"; "_in_" = "in"; "_enter_passphrase_" = "Enter passphrase (12 words)"; "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; +"_waiting_" = "Waiting …"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; +"_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; +"_password_not_present_" = "Please re-insert your credentials."; "_copy_passphrase_" = "Copy passphrase"; "_ok_copy_passphrase_" = "OK and copy passphrase"; "_select_color_" = "Select the color"; @@ -570,10 +1156,17 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; +"_no_data_available_" = "No data available"; +"_widget_available_nc25_" = "Widget only available starting with server version 25"; +"_keep_running_" = "Keep the app running for a better user experience"; +"_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; "_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; - +"_no_items_" = "No items"; +"_check_back_later_" = "Check back later"; +"_exporting_video_" = "Exporting video … Tap to cancel."; "_keep_running_" = "Keep the app running for a better user experience."; +//"_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; @@ -588,16 +1181,35 @@ "_mobile_config_" = "Download the configuration profile"; "_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; +"_preview_" = "Preview"; +"_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; "_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; +//"_modify_image_desc_" = "Tap the image for modify"; +//"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live effect"; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; +"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_reset_application_done_" = "Reset application, done."; "_rename_already_exists_" = "A file with this name already exists."; +"_created_" = "Created"; +"_recipients_" = "Recipients"; +"_are_sure_" = "Are you sure?"; +"_creation_" = "Creation"; +"_modified_" = "Modified"; "_group_folders_" = "Group folders"; "_play_from_files_" = "Play movie from a file"; "_play_from_url_" = "Play movie from URL"; "_valid_video_url_" = "Insert a valid URL"; +"_deletion_progess_" = "Deletion in progress"; +"_copying_progess_" = "Copying in progress"; +"_moving_progess_" = "Moving in progress"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; +"_chunk_create_folder_" = "The file could not be sent, please check the server log"; +"_chunk_files_null_" = "The file for sending could not be created"; +"_chunk_file_null_" = "The file could not be sent"; "_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; @@ -606,6 +1218,9 @@ "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; +"_zoom_" = "Zoom"; +"_zoom_in_" = "Zoom in"; +"_zoom_out_" = "Zoom out"; "_select_photos_" = "Select photos"; "_selected_photo_" = "selected photo"; "_selected_photos_" = "selected photos"; @@ -621,12 +1236,15 @@ "_account_settings_" = "Account settings"; "_users_" = "Users"; "_users_footer_" = "Every time the app is reactivated, the account will be requested."; +"_additional_view_options_" = "Additional view options"; "_while_charging_" = "While charging"; +"_downloading_" = "Downloading"; +"_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; "_error_not_found_" = "The requested resource could not be found"; "_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; "_error_precondition_" = "The server does not meet one of the preconditions of the requester"; -"_downloading_" = "Downloading"; + "_additional_options_" = "Additional options"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; "_disableFilesApp_" = "Files app cannot be used because it is disabled"; @@ -637,12 +1255,32 @@ "_recent_activity_" = "Recent activity"; "_maintenance_mode_" = "The server is currently in maintenance mode, which may take a while."; "_account_disabled_" = "Account disabled"; + +//Video +"_select_trace_" = "Select the trace"; +"_video_processing_" = "Video processing"; +"_video_being_processed_" = "Video being processed …"; +"_downloading_" = "Downloading"; +"_download_error_" = "Download error"; +"_subtitle_" = "Subtitle"; +"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_reuired_conversion_" = "This video takes a long time to convert."; +"_stay_app_foreground_" = "Keep the app in the foreground …"; +"_conversion_available_" = "The conversion is always available on menu"; +"_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; +"_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; +"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing"; +"_subtitle_not_found_" = "Subtitle not found"; +"_disable_" = "Disable"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; "_upload_foreground_msg_" = "Do not close %@ to complete the transfer …"; "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; "_creating_dir_progress_" = "Creating directories in progress … keep the application active."; "_creating_db_photo_progress_" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; @@ -656,7 +1294,7 @@ "_recommended_files_" = "Recommended Files"; "_propagate_layout_" = "Apply the following change to all subfolders"; "_version_mismatch_error_" = "Version change: please open the main app to update the data."; -"_check_auto_upload_" = "Automatic photo upload is currently disabled.\nWith Nextcloud iOS you can automatically back up your photos and keep them safe on your server.\nJust open Settings → Auto Upload, choose the options you prefer, and turn it on with the \"%@\" button."; +"_check_auto_upload_" = "Automatic photo upload is currently disabled.\nWith MagentaCLOUD iOS you can automatically back up your photos and keep them safe on your server.\nJust open Settings → Auto Upload, choose the options you prefer, and turn it on with the \"%@\" button."; "_cancel_all_request_" = "Are you sure you want to cancel all transfers?"; "_cancel_all_" = "Cancel All"; "_dismiss_" = "Dismiss"; @@ -683,6 +1321,7 @@ "_tip_open_mediadetail_" = "Swipe up to show the details"; "_tip_autoupload_button_" = "Configure the options as you prefer (folder, Wi-Fi, new content, subfolders) and tap ‘Turn on auto uploading’. You can stop it at any time, adjust the settings, and enable it again."; +"_tip_autoupload_" = "You can now Auto Upload specific Albums from your Photos. You can enable Auto Upload here"; // MARK: Accessibility @@ -695,15 +1334,42 @@ You can stop it at any time, adjust the settings, and enable it again."; "_on_" = "On"; // a11y: On/Off "_off_" = "Off"; +"_grid_view_" = "Show grid view"; +"_list_view_" = "Show list view"; "_list_" = "List"; "_icons_" = "Icons"; +// MARK: Plan customer +"_leave_plan_title" = "We're sorry to see you go"; +"_leave_plan_description" = "You'll no longer have access to:"; +"_current_plan_" = "Current Plan"; +"_billing_plan_" = "Billing Plan"; +"_keep_plan_" = "Keep Plan"; +"_leave_plan_" = "Leave Plan"; +"_change_plan_" = "Change Plan"; +"_manage_plan_" = "Manage Plan"; +"_purchase_plan_" = "Purchase Plan"; +"_restore_plan_" = "Restore Purchased Plan"; +"_purchase_plan_description_" = "Purchases have been restored"; +"_choose_plan_" = "You should choose a plan in order to purchase it."; +"_already_plan_" = "The selected plan has already been bought."; +"_change_billing_" = "Change Billing"; +"_payment_method_" = "Payment Method"; + +// MARK: Mantis library +"Mantis.Done" = "Done"; +"Mantis.Cancel" = "Cancel"; +"Mantis.Reset" = "Reset"; +"Mantis.Original" = "Original"; +"Mantis.Square" = "Square"; + // MARK: Assistant "_assistant_task_unknown_" = "Unknown"; "_assistant_task_scheduled_" = "Scheduled"; "_assistant_task_in_progress_" = "In Progress"; "_assistant_task_completed_" = "Completed"; "_assistant_task_failed_" = "Failed"; +"_all_" = "All"; "_input_" = "Input"; "_output_" = "Output"; "_task_details_" = "Task details"; @@ -750,6 +1416,55 @@ You can stop it at any time, adjust the settings, and enable it again."; "_auto_upload_all_photos_warning_message_" = "This can take some time to process depending on the amount of photos."; "_item_with_same_name_already_exists_" = "An item with the same name already exists."; +// MARK: Privacy Policy +"_privacy_settings_title_" = "Privacy Settings"; +"_privacy_help_text_after_login_" = "This app uses Cookies and similiar technolgies (tools). By clicking Accept, you accept the processing and also the Transfer of your data to third parties. The data will be used for Analysis, retargeting and to Display personalized Content and Advertising on sites and third-party sites. You can find further informatin, including Information on data processing by third-party Providers, in the settings and in our Privacy Policy.You can reject the use of the Tools or customize them at any time in the Settings."; +"_key_privacy_help_" = "Privacy Policy"; +"_key_reject_help_" = "reject"; +"_key_settings_help_" = "Settings"; +"_accept_button_title_" = "Accept"; +"_privacy_settings_help_text_" = "To optimize your app, we collect anonymous data. For this we use software solutions of different partners. We would like to give you full transparency and decision-making power over the processin and collection of your anonymized usage data. You can also change your settings at any time later in the app settings under data protection. Please note, however, that data collection makes a considerable contribution to the optimization of this app and you prevent this optimization by preventing data transmission."; +"_required_data_collection_" = "Required data collection"; +"_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; +"_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; +"_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; + +// MARK: Collabora +"_prefix_upload_path_" = "MagentaCLOUD/"; +"_please_enter_file_name_" = "Please enter the file name"; + +// MARK: Scan +"_location_" = "Location"; +"_prefix_upload_path_" = "MagentaCLOUD/"; + +"_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; +"_pdf_with_ocr_" = "PDF (OCR)"; +"_text_file_ocr_" = "Textfile (txt)"; +"_save_without_text_recognition_" = "SAVE WITHOUT TEXT RECOGNITION"; +"_pdf_" = "PDF"; +"_jpg_" = "JPG"; +"_png_" = "PNG"; +"_set_password_" = "Set password"; +"_no_password_warn_" = "Please enter a password for the PDF you want to create or disable the function."; +"_saved_info_alert_" = "Saving will take some time, especially if you have selected several pages and file formats."; +"_no_file_type_selection_error_" = "Please select at least one filetype"; +"_no_internet_alert_message_" = "A data connection is not currently allowed."; +"_no_internet_alert_title_" = "Connection error"; +"_auto_upload_help_text_" = "With this option, you upload your photos or videos to the same folder that you selected for \"Automatic upload.\""; +"_item_with_same_name_already_exists_" = "An item with the same name already exists."; + +// MARK: Dashboard +"_shared_" = "Shared"; +"_recieved_" = "Received"; + +// MARK: App Updater +"update_available" = "Update available"; +"update_description" = "MagentaCLOUD version %@ is now available"; +"update" = "Update"; +"not_now" = "Not Now"; + +"_prompt_insert_file_name" = "Please enter filename"; + // MARK: Migration Multi Domains "_preparing_migration_" = "Preparing migration …";