diff --git a/File Provider Extension/FileProviderData.swift b/File Provider Extension/FileProviderData.swift index 172beb53fe..d73e8f6669 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 a3788d3efc..73057bbf53 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,20 @@ AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + 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 */; }; F31165022F9674A1009A1E37 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = F31165012F9674A1009A1E37 /* AppIcon.icon */; }; @@ -1270,6 +1284,18 @@ AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuPlayerTracks.swift; sourceTree = ""; }; BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + 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 = ""; }; @@ -2094,6 +2120,8 @@ children = ( F34BDB3B2F574A58007A222C /* BidiSafeFilenameTests.swift */, AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2611,6 +2639,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 */, @@ -4051,6 +4089,11 @@ F7CEE6002BA9A5C9003EFD89 /* NCTrashGridCell.xib 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 */, @@ -4621,6 +4664,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+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 011ddbf681..67ed7ad2f2 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -235,7 +235,7 @@ extension tableMetadata { if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { return false } - return true + return !e2eEncrypted } var hasPreviewBorder: Bool { @@ -510,6 +510,19 @@ extension NCManageDatabase { } } } + + func deleteMetadataOcIds(_ ocIds: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId IN %@", ocIds) + realm.delete(results) + } + } catch let error as NSError { + nkLog(error: "Could not access database: \(error)") + + } + } func replaceMetadataAsync(ocId: String, metadata: tableMetadata) async { let detached = metadata.detachedCopy() 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.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 7db4928ad4..f694bc9220 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -13,146 +13,130 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, @IBOutlet weak var collectionView: UICollectionView! - internal let database = NCManageDatabase.shared - internal let global = NCGlobal.shared - internal let utility = NCUtility() - internal let utilityFileSystem = NCUtilityFileSystem() - internal let imageCache = NCImageCache.shared - internal var dataSource = NCCollectionViewDataSource() - internal let networking = NCNetworking.shared - internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! - internal var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() - private var isNavigatingMetadata = false - - internal var autoUploadFileName = "" - internal var autoUploadDirectory = "" - internal let refreshControl = UIRefreshControl() - internal var searchController: UISearchController? - internal var backgroundImageView = UIImageView() - internal var serverUrl: String = "" - internal var isEditMode = false - internal var isDirectoryE2EE = false - internal var fileSelect: [String] = [] - internal var metadataFolder: tableMetadata? - internal var richWorkspaceText: String? - internal var sectionFirstHeader: NCSectionFirstHeader? - internal var sectionFirstHeaderEmptyData: NCSectionFirstHeaderEmptyData? - - // Layout - // - internal var layoutForView: NCDBLayoutForView? - internal var layoutForViewLayoutStore: String? - internal var listLayout = NCListLayout() - internal var gridLayout = NCGridLayout() - internal var mediaLayout = NCMediaLayout() - internal var layoutType = NCGlobal.shared.layoutList - - internal var tabBarSelect: NCCollectionViewCommonSelectTabBar? - - internal var attributesZoomIn: UIMenuElement.Attributes = [] - internal var attributesZoomOut: UIMenuElement.Attributes = [] - - internal var tipViewAccounts: EasyTipView? - internal var syncMetadatasTask: Task? - - // Edit Menu - // - internal let dragDropMenuIdentifier = "dragdrop" - internal var editMenuInteraction: UIEditMenuInteraction? - internal var currentMenuObjectId: String? - internal var currentMenuPoint: CGPoint = .zero - - // Search - // - internal var isSearchingMode: Bool = false - internal var networkSearchInProgress: Bool = false - internal var searchOperationHandle = NKOperationHandle() - internal var searchTask: URLSessionTask? - internal var searchResultText: String? - internal var searchResultStore: String? + let database = NCManageDatabase.shared + let global = NCGlobal.shared + let utility = NCUtility() + let utilityFileSystem = NCUtilityFileSystem() + let imageCache = NCImageCache.shared + var dataSource = NCCollectionViewDataSource() + let networking = NCNetworking.shared + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() + + var autoUploadFileName = "" + var autoUploadDirectory = "" + let refreshControl = UIRefreshControl() + var searchController: UISearchController? + var backgroundImageView = UIImageView() + var serverUrl: String = "" + var isEditMode = false + var isDirectoryE2EE = false + var isDirectoryEncrypted = false + var fileSelect: [String] = [] + var metadataFolder: tableMetadata? + var richWorkspaceText: String? + var sectionFirstHeader: NCSectionFirstHeader? + var sectionFirstHeaderEmptyData: NCSectionFirstHeaderEmptyData? + var isSearchingMode: Bool = false + var networkSearchInProgress: Bool = false + var layoutForView: NCDBLayoutForView? + var searchDataSourceTask: URLSessionTask? + var dataSourceTask: URLSessionTask? + var providers: [NKSearchProvider]? + var searchResults: [NKSearchResult]? + var listLayout = NCListLayout() + var gridLayout = NCGridLayout() + var mediaLayout = NCMediaLayout() + var layoutType = NCGlobal.shared.layoutList + var literalSearch: String? + var tabBarSelect: NCCollectionViewCommonSelectTabBar? + var attributesZoomIn: UIMenuElement.Attributes = [] + var attributesZoomOut: UIMenuElement.Attributes = [] + var tipViewAccounts: EasyTipView? + var syncMetadatasTask: Task? // DECLARE - // - internal var layoutKey = "" - internal var titleCurrentFolder = "" - internal var titlePreviusFolder: String? - internal var enableSearchBar: Bool = false - internal var headerRichWorkspaceDisable: Bool = false - - internal var emptyImageName: String? - internal var emptyImageColors: [UIColor]? - internal var emptyTitle: String = "" - - internal var emptyDescription: String = "" - internal var emptyDataPortaitOffset: CGFloat = 0 - internal var emptyDataLandscapeOffset: CGFloat = -20 - - internal var lastScale: CGFloat = 1.0 - internal var currentScale: CGFloat = 1.0 - internal var maxColumns: Int { + var layoutKey = "" + var titleCurrentFolder = "" + var titlePreviusFolder: String? + var enableSearchBar: Bool = false + + let maxImageGrid: CGFloat = 7 + var headerMenu: NCSectionHeaderMenu? + var headerMenuTransferView = false + var headerMenuButtonsView: Bool = true + var headerRichWorkspaceDisable: Bool = false + + var groupByField = "name" + var headerRichWorkspaceDisable: Bool = false + + var emptyImageName: String? + var emptyImageColors: [UIColor]? + var emptyTitle: String = "" + + var emptyDescription: String = "" + var emptyDataPortaitOffset: CGFloat = 0 + var emptyDataLandscapeOffset: CGFloat = -20 + + var lastScale: CGFloat = 1.0 + var currentScale: CGFloat = 1.0 + var maxColumns: Int { let screenWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) let column = Int(screenWidth / 44) return column } - internal var transitionColumns = false - internal var numberOfColumns: Int = 0 - internal var lastNumberOfColumns: Int = 0 + var transitionColumns = false + var numberOfColumns: Int = 0 + var lastNumberOfColumns: Int = 0 - internal let heightHeaderRecommendations: CGFloat = 160 - internal let heightHeaderSection: CGFloat = 30 + let heightHeaderRecommendations: CGFloat = 160 + let heightHeaderSection: CGFloat = 30 - internal var isLayoutPhoto: Bool { + @MainActor + var session: NCSession.Session { + NCSession.shared.getSession(controller: tabBarController) + } + + var isLayoutPhoto: Bool { layoutForView?.layout == global.layoutPhotoRatio || layoutForView?.layout == global.layoutPhotoSquare } - internal var isLayoutGrid: Bool { + var isLayoutGrid: Bool { layoutForView?.layout == global.layoutGrid } - internal var isLayoutList: Bool { + var isLayoutList: Bool { layoutForView?.layout == global.layoutList } - internal var showDescription: Bool { + var showDescription: Bool { !headerRichWorkspaceDisable && NCPreferences().showDescription } - internal var isRecommendationActived: Bool { + var isRecommendationActived: Bool { let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations } - internal var infoLabelsSeparator: String { + var infoLabelsSeparator: String { layoutForView?.layout == global.layoutList ? " - " : "" } @MainActor - internal var session: NCSession.Session { - NCSession.shared.getSession(controller: tabBarController) - } - - @MainActor - internal var controller: NCMainTabBarController? { + var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } - @MainActor - internal var mainNavigationController: NCMainNavigationController? { + var mainNavigationController: NCMainNavigationController? { self.navigationController as? NCMainNavigationController } - @MainActor - internal var sceneIdentifier: String { + var sceneIdentifier: String { (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } - @MainActor - internal var windowScene: UIWindowScene? { - SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) - } - - internal var isNumberOfItemsInAllSectionsNull: Bool { + var isNumberOfItemsInAllSectionsNull: Bool { var totalItems = 0 for section in 0.. Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as? UINavigationController + newViewController?.modalPresentationStyle = .fullScreen + self.present(newViewController!, animated: true, completion: nil) + } + + // 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 presentationControllerDidDismiss( _ presentationController: UIPresentationController) { let viewController = presentationController.presentedViewController - + if viewController is NCViewerRichWorkspaceWebView { closeRichWorkspaceWebView() } } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { _ in + let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { + self.collectionView?.collectionViewLayout.invalidateLayout() + } + animator.startAnimation() + }) + + self.dismissTip() + } + + override var canBecomeFirstResponder: Bool { + return true + } + + @objc func updateIcons() { +// collectionView.reloadData() + Task { + await self.reloadDataSource() + } + } + + 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: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { self.mainNavigationController?.menuPlus?.resetPlusButtonAlpha() +// self.resetPlusButtonAlpha() +// self.refreshControl.endRefreshing() } - @objc func closeRichWorkspaceWebView() { - Task { - await self.reloadDataSource() + @objc func reloadAvatar(_ notification: NSNotification) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.showTipAccounts() } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error.errorCode != global.errorNotModified else { return } + /// Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() } - // MARK: - Layout + @objc func changeTheming(_ notification: NSNotification) { + self.reloadDataSource() + } - func changeLayout(layoutForView: NCDBLayoutForView) { - let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) - let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 + @objc func changeLayout(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, + account == session.account, + serverUrl == self.serverUrl + else { return } + + func changeLayout(withSubFolders: Bool) { + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + Task { + await self.reloadDataSource() + } + return + } + + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout + + collectionView.reloadData() + + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break + } + + self.collectionView.collectionViewLayout.invalidateLayout() - if serverUrl == homeServer || numFoldersLayoutsForView == 1 { Task { - await setLayout(layoutForView: layoutForView) - await self.reloadDataSource() + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } + } + + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + changeLayout(withSubFolders: false) } else { let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in - Task { - await self.setLayout(layoutForView: layoutForView, withSubFolders: true) - await self.reloadDataSource() - } + changeLayout(withSubFolders: true) })) alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in - Task { - await self.setLayout(layoutForView: layoutForView) - await self.reloadDataSource() - } + changeLayout(withSubFolders: false) })) +// if self.layoutForView?.layout == layoutForView.layout { +// self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) +// self.reloadDataSource() +// return +// } +// +// self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) +// layoutForView.layout = layoutForView.layout +// self.layoutType = layoutForView.layout +// +// collectionView.reloadData() - self.present(alertController, animated: true) + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } + + self.collectionView.collectionViewLayout.invalidateLayout() + +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() } internal func setLayout(layoutForView: NCDBLayoutForView, withSubFolders: Bool = false) async { @@ -464,133 +612,400 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, collectionView.collectionViewLayout.invalidateLayout() } - func getNavigationTitle() -> String { - let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) - if let tblAccount, - !tblAccount.alias.isEmpty { - return tblAccount.alias + @objc func reloadDataSource(_ notification: NSNotification) { + if let userInfo = notification.userInfo as? NSDictionary { + if let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } + + if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { + self.dataSource.removeAll() + } } - return NCBrandOptions.shared.brand - } - @MainActor - func startGUIGetServerData() { - self.dataSource.setGetServerData(false) + reloadDataSource() + } - // Don't show spinner on iPad root folder - if UIDevice.current.userInterfaceIdiom == .pad, - (self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session)) || self.serverUrl.isEmpty { - return + @objc func getServerData(_ notification: NSNotification) { + if let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } } - let spinner = UIActivityIndicatorView(style: .medium) - spinner.startAnimating() + getServerData() + } - let container = UIView() - container.translatesAutoresizingMaskIntoConstraints = false - container.addSubview(spinner) + @objc func reloadHeader(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == session.account + else { return } - spinner.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor), - spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor) - ]) + self.collectionView.reloadData() + } - self.navigationItem.titleView = container + @objc func changeStatusFolderE2EE(_ notification: NSNotification) { + reloadDataSource() } - @MainActor - func stopGUIGetServerData() { - self.dataSource.setGetServerData(true) - self.navigationItem.titleView = nil - self.navigationItem.title = self.titleCurrentFolder + @objc func reloadAvatar(_ notification: NSNotification) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.showTipAccounts() + } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error.errorCode != global.errorNotModified else { return } + /// Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() } - // MARK: - SEARCH + @objc func changeTheming(_ notification: NSNotification) { + self.reloadDataSource() + } - func searchController(enabled: Bool) { - guard enableSearchBar else { - return - } - searchController?.searchBar.isUserInteractionEnabled = enabled + @objc func changeLayout(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, + account == session.account, + serverUrl == self.serverUrl + else { return } - if enabled { - searchController?.searchBar.alpha = 1 + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + Task { + await setLayout(layoutForView: layoutForView) + await self.reloadDataSource() + } } else { - searchController?.searchBar.alpha = 0.3 + let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in + Task { + await self.setLayout(layoutForView: layoutForView, withSubFolders: true) + await self.reloadDataSource() + } + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in + Task { + await self.setLayout(layoutForView: layoutForView) + await self.reloadDataSource() + } + })) +// if self.layoutForView?.layout == layoutForView.layout { +// self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) +// self.reloadDataSource() +// return +// } +// +// self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) +// layoutForView.layout = layoutForView.layout +// self.layoutType = layoutForView.layout +// +// collectionView.reloadData() + + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } - } - func updateSearchResults(for searchController: UISearchController) { - searchResultText = searchController.searchBar.text + self.collectionView.collectionViewLayout.invalidateLayout() + +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() } - func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { - // TIP - dismissTip() + internal func setLayout(layoutForView: NCDBLayoutForView, withSubFolders: Bool = false) async { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout - // (+) - self.mainNavigationController?.menuPlus?.hiddenPlusButton(true) + collectionView.reloadData() - if !isSearchingMode { - self.isSearchingMode = true - self.dataSource.removeAll() - self.collectionView.reloadData() + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } - } - func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { - if isSearchingMode, - searchResultText?.count ?? 0 >= 2 { - Task { - await self.search() - } - } + collectionView.collectionViewLayout.invalidateLayout() } - func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - // (+) - self.mainNavigationController?.menuPlus?.hiddenPlusButton(false) + @objc func reloadDataSource(_ notification: NSNotification) { + if let userInfo = notification.userInfo as? NSDictionary { + if let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } - self.isSearchingMode = false - self.networkSearchInProgress = false - self.searchResultText = nil - self.searchResultStore = nil + if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { + self.dataSource.removeAll() + } + } - Task { - await searchOperationHandle.cancel() - await reloadDataSource() + reloadDataSource() + } - // Restore Layout - if let layoutForViewLayoutStore { - let layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - layoutForView.layout = layoutForViewLayoutStore - await setLayout(layoutForView: layoutForView) + @objc func getServerData(_ notification: NSNotification) { + if let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return } - layoutForViewLayoutStore = nil - - // update Option menu - await mainNavigationController?.updateMenuOption() } + + getServerData() + } + + @objc func reloadHeader(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == session.account + else { return } + + self.collectionView.reloadData() + } + + @objc func changeStatusFolderE2EE(_ notification: NSNotification) { + reloadDataSource() + } + + @objc func closeRichWorkspaceWebView() { + Task { + await self.reloadDataSource() + } + } + + // MARK: - Layout + + func changeLayout(layoutForView: NCDBLayoutForView) { + let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) + let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 + + func changeLayout(withSubFolders: Bool) { + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + Task { + await self.reloadDataSource() + } + return + } + + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout + + collectionView.reloadData() + + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break + } + + self.collectionView.collectionViewLayout.invalidateLayout() + + Task { + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } + } + + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + changeLayout(withSubFolders: false) + } else { + let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: true) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: false) + })) + + self.present(alertController, animated: true) + } + } + + func getNavigationTitle() -> String { + let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) + if let tblAccount, + !tblAccount.alias.isEmpty { + return tblAccount.alias + } + return NCBrandOptions.shared.brand } @MainActor - func setSearchBarLoading(_ loading: Bool) { - guard let textField = searchController?.searchBar.searchTextField else { + func startGUIGetServerData() { + self.dataSource.setGetServerData(false) + + // Don't show spinner on iPad root folder + if UIDevice.current.userInterfaceIdiom == .pad, + (self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session)) || self.serverUrl.isEmpty { return } - if loading { - let spinner = UIActivityIndicatorView(style: .medium) - spinner.startAnimating() - textField.rightView = spinner - textField.rightViewMode = .always + + 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 stopGUIGetServerData() { + self.dataSource.setGetServerData(true) + self.navigationItem.titleView = nil + self.navigationItem.title = self.titleCurrentFolder + } + + // MARK: - SEARCH + + func searchController(enabled: Bool) { + guard enableSearchBar else { return } + searchController?.searchBar.isUserInteractionEnabled = enabled + if enabled { + searchController?.searchBar.alpha = 1 } else { - textField.rightView = nil + searchController?.searchBar.alpha = 0.3 + } + } + + func updateSearchResults(for searchController: UISearchController) { + self.literalSearch = searchController.searchBar.text + } + + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + isSearchingMode = true + self.providers?.removeAll() + self.dataSource.removeAll() + Task { + await self.reloadDataSource() } + // TIP + dismissTip() + // + mainNavigationController?.hiddenPlusButton(true) + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + if isSearchingMode && self.literalSearch?.count ?? 0 >= 2 { + networkSearch() + } + } + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + self.networking.cancelUnifiedSearchFiles() + + self.isSearchingMode = false + self.networkSearchInProgress = false + self.literalSearch = "" + self.providers?.removeAll() + self.dataSource.removeAll() + Task { + await self.reloadDataSource() + } + // + mainNavigationController?.hiddenPlusButton(false) } // MARK: - TAP EVENT + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + TealiumHelper.shared.trackEvent(title: "magentacloud-app.filebrowser.sharing", data: ["": ""]) + appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) + + NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) + } + + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + toggleMenu(metadata: metadata, image: image, sender: sender) + } + +// func tapRichWorkspace(_ sender: Any) { +// if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { +// if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { +// viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" +// viewerRichWorkspace.serverUrl = serverUrl +// viewerRichWorkspace.delegate = self +// +// navigationController.modalPresentationStyle = .fullScreen +// self.present(navigationController, animated: true, completion: nil) +// } +// } +// } + +// func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { +// toggleMenu(metadata: metadata, image: image, sender: sender) +// } + +// func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { +// unifiedSearchMore(metadataForSection: metadataForSection) +// } + +// func tapRecommendations(with metadata: tableMetadata) { +// didSelectMetadata(metadata, withOcIds: false) +// } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -603,6 +1018,116 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, }) } + func openMenuItems(with objectId: String?, gestureRecognizer: UILongPressGestureRecognizer) { + if gestureRecognizer.state != .began { return } + + var listMenuItems: [UIMenuItem] = [] + let touchPoint = gestureRecognizer.location(in: collectionView) + + becomeFirstResponder() + + if !serverUrl.isEmpty { + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) + } + + if !listMenuItems.isEmpty { + UIMenuController.shared.menuItems = listMenuItems + UIMenuController.shared.showMenu(from: collectionView, rect: CGRect(x: touchPoint.x, y: touchPoint.y, width: 0, height: 0)) + } + } + + // MARK: - Menu Item + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if #selector(pasteFilesMenu(_:)) == action { + if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { + return true + } + } else if #selector(copyMenuFile(_:)) == action { + return true + } else if #selector(moveMenuFile(_:)) == action { + return true + } + + return false + } + + @objc func pasteFilesMenu(_ sender: Any?) { + Task {@MainActor in + guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(account: session.account) else { + return + } + let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + let token = showHudBanner( + scene: scene, + title: NSLocalizedString("_upload_in_progress_", comment: "")) + + for (index, items) in UIPasteboard.general.items.enumerated() { + for item in items { + let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) + let results = NKFilePropertyResolver().resolve(inUTI: item.key, capabilities: capabilities) + guard let data = UIPasteboard.general.data(forPasteboardType: item.key, + inItemSet: IndexSet([index]))?.first + else { + continue + } + let fileName = results.name + "_" + NCPreferences().incrementalNumber + "." + results.ext + let serverUrlFileName = utilityFileSystem.createServerUrl(serverUrl: serverUrl, fileName: fileName) + let ocIdUpload = UUID().uuidString + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId( + ocIdUpload, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase + ) + do { + try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) + } catch { + continue + } + + let resultsUpload = await NCNetworking.shared.uploadFile(account: session.account, + fileNameLocalPath: fileNameLocalPath, + serverUrlFileName: serverUrlFileName) { _ in + } progressHandler: { _, _, fractionCompleted in + Task {@MainActor in + LucidBanner.shared.update( + payload: LucidBannerPayload.Update(progress: fractionCompleted), + for: token + ) + } + } + + if resultsUpload.error == .success, + let etag = resultsUpload.etag, + let ocId = resultsUpload.ocId { + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId( + ocId, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + self.utilityFileSystem.moveFile(atPath: fileNameLocalPath, toPath: toPath) + NCManageDatabase.shared.addLocalFile( + account: session.account, + etag: etag, + ocId: ocId, + fileName: fileName) + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadDataSource(serverUrl: self.serverUrl, requestData: true, status: nil) + } + } + } else { + Task { + await showErrorBanner(scene: scene, text: resultsUpload.error.errorDescription) + } + } + } + } + LucidBanner.shared.dismiss() + } + } + // MARK: - DataSource @MainActor @@ -619,78 +1144,144 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, delegate.transferReloadData(serverUrl: self.serverUrl) } - await mainNavigationController?.updateMenuOption() + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } func getServerData(forced: Bool = false) async { } - // MARK: - Push metadata - - /// Pushes or reuses the folder view controller associated with the provided metadata. - /// - /// - Parameter metadata: The metadata representing the selected folder. - @MainActor - func pushMetadata(_ metadata: tableMetadata) async { - guard !isNavigatingMetadata, - let navigationController = self.navigationController, - let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { + @objc func networkSearch() { + guard !networkSearchInProgress else { return } - - isNavigatingMetadata = true - defer { - isNavigatingMetadata = false + guard !session.account.isEmpty, + let literalSearch = literalSearch, + !literalSearch.isEmpty else { + return } + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - let serverUrlPush = utilityFileSystem.createServerUrl( - serverUrl: metadata.serverUrl, - fileName: metadata.fileName - ) - - // Update the last opening date without blocking the main flow. - Task.detached(priority: .utility) { [database] in - await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) + self.networkSearchInProgress = true + self.dataSource.removeAll() + Task { + await self.reloadDataSource() } - guard navigationController.transitionCoordinator == nil else { - return + if capabilities.serverVersionMajor >= global.nextcloudVersion20 { + self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } + } providers: { account, searchProviders in + self.providers = searchProviders + self.searchResults = [] + self.dataSource = NCCollectionViewDataSource(metadatas: [], + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: account) + } update: { _, _, searchResult, metadatas in + guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } + self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) + } completion: { _, _ in + Task { + await self.reloadDataSource() + } + self.networkSearchInProgress = false + } + } else { + self.networking.searchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } + } completion: { metadatasSearch, error in + Task { + guard let metadatasSearch, + error == .success, + self.isSearchingMode + else { + self.networkSearchInProgress = false + await self.reloadDataSource() + return + } + let ocId = metadatasSearch.map { $0.ocId } + let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), + withLayout: self.layoutForView, + withAccount: self.session.account) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: self.session.account) + self.networkSearchInProgress = false + await self.reloadDataSource() + } + } } + } - if let existingEntry = navigationCollectionViewCommon.first(where: { - $0.navigationController === navigationController && $0.serverUrl == serverUrlPush - }) { - let viewController = existingEntry.viewController + func unifiedSearchMore(metadataForSection: NCMetadataForSection?) { + guard let metadataForSection = metadataForSection, let lastSearchResult = metadataForSection.lastSearchResult, let cursor = lastSearchResult.cursor, let term = literalSearch else { return } - if navigationController.topViewController === viewController { - return + metadataForSection.unifiedSearchInProgress = true + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadData(serverUrl: nil) } + } - if navigationController.viewControllers.contains(where: { $0 === viewController }) { - navigationController.popToViewController(viewController, animated: true) - return + self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } + } completion: { _, searchResult, metadatas, error in + if error != .success { + Task { + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } } - navigationController.pushViewController(viewController, animated: true) - return + metadataForSection.unifiedSearchInProgress = false + guard let searchResult = searchResult, let metadatas = metadatas else { return } + self.dataSource.appendMetadatasToSection(metadatas, metadataForSection: metadataForSection, lastSearchResult: searchResult) + + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadData(serverUrl: nil) + } + } } + } + + // MARK: - Push metadata - guard let viewController = UIStoryboard(name: "NCFiles", bundle: nil).instantiateInitialViewController() as? NCFiles else { + func pushMetadata(_ metadata: tableMetadata) { + guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { return } + let serverUrlPush = utilityFileSystem.createServerUrl(serverUrl: metadata.serverUrl, fileName: metadata.fileName) + + // Set Last Opening Date + Task { + await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) + } - viewController.serverUrl = serverUrlPush - viewController.titlePreviusFolder = navigationItem.title - viewController.titleCurrentFolder = metadata.fileNameView + if let viewController = navigationCollectionViewCommon.first(where: { $0.navigationController == self.navigationController && $0.serverUrl == serverUrlPush})?.viewController, viewController.isViewLoaded { + navigationController?.pushViewController(viewController, animated: true) + } else { + if let viewController: NCFiles = UIStoryboard(name: "NCFiles", bundle: nil).instantiateInitialViewController() as? NCFiles { + viewController.serverUrl = serverUrlPush + viewController.titlePreviusFolder = navigationItem.title + viewController.titleCurrentFolder = metadata.fileNameView - navigationCollectionViewCommon.append( - NavigationCollectionViewCommon( - serverUrl: serverUrlPush, - navigationController: navigationController, - viewController: viewController - ) - ) + navigationCollectionViewCommon.append(NavigationCollectionViewCommon(serverUrl: serverUrlPush, navigationController: self.navigationController, viewController: viewController)) - navigationController.pushViewController(viewController, animated: true) + navigationController?.pushViewController(viewController, animated: true) + } + } } // MARK: - Header size @@ -766,8 +1357,119 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, if section == sections - 1 { return CGSize(width: collectionView.frame.width, height: height) } else { - return CGSize(width: collectionView.frame.width, height: 0) + size.height += 1 } + + if isSearchingMode && isPaginated && metadatasCount > 0 { + size.height += 30 + } + return size + } + + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } +} + +extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { + func tapRichWorkspace(_ sender: Any) { + if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { + if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { + viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" + viewerRichWorkspace.serverUrl = serverUrl + viewerRichWorkspace.delegate = self + + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true, completion: nil) + } + } + } + + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } +} + +extension NCCollectionViewCommon: NCSectionFooterDelegate { + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { + unifiedSearchMore(metadataForSection: metadataForSection) + } +} + +extension NCCollectionViewCommon: NCTransferDelegate { + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func transferReloadData(serverUrl: String?) { + Task { + await self.debouncerReloadData.call({ + self.collectionView.reloadData() + }, immediate: true) + } + } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) { + Task { + if error != .success, + error.errorCode != global.errorResourceNotFound { + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } + guard session.account == account else { + return + } + + if status == self.global.networkingStatusCreateFolder { + if serverUrl == self.serverUrl, + selector != self.global.selectorUploadAutoUpload, + let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { + self.pushMetadata(metadata) + } + return + } + + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + } else if self.serverUrl == serverUrl || destination == self.serverUrl || self.serverUrl.isEmpty { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() + } + } + } + } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { + Task { + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + return + } + + if requestData && (self.serverUrl == serverUrl || serverUrl == nil) { + await self.debouncerGetServerData.call { + await self.getServerData() + } + return + } + + if self.serverUrl == serverUrl || serverUrl == nil { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() + } + } + } + + if isSearchingMode && isPaginated && metadatasCount > 0 { + size.height += 30 + } + return size } func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } @@ -885,4 +1587,105 @@ extension NCCollectionViewCommon: NCTransferDelegate { } } } + + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } +} + +extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { + func tapRichWorkspace(_ sender: Any) { + if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { + if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { + viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" + viewerRichWorkspace.serverUrl = serverUrl + viewerRichWorkspace.delegate = self + + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true, completion: nil) + } + } + } + + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } +} + +extension NCCollectionViewCommon: NCSectionFooterDelegate { + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { + unifiedSearchMore(metadataForSection: metadataForSection) + } +} + +extension NCCollectionViewCommon: NCTransferDelegate { + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func transferReloadData(serverUrl: String?) { + Task { + await self.debouncerReloadData.call({ + self.collectionView.reloadData() + }, immediate: true) + } + } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) { + Task { + if error != .success, + error.errorCode != global.errorResourceNotFound { + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } + guard session.account == account else { + return + } + + if status == self.global.networkingStatusCreateFolder { + if serverUrl == self.serverUrl, + selector != self.global.selectorUploadAutoUpload, + let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { + self.pushMetadata(metadata) + } + return + } + + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + } else if self.serverUrl == serverUrl || destination == self.serverUrl || self.serverUrl.isEmpty { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() + } + } + } + } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { + Task { + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + return + } + + if requestData && (self.serverUrl == serverUrl || serverUrl == nil) { + await self.debouncerGetServerData.call { + await self.getServerData() + } + return + } + + if self.serverUrl == serverUrl || serverUrl == nil { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() + } + } + } + } } diff --git a/iOSClient/NCBackgroundLocationUploadManager.swift b/iOSClient/NCBackgroundLocationUploadManager.swift index f010b147d3..154fda662d 100644 --- a/iOSClient/NCBackgroundLocationUploadManager.swift +++ b/iOSClient/NCBackgroundLocationUploadManager.swift @@ -113,6 +113,7 @@ class NCBackgroundLocationUploadManager: NSObject, CLLocationManagerDelegate { 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)") diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 81f58bb5f2..2fcf152ceb 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -43,6 +43,7 @@ final class NCGlobal: Sendable { // Intro selector // let introLogin: Int = 0 + let introSignup: Int = 1 let introSignUpWithProvider: Int = 1 // Avatar @@ -103,6 +104,7 @@ final class NCGlobal: Sendable { let layoutViewOffline = "LayoutOffline" let layoutViewFavorite = "LayoutFavorite" let layoutViewFiles = "LayoutFiles" + let layoutViewTransfers = "LayoutTransfers" let layoutViewRecent = "LayoutRecent" let layoutViewShares = "LayoutShares" let layoutViewShareExtension = "LayoutShareExtension" @@ -113,7 +115,34 @@ 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" @@ -198,7 +227,9 @@ final class NCGlobal: Sendable { let selectorSaveAsScan = "saveAsScan" let selectorOpenDetail = "openDetail" let selectorSynchronizationOffline = "synchronizationOffline" - + let selectorPrint = "print" + let selectorDeleteFile = "deleteFile" + // Metadata : Status // // 0 normal @@ -231,8 +262,7 @@ final class NCGlobal: Sendable { let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] - let metadatasStatusInWaitingDownloadUpload = [-1, 1] - let metadatasStatusDownloadingUploading = [-2, 2] + let metadatasStatusInProgress = [-2, 2] // Auto upload subfolder granularity // @@ -252,6 +282,16 @@ final class NCGlobal: Sendable { let notificationCenterCheckUserDelaultErrorDone = "checkUserDelaultErrorDone" // userInfo: account, controller let notificationCenterServerDidUpdate = "serverDidUpdate" // userInfo: account let notificationCenterNetworkReachability = "networkReachability" + let notificationCenterDidCreateShareLink = "didCreateShareLink" + + let notificationCenterDeleteFile = "deleteFile" // userInfo: [ocId], error + let notificationCenterCopyMoveFile = "copyMoveFile" // userInfo: [ocId] serverUrl, account, dragdrop, type (copy, move) + let notificationCenterMoveFile = "moveFile" // userInfo: [ocId], [indexPath], error + let notificationCenterCopyFile = "copyFile" // userInfo: [ocId], [indexPath], error + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error + let notificationCenterFavoriteFile = "favoriteFile" // userInfo: ocId, serverUrl + let notificationCenterFileExists = "fileExists" // userInfo: ocId, fileExists + let notificationCenterReloadDataSource = "reloadDataSource" // userInfo: serverUrl?, clearDataSource let notificationCenterMenuSearchTextPDF = "menuSearchTextPDF" let notificationCenterMenuGotToPageInPDF = "menuGotToPageInPDF" @@ -259,6 +299,7 @@ final class NCGlobal: Sendable { let notificationCenterOpenMediaDetail = "openMediaDetail" // userInfo: ocId let notificationCenterDismissScanDocument = "dismissScanDocument" + let notificationCenterDismissUploadAssets = "dismissUploadAssets" let notificationCenterEnableSwipeGesture = "enableSwipeGesture" let notificationCenterDisableSwipeGesture = "disableSwipeGesture" @@ -269,6 +310,7 @@ final class NCGlobal: Sendable { let notificationCenterUserInteractionMonitor = "serInteractionMonitor" let notificationCenterNetworkingProcess = "networkingProcess" + let notificationCenterFavoriteStatusChanged = "favoriteStatusChanged" // Networking Status let networkingStatusCreateFolder = "statusCreateFolder" @@ -285,6 +327,7 @@ final class NCGlobal: Sendable { let networkingStatusUploaded = "statusUploaded" let networkingStatusReloadAvatar = "statusReloadAvatar" + let notificationCenterUpdateIcons = "updateIcons" // TIP // @@ -367,7 +410,21 @@ 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 // let logTagTask = "BGT" @@ -386,6 +443,10 @@ final class NCGlobal: Sendable { // let udMigrationMultiDomains = "migrationMultiDomains" let udLastVersion = "lastVersion" + + // Album + // + let selectedTabIndexAlbum: Int = 3 } /** diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index f43a4fbcd5..5541fb7326 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -218,7 +218,7 @@ class NCNetworkingE2EEUpload: NSObject { 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/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+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 3bd13a4c56..9467e9c0b7 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -298,7 +298,11 @@ extension NCNetworking { #if !EXTENSION await NCNetworking.shared.setLivePhoto(account: metadata.account) #endif - } + } else { +#if !EXTENSION + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__UPLOAD_FILE ,metadata: metadata) +#endif + } await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferChange(status: self.global.networkingStatusUploaded, diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 9671a57355..37629dadd1 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -435,7 +435,7 @@ extension NCNetworking { return results.error } - + // MARK: - Rename func setStatusWaitRename(_ metadata: tableMetadata, fileNameNew: String, windowScene: UIWindowScene?) async -> NKError { @@ -499,6 +499,51 @@ extension NCNetworking { return results.error } + 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 { + let ocId = metadata.ocId + let serverUrl = metadata.serverUrl + await self.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: ocId, status: self.global.metadataStatusWaitRename) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitRename) + } +// 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: "")) + } + } + // MARK: - Move func setStatusWaitMove(_ metadata: tableMetadata, destination: String, overwrite: Bool) async -> NKError { @@ -639,8 +684,15 @@ extension NCNetworking { let serverUrl = metadata.serverUrl let favorite = metadata.favorite await self.transferDispatcher.notifyAllDelegatesAsync { delegate in + +#if !EXTENSION + if !metadata.favorite, !metadata.contentType.contains("directory") { + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__ADD_FAVORITE ,metadata: metadata) + } +#endif await NCManageDatabase.shared.setMetadataFavoriteAsync(ocId: ocId, favorite: !favorite, saveOldFavorite: favorite.description, status: self.global.metadataStatusWaitFavorite) delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitFavorite) + } return .success @@ -811,3 +863,4 @@ class NCOperationDownloadAvatar: ConcurrentOperation, @unchecked Sendable { } } } + diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 516b0e64e1..c0684756d4 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -92,6 +92,7 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { let downloadThumbnailTrashQueue = Queuer(name: "downloadThumbnailTrashQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default) let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) + let fileExistsQueue = Queuer(name: "fileExistsQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) #endif // MARK: - init 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..9e1bd0ed64 --- /dev/null +++ b/iOSClient/Settings/PrivacyPolicyViewController.swift @@ -0,0 +1,66 @@ +// +// 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) + + // Re-evaluate in-app messages after viewDidAppear + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "viewDidAppear") + + 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..782f3e76bb --- /dev/null +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -0,0 +1,146 @@ +// +// 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..b314f62bab --- /dev/null +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift @@ -0,0 +1,46 @@ +// +// 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 822dd4e1b1..7d20929728 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,60 +28,118 @@ "_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_does_not_exist_" = "The account %@ does not exist. Please log in again."; +//"_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_" = "Details"; +"_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"; @@ -92,6 +151,8 @@ "_edit_tags_" = "Manage tags"; "_create_tag_error_" = "New tag could not be created. Please try again."; "_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"; @@ -107,9 +168,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"; @@ -139,24 +205,52 @@ "_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 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"; @@ -174,6 +268,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" = "%ld 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 */ @@ -203,6 +324,7 @@ "_status_message_" = "Status message"; "_status_message_placehorder_" = "What is your status?"; "_online_status_" = "Online status"; +"_clear_status_message_" = "Clear status message"; "_set_status_message_" = "Set status message"; "_clear_status_message_after_" = "Clear status after"; @@ -220,49 +342,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 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_" = "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_description_" = "Choose whether new photos or videos will be automatically uploaded to your account."; "_autoupload_fullphotos_" = "Upload the whole camera roll"; "_start_autoupload_" = "Turn on auto upload"; @@ -273,48 +495,84 @@ "_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"; "_login_error_" = "Could not log in. Please try again later."; +"_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 team folders yet"; +//"_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"; @@ -325,25 +583,92 @@ "_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"; "_remove_favorites_" = "Unfavorite"; "_add_favorites_" = "Favorite"; +"_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"; +"_remove_favorites_" = "Unfavorite"; +"_add_favorites_" = "Favorite"; "_share_" = "Share"; +"_reload_" = "Reload"; +"_open_in_" = "Open with..."; //"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"; @@ -352,24 +677,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"; @@ -378,12 +715,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_viewvideo_show_" = "Show only videos"; "_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"; @@ -395,78 +739,236 @@ // MARK: Share "_share_link_" = "Share link"; +//"_share_link_" = "Share link"; +"_share_link_" = "Link"; +//"_share_link_" = "Share link"; +"_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; "_share_password_" = "Password protected link"; +"_share_password_must_be_at_least_6_chars" = "Password must be at least 6 characters."; +"_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_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_remaining_download_" = "Downloads:"; "_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"; +"_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."; +"_share_permission_should_not_be_empty_" = "Please select at least one permission."; "_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."; @@ -475,23 +977,79 @@ "_e2e_file_encrypted_" = "File encrypted"; "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_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"; @@ -507,7 +1065,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."; @@ -515,16 +1128,22 @@ "_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"; -"Enter Passcode" = "Enter Passcode"; -"Enter a new passcode" = "Enter a new passcode"; -"Confirm new passcode" = "Confirm new passcode"; +"_voice_memo_filename_" = "Voice memo"; +"_voice_memo_title_" = "Upload voice memo"; "_too_many_failed_passcode_attempts_error_" = "Too many failed passcode attempts. Please try again."; -"Passcodes didn't match. Try again." = "Passcodes didn't match. Try again."; +//"Passcodes didn't match. Try again." = "Passcodes didn't match. Try again."; "Delete" = "Delete"; "Cancel" = "Cancel"; "_certificate_details_" = "Certificate Server details"; @@ -547,10 +1166,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"; @@ -558,17 +1182,28 @@ "_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"; "_privacy_screen_" = "Hide content when app is inactive"; "_privacy_screen_footer_" = "Hide the content in the app and show a splash screen when the app is inactive."; +"_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_" = "⋯ Load 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 MagentaCLOUD 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"; @@ -576,10 +1211,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 …"; @@ -594,16 +1236,36 @@ "_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."; "_group_folders_" = "Team folders"; +"_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"; @@ -612,6 +1274,9 @@ "_reset_wrong_passcode_desc_" = "Enabling this will remove all accounts and local data after %d failed passcode 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"; @@ -627,12 +1292,16 @@ "_account_settings_" = "Account settings"; "_users_" = "Users"; "_users_footer_" = "Allows you to select an account every time you open the app."; +//"_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"; @@ -643,12 +1312,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_" = "There was an issue authorizing the account %@. Please log in again."; @@ -662,7 +1351,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"; @@ -671,7 +1360,7 @@ "_delete_in_progress_" = "Delete in progress …"; "_download_in_progress_" = "Download in progress …"; "_upload_in_progress_" = "Upload in progress …"; -"_transfer_in_progress_" = "Transfer in progress …"; +"_transfer_in_progress_" = "Transfer in progress …"; "_in_waiting_" = "In waiting"; "_in_progress_" = "In progress"; "_in_error_" = "In error"; @@ -691,6 +1380,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 @@ -703,15 +1393,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"; @@ -767,6 +1484,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 …"; "_scanning_files_" = "Scanning files …"; @@ -809,3 +1575,46 @@ You can stop it at any time, adjust the settings, and enable it again."; "_e2ee_setup_store_privatekey_" = "Private key generation returned no usable key"; "_e2ee_setup_verify_publickey_" = "Public key does not match the certificate"; "_e2ee_setup_generate_passphrase_" = "Unable to generate a passphrase"; + +// MARK: Albums +"_albums_loading_popup_desc_" = "Wait a moment..."; +"_albums_list_nav_title_" = "Albums"; +"_albums_list_new_album_btn_" = "New"; +"_albums_list_loading_msg_" = "Loading..."; +"_albums_list_error_msg_" = "Unable to load albums. Please try again later!"; +"_albums_list_album_name_validation_nonempty_" = "Album name cannot be empty."; +"_albums_list_album_name_validation_min_length_" = "Album name must be at least 3 characters."; +"_albums_list_album_name_validation_max_length_" = "Album name cannot be more than 30 characters."; +"_albums_list_album_name_validation_specials_" = "Album name cannot contain slashes."; +"_albums_list_empty_heading_" = "Create\nAlbums\nfor your\nPhotos"; +"_albums_list_empty_subheading_" = "You can organize all your photos in as many albums as you like. You haven't created an album yet."; +"_albums_list_empty_new_album_btn_" = "Create album"; +"_albums_list_own_albums_heading_" = "My albums"; +"_albums_list_entities_" = "Items"; +"_albums_list_new_album_popup_title_" = "Create new Album"; +"_albums_list_new_album_popup_desc_" = "Please enter an album name between 3 and 30 characters."; +"_albums_list_new_album_popup_hint_" = "Album's name"; +"_albums_list_new_album_popup_positive_btn_" = "Create"; +"_albums_list_new_album_popup_negative_btn_" = "Cancel"; +"_albums_list_rename_album_popup_title_" = "Rename Album"; +"_albums_list_rename_album_popup_desc_" = "Please enter new album name between 3 and 30 characters."; +"_albums_list_rename_album_popup_hint_" = "Album's new name"; +"_albums_list_rename_album_popup_positive_btn_" = "Rename"; +"_albums_list_rename_album_popup_negative_btn_" = "Cancel"; +"_albums_photo_selection_sheet_title_" = "Select items"; +"_albums_photo_selection_sheet_back_btn_" = "Back"; +"_albums_photo_selection_sheet_done_btn_" = "Done"; +"_albums_photos_loading_msg_" = "Loading photos..."; +"_albums_photos_error_msg_" = "Unable to load photos. Please try again later!"; +"_albums_photos_empty_heading_" = "All that's\nmissing are\nyour photos"; +"_albums_photos_empty_subheading_" = "You can add as many photos as you like. A photo can also belong to more than one album."; +"_albums_photos_empty_add_photos_btn_" = "Add photos"; +"_albums_photos_add_photos_btn_" = "Add"; +"_albums_photos_rename_album_btn_" = "Rename Album"; +"_albums_photos_delete_album_btn_" = "Delete Album"; +"_albums_delete_album_popup_title_" = "Delete Album?"; +"_albums_delete_album_popup_desc_" = "Are you sure you want to delete this album? This action cannot be undone."; +"_albums_delete_album_popup_positive_btn_" = "Delete"; +"_albums_delete_album_popup_negative_btn_" = "Cancel"; +"_add_to_album" = "Add to Album"; +"_remove_from_album_" = "Remove from Album";