From cec015f0a076e1fb6ac8d49d3c0072d5b2503718 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Wed, 24 Oct 2018 16:41:55 +0200 Subject: [PATCH 01/34] - Made a new option for open in another app inside the card view controller shown up via the 3 dots in the file list cell. - Made the open in another app via UIDocumentInteractionController. --- ios-sdk | 2 +- ownCloud/Client/ClientQueryViewController.swift | 2 ++ ownCloud/Resources/en.lproj/Localizable.strings | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index b5976fa40..8173316d4 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit b5976fa406c4936b2a4d79b383331ac10010ef6f +Subproject commit 8173316d4254c8a398a337c9678e57682be29f76 diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 2c44bc13a..4bf80a2fc 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -51,6 +51,8 @@ class ClientQueryViewController: UITableViewController, Themeable { private var observerContext : UnsafeMutableRawPointer var refreshController: UIRefreshControl? + var interactionController: UIDocumentInteractionController? + // MARK: - Init & Deinit public init(core inCore: OCCore, query inQuery: OCQuery) { observerContext = UnsafeMutableRawPointer(&observerContextValue) diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 79434313e..44da73c79 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -192,6 +192,7 @@ "Create folder" =" Create folder"; "Duplicate" = "Duplicate"; "Move" = "Move"; +"Open in" = "Open in"; /* Directory Picker Messages */ "Move here" = "Move here"; From 1b855846bae3a69bfd1a0df274fe8b54b8ddc73d Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Thu, 25 Oct 2018 10:36:24 +0200 Subject: [PATCH 02/34] - Removed unneeded observerContext in ClientQueryViewController --- ownCloud/Client/ClientQueryViewController.swift | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 4bf80a2fc..ee0039104 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -47,15 +47,13 @@ class ClientQueryViewController: UITableViewController, Themeable { } var progressSummarizer : ProgressSummarizer? var initialAppearance : Bool = true - private var observerContextValue = 1 - private var observerContext : UnsafeMutableRawPointer var refreshController: UIRefreshControl? var interactionController: UIDocumentInteractionController? // MARK: - Init & Deinit public init(core inCore: OCCore, query inQuery: OCQuery) { - observerContext = UnsafeMutableRawPointer(&observerContextValue) + super.init(style: .plain) core = inCore query = inQuery @@ -66,9 +64,7 @@ class ClientQueryViewController: UITableViewController, Themeable { query.delegate = self - query.addObserver(self, forKeyPath: "state", options: .initial, context: observerContext) - core.addObserver(self, forKeyPath: "reachabilityMonitor.available", options: .initial, context: observerContext) - + query.addObserver(self, forKeyPath: "state", options: .initial, context: nil) core.start(query) self.navigationItem.title = (query.queryPath as NSString?)!.lastPathComponent @@ -79,8 +75,7 @@ class ClientQueryViewController: UITableViewController, Themeable { } deinit { - query.removeObserver(self, forKeyPath: "state", context: observerContext) - core.removeObserver(self, forKeyPath: "reachabilityMonitor.available", context: observerContext) + query.removeObserver(self, forKeyPath: "state", context: nil) core.stop(query) Theme.shared.unregister(client: self) From 394b48b966041371a4bae0dd69a32387d9ee5b5d Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Thu, 25 Oct 2018 13:49:35 +0200 Subject: [PATCH 03/34] - Coded open in inside the file preview. --- ownCloud/Viewer/DisplayViewController.swift | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 45ac54014..1389cc02e 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -42,6 +42,8 @@ class DisplayViewController: UIViewController { private let ICON_IMAGE_SIZE: CGSize = CGSize(width: 200.0, height: 200.0) + private var interactionController: UIDocumentInteractionController? + // MARK: - Configuration weak var item: OCItem! weak var core: OCCore! { @@ -213,6 +215,7 @@ class DisplayViewController: UIViewController { } parent.navigationItem.title = item.name + parent.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "•••", style: .plain, target: self, action: #selector(optionsBarButtonPressed)) } // MARK: - Download actions @@ -307,6 +310,52 @@ class DisplayViewController: UIViewController { self.noNetworkLabel?.isHidden = true self.showPreviewButton?.isHidden = false } + + @objc func optionsBarButtonPressed() { + let tableViewController = MoreStaticTableViewController(style: .grouped) + let header = MoreViewHeader(for: item, with: core!) + let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) + + let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + + let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in + if UIDevice.current.isIpad() { + self?.openInRow(self!.item, button: self!.parent!.navigationItem.rightBarButtonItem!) + } else { + self?.openInRow(self!.item) + } + moreViewController.dismiss(animated: true) + }, title: "Open in".localized, style: .plainNonOpaque) + + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [openInRow])) + + self.present(asCard: moreViewController, animated: true) + } + + // MARK: - Actions + func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { + + guard source != nil else { + return + } + + OnMainThread { + self.interactionController = UIDocumentInteractionController(url: self.source) + self.interactionController?.delegate = self + if button != nil { + self.interactionController?.presentOptionsMenu(from: button!, animated: true) + } else { + self.interactionController?.presentOptionsMenu(from: .zero, in: self.view, animated: true) + } + } + } +} + +// MARK: - UIDocumentInteractionControllerDelegate +extension DisplayViewController: UIDocumentInteractionControllerDelegate { + func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) { + self.interactionController = nil + } } // MARK: - Public API From 9c663a47ac8d36d1dd30d46aa113e6ad9baf90ad Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Fri, 26 Oct 2018 14:20:28 +0200 Subject: [PATCH 04/34] - Coded support for open in when the previewed file is not supported --- ownCloud/Viewer/DisplayViewController.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 1389cc02e..6609855ae 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -335,12 +335,20 @@ class DisplayViewController: UIViewController { // MARK: - Actions func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { - guard source != nil else { - return + if source == nil { + core.downloadItem(item, options: nil) { (error, _, _, file) in + if error == nil { + self.openDocumentInteractionController(with: file!.url, button: button) + } + } + } else { + openDocumentInteractionController(with: source, button: button) } + } + private func openDocumentInteractionController(with source: URL, button: UIBarButtonItem?) { OnMainThread { - self.interactionController = UIDocumentInteractionController(url: self.source) + self.interactionController = UIDocumentInteractionController(url: source) self.interactionController?.delegate = self if button != nil { self.interactionController?.presentOptionsMenu(from: button!, animated: true) From 2f8de833bc2bd7b86dad8264f040423c23041f99 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 29 Oct 2018 12:31:24 +0100 Subject: [PATCH 05/34] - Made new screen to download files before open in, being able to cancel the download. --- ownCloud.xcodeproj/project.pbxproj | 4 + .../Client/ClientQueryViewController.swift | 5 +- .../Resources/en.lproj/Localizable.strings | 1 + ...ownloadFileProgressHUDViewController.swift | 206 ++++++++++++++++++ ownCloud/Viewer/DisplayViewController.swift | 14 +- 5 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index b0387d1a8..85a442ecb 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 59B09E7221AD6215007827B8 /* ownCloudMocking.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 59D4895220C83F2E00369C2E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 59D4895420C83F2E00369C2E /* InfoPlist.strings */; }; 6D107AA0B21417432C72755A /* EarlGrey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F3B3E74D4B04F9CAF95C09 /* EarlGrey.swift */; }; + 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */; }; 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */; }; 6E83C77E20A32C1B0066EC23 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */; }; 6E83C78420A33C180066EC23 /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */; }; @@ -432,6 +433,7 @@ 59D4895320C83F2E00369C2E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 59EACA8020CAA37F00F082EE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 6CBDF92D3844CF78B20B5770 /* Pods-ownCloudTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ownCloudTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ownCloudTests/Pods-ownCloudTests.release.xcconfig"; sourceTree = ""; }; + 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileProgressHUDViewController.swift; sourceTree = ""; }; 6E216A632112F58700ED21BD /* NamingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingViewController.swift; sourceTree = ""; }; 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDisplayViewController.swift; sourceTree = ""; }; 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; @@ -1096,6 +1098,7 @@ DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */, DC89C46220860B680044BCAE /* Progress */, 6EA78B8E2179B55400A5216A /* ImageScrollView.swift */, + 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */, ); path = "UI Elements"; sourceTree = ""; @@ -1607,6 +1610,7 @@ 4C464BF32187AF1500D30602 /* PDFOutlineViewController.swift in Sources */, 6EADE9372192E235006821B3 /* UIImagePickerController+Extension.swift in Sources */, DC85572C20513B8C00189B9A /* ServerListTableViewController.swift in Sources */, + 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */, 233BDEA0204FEFE500C06732 /* AppDelegate.swift in Sources */, 236735A621217C3500E5834A /* MoreViewController.swift in Sources */, 23957A6D209AFFE8003C8537 /* MoreSettingsSection.swift in Sources */, diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index ee0039104..552f45804 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -663,7 +663,10 @@ class ClientQueryViewController: UITableViewController, Themeable { completionHandler?(true) } }) { - self.progressSummarizer?.startTracking(progress: progress) + OnMainThread { + controller.present(on: self) + controller.attach(progress: progress) + } } } diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 44da73c79..b971005e0 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -202,6 +202,7 @@ "There is no network" = "There is no network"; "Error" = "Error"; "Could not get the picture" = "Could not get the picture"; +"Downloading" = "Downloading"; /* PDF Viewer */ "Resume" = "Resume"; diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift new file mode 100644 index 000000000..9833d056d --- /dev/null +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -0,0 +1,206 @@ +// +// DownloadFileProgressHUDViewController.swift +// ownCloud +// +// Created by Pablo Carrascal on 29/10/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import UIKit + +class DownloadFileProgressHUDViewController: UIViewController { + + private let PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT: CGFloat = 20 + private let PROGRESSVIEW_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 10 + private let CANCELBUTTON_TOP_ANCHOR_CONSTANT: CGFloat = 10 + private let CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 40 + private let INFOLABEL_BOTTOM_ANCHOR_CONSTANT: CGFloat = 10 + private let INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 20 + + // MARK: - Instance variables. + private var progressView: UIProgressView { + didSet { + if let observedProgress = progressView.observedProgress { + if observedProgress.isFinished { + self.dismiss(animated: true, completion: completion) + } + } + } + } + + private var cancelButton: ThemeButton + private var infoLabel: UILabel + private var transitionAnimator = ProgressHUDViewControllerAnimator() + private var completion: (() -> Void)? + + // MARK: - Init & deinit + init(with completionHandler: (() -> Void)? = nil) { + progressView = UIProgressView(progressViewStyle: .bar) + cancelButton = ThemeButton() + infoLabel = UILabel() + completion = completionHandler + + super.init(nibName: nil, bundle: nil) + + self.modalPresentationStyle = .overCurrentContext + self.transitioningDelegate = transitionAnimator + + Theme.shared.register(client: self) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + Theme.shared.unregister(client: self) + } + + override func loadView() { + super.loadView() + + view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.7) + + // Progress view + progressView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(progressView) + NSLayoutConstraint.activate([ + progressView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), + progressView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), + progressView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT), + progressView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT), + progressView.heightAnchor.constraint(equalToConstant: PROGRESSVIEW_HEIGHT_CONSTRAINT_CONSTANT) + ]) + + // Cancel button + cancelButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(cancelButton) + NSLayoutConstraint.activate([ + cancelButton.centerXAnchor.constraint(equalTo: progressView.centerXAnchor), + cancelButton.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: CANCELBUTTON_TOP_ANCHOR_CONSTANT), + cancelButton.leftAnchor.constraint(equalTo: progressView.leftAnchor), + cancelButton.rightAnchor.constraint(equalTo: progressView.rightAnchor), + cancelButton.heightAnchor.constraint(equalToConstant: CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT) + ]) + cancelButton.setTitle("Cancel".localized, for: .normal) + + // Info label + infoLabel.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(infoLabel) + NSLayoutConstraint.activate([ + infoLabel.centerXAnchor.constraint(equalTo: progressView.centerXAnchor), + infoLabel.bottomAnchor.constraint(equalTo: progressView.topAnchor, constant: -INFOLABEL_BOTTOM_ANCHOR_CONSTANT), + infoLabel.leftAnchor.constraint(equalTo: progressView.leftAnchor), + infoLabel.rightAnchor.constraint(equalTo: progressView.rightAnchor), + infoLabel.heightAnchor.constraint(equalToConstant: INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT) + ]) + + infoLabel.text = "Downloading" + infoLabel.textAlignment = .center + + progressView.progressTintColor = .white + } + + override func viewDidLoad() { + super.viewDidLoad() + + cancelButton.addTarget(self, action: #selector(cancelButtonPressed), for: .touchUpInside) + } + + @objc private func cancelButtonPressed() { + guard let observedProgress = progressView.observedProgress, observedProgress.isCancellable else { + return + } + + observedProgress.cancel() + dismiss(animated: true, completion: completion) + } + +} + +// MARK: - Public API +extension DownloadFileProgressHUDViewController { + + func present(on viewController: UIViewController) { + viewController.present(self, animated: true) + } + + func attach(progress: Progress) { + progressView.observedProgress = progress + } + +} + +// MARK: - Theme support +extension DownloadFileProgressHUDViewController: Themeable { + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + infoLabel.applyThemeCollection(collection) + cancelButton.applyThemeCollection(collection) + } +} + +internal class DownloadFileProgressHUDViewControllerAnimator : NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { + var isDismissing : Bool = false + let duration = 0.4 + + // MARK: - UIViewControllerTransitioningDelegate + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + isDismissing = true + return self + } + + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + isDismissing = false + return self + } + + // MARK: - UIViewControllerAnimatedTransitioning + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let containerView = transitionContext.containerView + + if isDismissing { + if let fromView = transitionContext.view(forKey: .from) { + let fromViewController = transitionContext.viewController(forKey: .from) + let hudViewController = fromViewController as? DownloadFileProgressHUDViewController + + if fromViewController != nil { + fromView.frame = transitionContext.initialFrame(for: fromViewController!) + } + + containerView.addSubview(fromView) + + UIView.animate(withDuration: duration, animations: { + fromView.alpha = 0 + hudViewController?.view.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) + }, completion: { (_) in + transitionContext.completeTransition(true) + }) + } + } else { + if let toView = transitionContext.view(forKey: .to) { + let toViewController = transitionContext.viewController(forKey: .to) + let hudViewController = toViewController as? DownloadFileProgressHUDViewController + + if toViewController != nil { + toView.frame = transitionContext.finalFrame(for: toViewController!) + } + + containerView.addSubview(toView) + + toView.alpha = 0 + hudViewController?.view.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) + + UIView.animate(withDuration: duration, animations: { + toView.alpha = 1 + hudViewController?.view.transform = .identity + }, completion: { (_) in + transitionContext.completeTransition(true) + }) + } + } + } +} diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 6609855ae..8b4c03afb 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -336,9 +336,19 @@ class DisplayViewController: UIViewController { func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { if source == nil { - core.downloadItem(item, options: nil) { (error, _, _, file) in + let controller = DownloadFileProgressHUDViewController(with: nil) + + if let progress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in if error == nil { - self.openDocumentInteractionController(with: file!.url, button: button) + self.source = file!.url + controller.dismiss(animated: true, completion: { + self.openDocumentInteractionController(with: file!.url, button: button) + }) + } + }) { + OnMainThread { + controller.present(on: self) + controller.attach(progress: progress) } } } else { From 5fd8a38f9c7fa1984ce463fe71609e2a6547b375 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 29 Oct 2018 12:56:44 +0100 Subject: [PATCH 06/34] - Clean up the code. --- ...ownloadFileProgressHUDViewController.swift | 26 ++++++++++++------- ownCloud/Viewer/DisplayViewController.swift | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index 9833d056d..96b2b937d 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -16,6 +16,8 @@ class DownloadFileProgressHUDViewController: UIViewController { private let CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 40 private let INFOLABEL_BOTTOM_ANCHOR_CONSTANT: CGFloat = 10 private let INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 20 + private let ROOT_VIEW_BACKGROUND_WHITE: CGFloat = 0.0 + private let ROOT_VIEW_BACKGROUND_ALPHA: CGFloat = 0.7 // MARK: - Instance variables. private var progressView: UIProgressView { @@ -42,7 +44,7 @@ class DownloadFileProgressHUDViewController: UIViewController { super.init(nibName: nil, bundle: nil) - self.modalPresentationStyle = .overCurrentContext + self.modalPresentationStyle = .overFullScreen self.transitioningDelegate = transitionAnimator Theme.shared.register(client: self) @@ -54,12 +56,16 @@ class DownloadFileProgressHUDViewController: UIViewController { deinit { Theme.shared.unregister(client: self) + + if let progress = progressView.observedProgress, !progress.isFinished { + progress.cancel() + } } override func loadView() { super.loadView() - view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.7) + view.backgroundColor = UIColor.init(white: ROOT_VIEW_BACKGROUND_WHITE, alpha: ROOT_VIEW_BACKGROUND_ALPHA) // Progress view progressView.translatesAutoresizingMaskIntoConstraints = false @@ -96,6 +102,7 @@ class DownloadFileProgressHUDViewController: UIViewController { ]) infoLabel.text = "Downloading" + infoLabel.textColor = .white infoLabel.textAlignment = .center progressView.progressTintColor = .white @@ -134,14 +141,15 @@ extension DownloadFileProgressHUDViewController { // MARK: - Theme support extension DownloadFileProgressHUDViewController: Themeable { func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - infoLabel.applyThemeCollection(collection) cancelButton.applyThemeCollection(collection) } } internal class DownloadFileProgressHUDViewControllerAnimator : NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { + private let ANIMATION_DURATION: Double = 0.4 + private let AFFINE_TRANSFORM_SCALE: CGAffineTransform = CGAffineTransform(scaleX: 0.6, y: 0.6) + var isDismissing : Bool = false - let duration = 0.4 // MARK: - UIViewControllerTransitioningDelegate func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { @@ -156,7 +164,7 @@ internal class DownloadFileProgressHUDViewControllerAnimator : NSObject, UIViewC // MARK: - UIViewControllerAnimatedTransitioning func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return duration + return ANIMATION_DURATION } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -173,9 +181,9 @@ internal class DownloadFileProgressHUDViewControllerAnimator : NSObject, UIViewC containerView.addSubview(fromView) - UIView.animate(withDuration: duration, animations: { + UIView.animate(withDuration: ANIMATION_DURATION, animations: { fromView.alpha = 0 - hudViewController?.view.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) + hudViewController?.view.transform = self.AFFINE_TRANSFORM_SCALE }, completion: { (_) in transitionContext.completeTransition(true) }) @@ -192,9 +200,9 @@ internal class DownloadFileProgressHUDViewControllerAnimator : NSObject, UIViewC containerView.addSubview(toView) toView.alpha = 0 - hudViewController?.view.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) + hudViewController?.view.transform = self.AFFINE_TRANSFORM_SCALE - UIView.animate(withDuration: duration, animations: { + UIView.animate(withDuration: ANIMATION_DURATION, animations: { toView.alpha = 1 hudViewController?.view.transform = .identity }, completion: { (_) in diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 8b4c03afb..52381d33c 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -336,7 +336,7 @@ class DisplayViewController: UIViewController { func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { if source == nil { - let controller = DownloadFileProgressHUDViewController(with: nil) + let controller = DownloadFileProgressHUDViewController() if let progress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in if error == nil { From d68e8bb5a614ccb005fc2f3b14f0b7a490f580dc Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 29 Oct 2018 13:17:30 +0100 Subject: [PATCH 07/34] - Localize "Downloading" string --- .../UI Elements/DownloadFileProgressHUDViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index 96b2b937d..e00ebe112 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -101,7 +101,7 @@ class DownloadFileProgressHUDViewController: UIViewController { infoLabel.heightAnchor.constraint(equalToConstant: INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT) ]) - infoLabel.text = "Downloading" + infoLabel.text = "Downloading".localized infoLabel.textColor = .white infoLabel.textAlignment = .center From 18053feb9f61c695282faa4611be07bfca57ada4 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 29 Oct 2018 14:40:13 +0100 Subject: [PATCH 08/34] - Managed state of the view when there is no network (WIP). - Removed unneeded constraint for the progress view. --- .../Client/ClientQueryViewController.swift | 6 ++-- ...ownloadFileProgressHUDViewController.swift | 5 ++- ownCloud/Viewer/DisplayViewController.swift | 33 ++++++++++++------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 552f45804..bc860a455 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -662,10 +662,10 @@ class ClientQueryViewController: UITableViewController, Themeable { Log.debug("Success uploading \(Log.mask(name)) file to \(Log.mask(self?.query.rootItem.path))") completionHandler?(true) } - }) { + } else { OnMainThread { - controller.present(on: self) - controller.attach(progress: progress) + let alert = UIAlertController(with: "No Network connection", message: "No network connection") + self.present(alert, animated: true) } } } diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index e00ebe112..a54c9c028 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -11,7 +11,6 @@ import UIKit class DownloadFileProgressHUDViewController: UIViewController { private let PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT: CGFloat = 20 - private let PROGRESSVIEW_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 10 private let CANCELBUTTON_TOP_ANCHOR_CONSTANT: CGFloat = 10 private let CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 40 private let INFOLABEL_BOTTOM_ANCHOR_CONSTANT: CGFloat = 10 @@ -74,8 +73,7 @@ class DownloadFileProgressHUDViewController: UIViewController { progressView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), progressView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), progressView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT), - progressView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT), - progressView.heightAnchor.constraint(equalToConstant: PROGRESSVIEW_HEIGHT_CONSTRAINT_CONSTANT) + progressView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT) ]) // Cancel button @@ -106,6 +104,7 @@ class DownloadFileProgressHUDViewController: UIViewController { infoLabel.textAlignment = .center progressView.progressTintColor = .white + progressView.trackTintColor = .lightGray } override func viewDidLoad() { diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 52381d33c..38cdf7b8e 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -336,19 +336,28 @@ class DisplayViewController: UIViewController { func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { if source == nil { - let controller = DownloadFileProgressHUDViewController() - - if let progress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in - if error == nil { - self.source = file!.url - controller.dismiss(animated: true, completion: { - self.openDocumentInteractionController(with: file!.url, button: button) - }) - } - }) { + if !core.reachabilityMonitor.available { OnMainThread { - controller.present(on: self) - controller.attach(progress: progress) + let alert = UIAlertController(with: "No Network connection", message: "No network connection") + self.present(alert, animated: true) + } + } else { + let controller = DownloadFileProgressHUDViewController() + + if let progress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in + if error == nil { + self.source = file!.url + controller.dismiss(animated: true, completion: { + self.openDocumentInteractionController(with: file!.url, button: button) + }) + } else { + controller.dismiss(animated: true) + } + }) { + OnMainThread { + controller.present(on: self) + controller.attach(progress: progress) + } } } } else { From 1128f611f0a6d3bc833389c1ee2fe9fc60118e0f Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 30 Oct 2018 12:42:36 +0100 Subject: [PATCH 09/34] - Coding style changes. --- ...ownloadFileProgressHUDViewController.swift | 28 +++++++++---------- ownCloud/Viewer/DisplayViewController.swift | 10 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index a54c9c028..7655a21d3 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -10,13 +10,13 @@ import UIKit class DownloadFileProgressHUDViewController: UIViewController { - private let PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT: CGFloat = 20 - private let CANCELBUTTON_TOP_ANCHOR_CONSTANT: CGFloat = 10 - private let CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 40 - private let INFOLABEL_BOTTOM_ANCHOR_CONSTANT: CGFloat = 10 - private let INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT: CGFloat = 20 - private let ROOT_VIEW_BACKGROUND_WHITE: CGFloat = 0.0 - private let ROOT_VIEW_BACKGROUND_ALPHA: CGFloat = 0.7 + private let progressViewSidesConstraintConstant: CGFloat = 20 + private let cancelButtonTopAnchor: CGFloat = 10 + private let cancelButtonHeightConstraintConstant: CGFloat = 40 + private let infoLabelBottomAnchor: CGFloat = 10 + private let infoLabelHeightConstraintConstant: CGFloat = 20 + private let rootViewBackgroundWhite: CGFloat = 0.0 + private let rootViewBackgroundAlpha: CGFloat = 0.7 // MARK: - Instance variables. private var progressView: UIProgressView { @@ -64,7 +64,7 @@ class DownloadFileProgressHUDViewController: UIViewController { override func loadView() { super.loadView() - view.backgroundColor = UIColor.init(white: ROOT_VIEW_BACKGROUND_WHITE, alpha: ROOT_VIEW_BACKGROUND_ALPHA) + view.backgroundColor = UIColor.init(white: rootViewBackgroundWhite, alpha: rootViewBackgroundAlpha) // Progress view progressView.translatesAutoresizingMaskIntoConstraints = false @@ -72,8 +72,8 @@ class DownloadFileProgressHUDViewController: UIViewController { NSLayoutConstraint.activate([ progressView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), progressView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), - progressView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT), - progressView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -PROGRESSVIEW_SIDES_CONSTRAINT_CONSTANT) + progressView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: progressViewSidesConstraintConstant), + progressView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -progressViewSidesConstraintConstant) ]) // Cancel button @@ -81,10 +81,10 @@ class DownloadFileProgressHUDViewController: UIViewController { view.addSubview(cancelButton) NSLayoutConstraint.activate([ cancelButton.centerXAnchor.constraint(equalTo: progressView.centerXAnchor), - cancelButton.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: CANCELBUTTON_TOP_ANCHOR_CONSTANT), + cancelButton.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: cancelButtonTopAnchor), cancelButton.leftAnchor.constraint(equalTo: progressView.leftAnchor), cancelButton.rightAnchor.constraint(equalTo: progressView.rightAnchor), - cancelButton.heightAnchor.constraint(equalToConstant: CANCELBUTTON_HEIGHT_CONSTRAINT_CONSTANT) + cancelButton.heightAnchor.constraint(equalToConstant: cancelButtonHeightConstraintConstant) ]) cancelButton.setTitle("Cancel".localized, for: .normal) @@ -93,10 +93,10 @@ class DownloadFileProgressHUDViewController: UIViewController { view.addSubview(infoLabel) NSLayoutConstraint.activate([ infoLabel.centerXAnchor.constraint(equalTo: progressView.centerXAnchor), - infoLabel.bottomAnchor.constraint(equalTo: progressView.topAnchor, constant: -INFOLABEL_BOTTOM_ANCHOR_CONSTANT), + infoLabel.bottomAnchor.constraint(equalTo: progressView.topAnchor, constant: -infoLabelBottomAnchor), infoLabel.leftAnchor.constraint(equalTo: progressView.leftAnchor), infoLabel.rightAnchor.constraint(equalTo: progressView.rightAnchor), - infoLabel.heightAnchor.constraint(equalToConstant: INFOLABEL_HEIGHT_CONSTRAINT_CONSTANT) + infoLabel.heightAnchor.constraint(equalToConstant: infoLabelHeightConstraintConstant) ]) infoLabel.text = "Downloading".localized diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 38cdf7b8e..8cbf89091 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -40,7 +40,7 @@ protocol DisplayViewEditingDelegate: class { class DisplayViewController: UIViewController { - private let ICON_IMAGE_SIZE: CGSize = CGSize(width: 200.0, height: 200.0) + private let iconImageSize: CGSize = CGSize(width: 200.0, height: 200.0) private var interactionController: UIDocumentInteractionController? @@ -156,7 +156,7 @@ class DisplayViewController: UIViewController { NSLayoutConstraint.activate([ iconImageView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), iconImageView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor, constant: -60), - iconImageView.heightAnchor.constraint(equalToConstant: ICON_IMAGE_SIZE.height), + iconImageView.heightAnchor.constraint(equalToConstant: iconImageSize.height), iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor), metadataInfoLabel!.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), @@ -182,11 +182,11 @@ class DisplayViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - iconImageView.image = item.icon(fitInSize:ICON_IMAGE_SIZE) + iconImageView.image = item.icon(fitInSize:iconImageSize) if item.thumbnailAvailability != .none { let displayThumbnail = { (thumbnail: OCItemThumbnail?) in - _ = thumbnail?.requestImage(for: self.ICON_IMAGE_SIZE, scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in + _ = thumbnail?.requestImage(for: self.iconImageSize, scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in if error == nil, image != nil, self.item.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { @@ -202,7 +202,7 @@ class DisplayViewController: UIViewController { if let thumbnail = item.thumbnail { displayThumbnail(thumbnail) } else { - _ = core?.retrieveThumbnail(for: item, maximumSize: ICON_IMAGE_SIZE, scale: 0, retrieveHandler: { (_, _, _, thumbnail, _, _) in + _ = core?.retrieveThumbnail(for: item, maximumSize: iconImageSize, scale: 0, retrieveHandler: { (_, _, _, thumbnail, _, _) in displayThumbnail(thumbnail) }) } From 79eef09c0f204647d161d03543472d88f02ea243 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 30 Oct 2018 12:44:15 +0100 Subject: [PATCH 10/34] - Coded missing license. --- ownCloud.xcodeproj/project.pbxproj | 4 + ownCloud/Client/Actions/Action.swift | 208 ++++++++++++++++++ .../Client/ClientQueryViewController.swift | 88 +++++--- ...ownloadFileProgressHUDViewController.swift | 10 + ownCloud/Viewer/DisplayViewController.swift | 40 ++-- 5 files changed, 300 insertions(+), 50 deletions(-) create mode 100644 ownCloud/Client/Actions/Action.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 85a442ecb..6006759a5 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 59D4895220C83F2E00369C2E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 59D4895420C83F2E00369C2E /* InfoPlist.strings */; }; 6D107AA0B21417432C72755A /* EarlGrey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F3B3E74D4B04F9CAF95C09 /* EarlGrey.swift */; }; 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */; }; + 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */; }; 6E83C77E20A32C1B0066EC23 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */; }; 6E83C78420A33C180066EC23 /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */; }; @@ -435,6 +436,7 @@ 6CBDF92D3844CF78B20B5770 /* Pods-ownCloudTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ownCloudTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ownCloudTests/Pods-ownCloudTests.release.xcconfig"; sourceTree = ""; }; 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileProgressHUDViewController.swift; sourceTree = ""; }; 6E216A632112F58700ED21BD /* NamingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingViewController.swift; sourceTree = ""; }; + 6E37F48A2188B27D00CF16CA /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDisplayViewController.swift; sourceTree = ""; }; 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LAContext+Extension.swift"; sourceTree = ""; }; @@ -708,6 +710,7 @@ 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */, 232B01F52126B10900366FA0 /* MoreStaticTableViewController.swift */, 23D77FC6212BFBD100DE76F1 /* NamingViewController.swift */, + 6E37F48A2188B27D00CF16CA /* Action.swift */, ); path = Actions; sourceTree = ""; @@ -1635,6 +1638,7 @@ DC42244C207CAFBB0006A2A6 /* ThemeCollection.swift in Sources */, DC68057A212EAB5E006C3B1F /* ThemeCertificateViewController.swift in Sources */, DCFED9BA20809B8900A2D984 /* ThemeTVGResource.swift in Sources */, + 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */, DC3BE0DE2077CC14002A0AC0 /* ClientQueryViewController.swift in Sources */, 23C56538212167BE00BD4B47 /* CardTransitionDelegate.swift in Sources */, DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */, diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift new file mode 100644 index 000000000..289b231de --- /dev/null +++ b/ownCloud/Client/Actions/Action.swift @@ -0,0 +1,208 @@ +// +// Action.swift +// ownCloud +// +// Created by Pablo Carrascal on 30/10/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK + +typealias ActionCompletion = ((_ item: OCItem, _ core: OCCore, _ vcToPresent: UIViewController) -> Void)? + +enum Result { + case failure(Error) + case sucess(T) +} + +enum ActionType { + case destructive + case regular +} + +struct Action { + var name: String + var type: ActionType + var completion: ActionCompletion + + init(with name: String, completion: ActionCompletion, type: ActionType) { + self.name = name + self.completion = completion + self.type = type + + } +} + +class ActionsMoreViewController: NSObject { + + weak var vcToPresentIn: UIViewController? + var moreViewController: UIViewController? + var core: OCCore + var item: OCItem + + var interactionController: UIDocumentInteractionController? + + init (item: OCItem, core: OCCore, into viewController: UIViewController) { + self.vcToPresentIn = viewController + self.core = core + self.item = item + super.init() + } + + func presentActionsCard(with actions: [Action], completion: () -> Void) { + self.moreViewController = actionsViewController(with: actions, for: item, core: core) + vcToPresentIn?.present(asCard: moreViewController!, animated: true) + } + + func actionsViewController(with actions: [Action], for item: OCItem, core: OCCore) -> MoreViewController { + + let header = MoreViewHeader(for: item, with: core) + let tableViewController = MoreStaticTableViewController(style: .grouped) + let moreViewController: MoreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) + + var rows: [StaticTableViewRow] = [] + + for action in actions { + + var style: StaticTableViewRowButtonStyle + switch action.type { + case .destructive: + style = .destructive + default: + style = .plainNonOpaque + } + + let row: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { (_, _) in + moreViewController.dismiss(animated: true, completion: { + action.completion?(item, core, self.vcToPresentIn!) + }) + }, title: action.name, style: style) + + rows.append(row) + } + + let title = NSAttributedString(string: "Actions".localized, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + + let section = MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: rows) + + tableViewController.addSection(section) + return moreViewController + } + + var openIn: Action { + let action = Action(with: "Open in".localized, completion: { (item, core, vcToPresentIn) in + let controller = DownloadFileProgressHUDViewController() + controller.present(on: vcToPresentIn) + + if let downloadProgress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in + if error != nil { + Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") + } else { + controller.dismiss(animated: true, completion: { + self.interactionController = UIDocumentInteractionController(url: file!.url) + self.interactionController?.presentOptionsMenu(from: .zero, in: vcToPresentIn.view, animated: true) + }) + } + }) { + controller.attach(progress: downloadProgress) + } else { + let alert = UIAlertController(with: "No Network connection", message: "No network connection") + vcToPresentIn.present(alert, animated: true) + } + }, type: .regular) + return action + } + + var duplicate: Action { + let action = Action(with: "Duplicate", completion: { (item, core, viewcontroller) in + + guard let viewController = viewcontroller as? ClientQueryViewController else { + return + } + + var name: String = "\(item.name!) copy" + + if item.type != .collection { + let itemName = item.nameWithoutExtension() + var fileExtension = item.fileExtension() + + if fileExtension != "" { + fileExtension = ".\(fileExtension)" + } + + name = "\(itemName) copy\(fileExtension)" + } + + if let progress = core.copy(item, to: viewController.query?.rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in + if error != nil { + Log.log("Error \(String(describing: error)) deleting \(String(describing: item?.path))") + } + }) { + viewController.progressSummarizer?.startTracking(progress: progress) + } + + }, type: .regular) + + return action + } + + var move: Action { + let action = Action(with: "Move".localized, completion: { (item, core, viewController) in + + guard let viewController = viewController as? ClientQueryViewController else { + return + } + + let directoryPickerVC = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in + + if let progress = core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") + } else { + } + }) { + viewController.progressSummarizer?.startTracking(progress: progress) + } + }) + + let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerVC) + viewController.navigationController?.present(pickerNavigationController, animated: true) + }, type: .regular) + + return action + } + + var delete: Action { + let action = Action(with: "Delete".localized, completion: { (item, core, viewController) in + let alertController = UIAlertController( + with: item.name!, + message: "Are you sure you want to delete this item from the server?".localized, + destructiveLabel: "Delete".localized, + preferredStyle: UIDevice.current.isIpad() ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet, + destructiveAction: { + if let progress = core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") + } + }) { + if let viewController = viewController as? ClientQueryViewController { + viewController.progressSummarizer?.startTracking(progress: progress) + } else { + viewController.dismiss(animated: true) + } + } + }) + + viewController.present(alertController, animated: true) + }, type: .destructive) + + return action + } +} + +extension ActionsMoreViewController: UIDocumentInteractionControllerDelegate { + func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) { + self.interactionController = nil + } +} diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index bc860a455..adab60aa6 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -836,40 +836,64 @@ extension ClientQueryViewController: UISearchResultsUpdating { // MARK: - ClientItemCell Delegate extension ClientQueryViewController: ClientItemCellDelegate { - func moreButtonTapped(cell: ClientItemCell) { - if let item = cell.item { - - let tableViewController = MoreStaticTableViewController(style: .grouped) - let header = MoreViewHeader(for: item, with: core) - let moreViewController = MoreViewController(header: header, viewController: tableViewController) - - let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - - let deleteRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self, weak moreViewController] (_, _) in - moreViewController?.dismiss(animated: true, completion: { - self?.delete(item) - }) - }, title: "Delete".localized, style: .destructive) - - let renameRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self, weak moreViewController] (_, _) in - moreViewController?.dismiss(animated: true, completion: { - self?.rename(item) - }) - }, title: "Rename".localized, style: .plainNonOpaque) - - let moveRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self, weak moreViewController] (_, _) in - moreViewController?.dismiss(animated: true, completion: { - self?.move(item) - }) - }, title: "Move".localized, style: .plainNonOpaque) +// func moreButtonTapped(cell: ClientItemCell) { +// if let item = cell.item { +// +// let tableViewController = MoreStaticTableViewController(style: .grouped) +// let header = MoreViewHeader(for: item, with: core!) +// let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) +// +// let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) +// +// let deleteRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { (_, _) in +// moreViewController.dismiss(animated: true, completion: { +// self.delete(item) +// }) +// }, title: "Delete".localized, style: .destructive) +// +// let renameRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in +// moreViewController.dismiss(animated: true, completion: { +// self?.rename(item) +// }) +// }, title: "Rename".localized, style: .plainNonOpaque) +// +// let moveRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in +// moreViewController.dismiss(animated: true, completion: { +// self?.move(item) +// }) +// }, title: "Move".localized, style: .plainNonOpaque) +// +// var rows = [renameRow, moveRow, deleteRow] +// +// if item.type == .file { +// let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in +// moreViewController.dismiss(animated: true, completion: { +// if UIDevice.current.isIpad() { +// +// self?.openInRow(item, cell: cell) +// } else { +// self?.openInRow(item) +// } +// }) +// }, title: "Open in".localized, style: .plainNonOpaque) +// +// rows.insert(openInRow, at: 0) +// } +// +// tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: rows)) +// +// self.present(asCard: moreViewController, animated: true) +// } +// } - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [ - renameRow, - moveRow, - deleteRow - ])) + func moreButtonTapped(cell: ClientItemCell) { + guard let item = cell.item else { + return + } - self.present(asCard: moreViewController, animated: true) + let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) + actionsObject.presentActionsCard(with: [actionsObject.openIn, actionsObject.duplicate, actionsObject.move, actionsObject.delete]) { + print("LOG ---> presented") } } } diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index 7655a21d3..af3c7623f 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import UIKit class DownloadFileProgressHUDViewController: UIViewController { diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 8cbf89091..857fb0d60 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -312,24 +312,28 @@ class DisplayViewController: UIViewController { } @objc func optionsBarButtonPressed() { - let tableViewController = MoreStaticTableViewController(style: .grouped) - let header = MoreViewHeader(for: item, with: core!) - let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) - - let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - - let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in - if UIDevice.current.isIpad() { - self?.openInRow(self!.item, button: self!.parent!.navigationItem.rightBarButtonItem!) - } else { - self?.openInRow(self!.item) - } - moreViewController.dismiss(animated: true) - }, title: "Open in".localized, style: .plainNonOpaque) - - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [openInRow])) - - self.present(asCard: moreViewController, animated: true) +// let tableViewController = MoreStaticTableViewController(style: .grouped) +// let header = MoreViewHeader(for: item, with: core!) +// let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) +// +// let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) +// +// let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in +// if UIDevice.current.isIpad() { +// self?.openInRow(self!.item, button: self!.parent!.navigationItem.rightBarButtonItem!) +// } else { +// self?.openInRow(self!.item) +// } +// moreViewController.dismiss(animated: true) +// }, title: "Open in".localized, style: .plainNonOpaque) +// +// tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [openInRow])) +// + // self.present(asCard: moreViewController, animated: true)let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) + let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) + actionsObject.presentActionsCard(with: [actionsObject.openIn, actionsObject.delete]) { + print("LOG ---> presented") + } } // MARK: - Actions From cff5098714244c5d60c91c1c9b09bc00a156e424 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 5 Nov 2018 10:25:03 +0100 Subject: [PATCH 11/34] - Made a completion handler for actions --- ownCloud/Client/Actions/Action.swift | 60 +++++++++++++++---- .../Client/ClientQueryViewController.swift | 5 +- ownCloud/Viewer/DisplayViewController.swift | 4 +- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 289b231de..fcfeb2aba 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -11,11 +11,6 @@ import ownCloudSDK typealias ActionCompletion = ((_ item: OCItem, _ core: OCCore, _ vcToPresent: UIViewController) -> Void)? -enum Result { - case failure(Error) - case sucess(T) -} - enum ActionType { case destructive case regular @@ -90,7 +85,7 @@ class ActionsMoreViewController: NSObject { return moreViewController } - var openIn: Action { + func openIn(completion: (() -> Void)? = nil) -> Action { let action = Action(with: "Open in".localized, completion: { (item, core, vcToPresentIn) in let controller = DownloadFileProgressHUDViewController() controller.present(on: vcToPresentIn) @@ -104,6 +99,7 @@ class ActionsMoreViewController: NSObject { self.interactionController?.presentOptionsMenu(from: .zero, in: vcToPresentIn.view, animated: true) }) } + completion?() }) { controller.attach(progress: downloadProgress) } else { @@ -114,7 +110,7 @@ class ActionsMoreViewController: NSObject { return action } - var duplicate: Action { + func duplicate(completion: (() -> Void)? = nil) -> Action { let action = Action(with: "Duplicate", completion: { (item, core, viewcontroller) in guard let viewController = viewcontroller as? ClientQueryViewController else { @@ -134,10 +130,11 @@ class ActionsMoreViewController: NSObject { name = "\(itemName) copy\(fileExtension)" } - if let progress = core.copy(item, to: viewController.query?.rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in + if let progress = core.copy(item, to: viewController.query.rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in if error != nil { Log.log("Error \(String(describing: error)) deleting \(String(describing: item?.path))") } + completion?() }) { viewController.progressSummarizer?.startTracking(progress: progress) } @@ -147,7 +144,7 @@ class ActionsMoreViewController: NSObject { return action } - var move: Action { + func move(completion: (() -> Void)? = nil) -> Action { let action = Action(with: "Move".localized, completion: { (item, core, viewController) in guard let viewController = viewController as? ClientQueryViewController else { @@ -159,8 +156,8 @@ class ActionsMoreViewController: NSObject { if let progress = core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") - } else { } + completion?() }) { viewController.progressSummarizer?.startTracking(progress: progress) } @@ -173,7 +170,7 @@ class ActionsMoreViewController: NSObject { return action } - var delete: Action { + func delete(completion: (() -> Void)? = nil) -> Action { let action = Action(with: "Delete".localized, completion: { (item, core, viewController) in let alertController = UIAlertController( with: item.name!, @@ -185,11 +182,10 @@ class ActionsMoreViewController: NSObject { if error != nil { Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") } + completion?() }) { if let viewController = viewController as? ClientQueryViewController { viewController.progressSummarizer?.startTracking(progress: progress) - } else { - viewController.dismiss(animated: true) } } }) @@ -199,6 +195,44 @@ class ActionsMoreViewController: NSObject { return action } + + func rename(completion: (() -> Void)? = nil) -> Action { + let action = Action(with: "Rename".localized, completion: { (item, core, viewController) in + guard let viewController = viewController as? ClientQueryViewController else { + return + } + + let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in + if name.contains("/") || name.contains("\\") { + return (false, "File name cannot contain / or \\") + } else { + return (true, nil) + } + }, completion: { newName, _ in + + guard newName != nil else { + return + } + + if let progress = core.move(item, to: viewController.query.rootItem, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") + } + completion?() + }) { + viewController.progressSummarizer?.startTracking(progress: progress) + } + }) + + renameViewController.navigationItem.title = "Rename".localized + + let navigationController = ThemeNavigationController(rootViewController: renameViewController) + navigationController.modalPresentationStyle = .overFullScreen + + viewController.present(navigationController, animated: true) + }, type: .regular) + return action + } } extension ActionsMoreViewController: UIDocumentInteractionControllerDelegate { diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index adab60aa6..05e0f4970 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -53,7 +53,6 @@ class ClientQueryViewController: UITableViewController, Themeable { // MARK: - Init & Deinit public init(core inCore: OCCore, query inQuery: OCQuery) { - super.init(style: .plain) core = inCore query = inQuery @@ -891,8 +890,8 @@ extension ClientQueryViewController: ClientItemCellDelegate { return } - let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) - actionsObject.presentActionsCard(with: [actionsObject.openIn, actionsObject.duplicate, actionsObject.move, actionsObject.delete]) { + let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core, into: self) + actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.duplicate(), actionsObject.rename(), actionsObject.move(), actionsObject.delete()]) { print("LOG ---> presented") } } diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 857fb0d60..276519b49 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -331,7 +331,9 @@ class DisplayViewController: UIViewController { // // self.present(asCard: moreViewController, animated: true)let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) - actionsObject.presentActionsCard(with: [actionsObject.openIn, actionsObject.delete]) { + actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.delete(completion: { + self.parent?.dismiss(animated: true) + })]) { print("LOG ---> presented") } } From 5ce980e84b19aeaa76afc41b9e58769f27a4767e Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Wed, 14 Nov 2018 08:30:18 +0100 Subject: [PATCH 12/34] - Made actions mechanism based on @felix-schwarz one. - Made one Action subclass per action. - Register the actions in the AppDelegate - Ask for extensions in ClientQueryViewController. --- ownCloud.xcodeproj/project.pbxproj | 20 ++ ownCloud/AppDelegate.swift | 3 + ownCloud/Client/Actions/Action.swift | 333 ++++++++---------- .../Actions+Extensions/DeleteAction.swift | 54 +++ .../Actions+Extensions/MoveAction.swift | 49 +++ .../Actions+Extensions/OpenInAction.swift | 66 ++++ .../Client/ClientQueryViewController.swift | 115 ++++-- ...ownloadFileProgressHUDViewController.swift | 4 +- ownCloud/Viewer/DisplayViewController.swift | 12 +- 9 files changed, 446 insertions(+), 210 deletions(-) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift create mode 100644 ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift create mode 100644 ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 6006759a5..1af788bd8 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -67,6 +67,9 @@ 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */; }; 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */; }; + 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFB2199A72600F680C4 /* OpenInAction.swift */; }; + 6E586CFE2199A75900F680C4 /* MoveAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFD2199A75900F680C4 /* MoveAction.swift */; }; + 6E586D002199A78E00F680C4 /* DeleteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */; }; 6E83C77E20A32C1B0066EC23 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */; }; 6E83C78420A33C180066EC23 /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */; }; 6EA78B8F2179B55400A5216A /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA78B8E2179B55400A5216A /* ImageScrollView.swift */; }; @@ -438,6 +441,9 @@ 6E216A632112F58700ED21BD /* NamingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingViewController.swift; sourceTree = ""; }; 6E37F48A2188B27D00CF16CA /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDisplayViewController.swift; sourceTree = ""; }; + 6E586CFB2199A72600F680C4 /* OpenInAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAction.swift; sourceTree = ""; }; + 6E586CFD2199A75900F680C4 /* MoveAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveAction.swift; sourceTree = ""; }; + 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAction.swift; sourceTree = ""; }; 6E83C77D20A32C1B0066EC23 /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LAContext+Extension.swift"; sourceTree = ""; }; 6EA78B8E2179B55400A5216A /* ImageScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; @@ -705,6 +711,7 @@ 236735A421217C2300E5834A /* Actions */ = { isa = PBXGroup; children = ( + 6E586CF52199A70100F680C4 /* Actions+Extensions */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, 236735A521217C3500E5834A /* MoreViewController.swift */, 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */, @@ -828,6 +835,16 @@ name = Pods; sourceTree = ""; }; + 6E586CF52199A70100F680C4 /* Actions+Extensions */ = { + isa = PBXGroup; + children = ( + 6E586CFB2199A72600F680C4 /* OpenInAction.swift */, + 6E586CFD2199A75900F680C4 /* MoveAction.swift */, + 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */, + ); + path = "Actions+Extensions"; + sourceTree = ""; + }; DC1B26FD209CF0D2004715E1 /* Issues Animators */ = { isa = PBXGroup; children = ( @@ -1593,6 +1610,7 @@ 4C464BF42187AF1500D30602 /* PDFSearchTableViewCell.swift in Sources */, DC1B2709209CF0D3004715E1 /* CertificateViewController.swift in Sources */, DC248C67213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift in Sources */, + 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */, DC136582208223F000FC0F60 /* OCBookmark+Extension.swift in Sources */, 23D77FCD212BFBD100DE76F1 /* NamingViewController.swift in Sources */, 23FA23E620BFD3D8009A6D73 /* SortBar.swift in Sources */, @@ -1657,8 +1675,10 @@ 23EC77592137F3DD0032D4E6 /* DisplayExtension.swift in Sources */, DC321261207EB01B00DB171D /* ThemeImage.swift in Sources */, DC7DBA54207FA80C00E7337D /* TVGImage.swift in Sources */, + 6E586CFE2199A75900F680C4 /* MoveAction.swift in Sources */, DC4FEAE7209E3A7700D4476B /* OCConnectionIssue+Extension.swift in Sources */, DC434D1320D7A8F100740056 /* UIAlertController+OCConnectionIssue.swift in Sources */, + 6E586D002199A78E00F680C4 /* DeleteAction.swift in Sources */, DC3317CE2084966700E36C8F /* ThemeTableViewCell.swift in Sources */, 6E83C77E20A32C1B0066EC23 /* SettingsSection.swift in Sources */, DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 0272e61fd..154c0a027 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -53,6 +53,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(WebViewDisplayViewController.displayExtension) OCExtensionManager.shared.addExtension(PDFViewerViewController.displayExtension) OCExtensionManager.shared.addExtension(ImageDisplayViewController.displayExtension) + OCExtensionManager.shared.addExtension(OpenInAction.actionExtension) + OCExtensionManager.shared.addExtension(DeleteAction.actionExtension) + OCExtensionManager.shared.addExtension(MoveAction.actionExtension) Theme.shared.activeCollection = ThemeCollection(with: ThemeStyle.preferredStyle) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index fcfeb2aba..ae65e64e9 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -9,234 +9,211 @@ import UIKit import ownCloudSDK -typealias ActionCompletion = ((_ item: OCItem, _ core: OCCore, _ vcToPresent: UIViewController) -> Void)? - -enum ActionType { +enum ActionCategory { + case normal case destructive - case regular + case informal + case edit + case save } -struct Action { - var name: String - var type: ActionType - var completion: ActionCompletion +enum ActionPosition : Int { + case none = -1 - init(with name: String, completion: ActionCompletion, type: ActionType) { - self.name = name - self.completion = completion - self.type = type + case first = 100 + case beforeMiddle = 200 + case middle = 300 + case afterMiddle = 400 + case last = 500 + static func between(_ position1: ActionPosition, and position2: ActionPosition) -> ActionPosition { + return ActionPosition(rawValue: ((position1.rawValue + position2.rawValue)/2))! } -} -class ActionsMoreViewController: NSObject { - - weak var vcToPresentIn: UIViewController? - var moreViewController: UIViewController? - var core: OCCore - var item: OCItem + func shift(by offset: Int) -> ActionPosition { + return ActionPosition(rawValue: self.rawValue + offset)! + } +} - var interactionController: UIDocumentInteractionController? +enum Result { + case success(T) + case failure(Error) +} - init (item: OCItem, core: OCCore, into viewController: UIViewController) { - self.vcToPresentIn = viewController - self.core = core - self.item = item - super.init() - } +typealias ActionCompletionHandler = ((Result) -> Void) +typealias ActionProgressHandler = ((Progress) -> Void) +typealias ActionBeforeRunHandler = () -> Void - func presentActionsCard(with actions: [Action], completion: () -> Void) { - self.moreViewController = actionsViewController(with: actions, for: item, core: core) - vcToPresentIn?.present(asCard: moreViewController!, animated: true) - } +extension OCExtensionType { + static let action: OCExtensionType = OCExtensionType("app.action") +} - func actionsViewController(with actions: [Action], for item: OCItem, core: OCCore) -> MoreViewController { +extension OCExtensionLocationIdentifier { + static let tableRow: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("tableRow") //!< Present as table row action + static let moreItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreItem") //!< Present in "more" card view for a single item + static let moreFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreFolder") //!< Present in "more" options for a whole folder + static let toolbar: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("toolbar") //!< Present in a toolbar +} - let header = MoreViewHeader(for: item, with: core) - let tableViewController = MoreStaticTableViewController(style: .grouped) - let moreViewController: MoreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) +class ActionExtension: OCExtension { + // MARK: - Custom Instance Properties. + var name: String + var category: ActionCategory - var rows: [StaticTableViewRow] = [] + // MARK: - Init & Deinit + init(name: String, category: ActionCategory = .normal, identifier: OCExtensionIdentifier, locations: [OCExtensionLocationIdentifier]?, features: [String : Any]?, objectProvider: OCExtensionObjectProvider?, customMatcher: OCExtensionCustomContextMatcher?) { - for action in actions { + self.name = name + self.category = category - var style: StaticTableViewRowButtonStyle - switch action.type { - case .destructive: - style = .destructive - default: - style = .plainNonOpaque - } + super.init(identifier: identifier, type: .action, locations: locations, features: features, objectProvider: objectProvider, customMatcher: customMatcher) + } +} - let row: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { (_, _) in - moreViewController.dismiss(animated: true, completion: { - action.completion?(item, core, self.vcToPresentIn!) - }) - }, title: action.name, style: style) +class ActionContext: OCExtensionContext { + // MARK: - Custom Instance Properties. + weak var viewController: UIViewController? + weak var core: OCCore? + weak var query: OCQuery? + var items: [OCItem] - rows.append(row) - } + // MARK: - Init & Deinit. + init(viewController: UIViewController, core: OCCore, query: OCQuery? = nil, items: [OCItem], location: OCExtensionLocation, requirements: [String : Any]? = nil, preferences: [String : Any]? = nil) { + self.items = items - let title = NSAttributedString(string: "Actions".localized, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + super.init() - let section = MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: rows) + self.viewController = viewController + self.core = core + self.location = location - tableViewController.addSection(section) - return moreViewController + self.query = query + self.requirements = requirements + self.preferences = preferences } +} - func openIn(completion: (() -> Void)? = nil) -> Action { - let action = Action(with: "Open in".localized, completion: { (item, core, vcToPresentIn) in - let controller = DownloadFileProgressHUDViewController() - controller.present(on: vcToPresentIn) - - if let downloadProgress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in - if error != nil { - Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") - } else { - controller.dismiss(animated: true, completion: { - self.interactionController = UIDocumentInteractionController(url: file!.url) - self.interactionController?.presentOptionsMenu(from: .zero, in: vcToPresentIn.view, animated: true) - }) - } - completion?() - }) { - controller.attach(progress: downloadProgress) - } else { - let alert = UIAlertController(with: "No Network connection", message: "No network connection") - vcToPresentIn.present(alert, animated: true) +class Action : NSObject { + // MARK: - Extension metadata + class var identifier : OCExtensionIdentifier? { return nil } + class var category : ActionCategory? { return .normal } + class var name : String? { return nil } + class var locations : [OCExtensionLocationIdentifier]? { return nil } + class var features : [String : Any]? { return nil } + + // MARK: - Extension creation + class var actionExtension : ActionExtension { + let objectProvider : OCExtensionObjectProvider = { (_ rawExtension, _ context, _ error) -> Any? in + if let actionExtension = rawExtension as? ActionExtension, + let actionContext = context as? ActionContext { + return self.init(for: actionExtension, with: actionContext) } - }, type: .regular) - return action - } - func duplicate(completion: (() -> Void)? = nil) -> Action { - let action = Action(with: "Duplicate", completion: { (item, core, viewcontroller) in + return nil + } + + let customMatcher : OCExtensionCustomContextMatcher = { (context, priority) -> OCExtensionPriority in + if let actionContext = context as? ActionContext, + self.applicablePosition(forContext: actionContext) == .none { + // Exclude actions whose applicablePosition returns .none + return .noMatch + } - guard let viewController = viewcontroller as? ClientQueryViewController else { - return + if let actionContext = context as? ActionContext { + let priority = OCExtensionPriority(rawValue: priority.rawValue + UInt(self.applicablePosition(forContext:actionContext).rawValue))! + print("LOG ---> priority = \(priority.rawValue) for extension \(context.location?.identifier?.rawValue)") + return priority } - var name: String = "\(item.name!) copy" + // Additional filtering (f.ex. via OCClassSettings, Settings) goes here + return priority + } - if item.type != .collection { - let itemName = item.nameWithoutExtension() - var fileExtension = item.fileExtension() + return ActionExtension(name: name!, category: category!, identifier: identifier!, locations: locations, features: features, objectProvider: objectProvider, customMatcher: customMatcher) + } - if fileExtension != "" { - fileExtension = ".\(fileExtension)" - } + // MARK: - Extension matching + class func applicablePosition(forContext: ActionContext) -> ActionPosition { + return .middle + } - name = "\(itemName) copy\(fileExtension)" - } + // MARK: - Finding actions + class func sortedApplicableActions(for context: ActionContext) -> [Action] { + var sortedActions : [Action] = [] - if let progress = core.copy(item, to: viewController.query.rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in - if error != nil { - Log.log("Error \(String(describing: error)) deleting \(String(describing: item?.path))") + if let matches = try? OCExtensionManager.shared.provideExtensions(for: context) { + for match in matches { + if let action = match.extension.provideObject(for: context) as? Action { + sortedActions.append(action) } - completion?() - }) { - viewController.progressSummarizer?.startTracking(progress: progress) } + } - }, type: .regular) + sortedActions.sort { (action1, action2) -> Bool in + return action1.position.rawValue < action2.position.rawValue + } - return action + return sortedActions } - func move(completion: (() -> Void)? = nil) -> Action { - let action = Action(with: "Move".localized, completion: { (item, core, viewController) in + // MARK: - Action metadata + var context : ActionContext + var actionExtension: ActionExtension + var core : OCCore - guard let viewController = viewController as? ClientQueryViewController else { - return - } + // MARK: - Action creation + required init(for actionExtension: ActionExtension, with context: ActionContext) { + self.actionExtension = actionExtension + self.context = context + self.core = context.core! - let directoryPickerVC = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in + super.init() + } - if let progress = core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") - } - completion?() - }) { - viewController.progressSummarizer?.startTracking(progress: progress) - } - }) + // MARK: - Execution metadata + var progressHandler : ActionProgressHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired + var completionHandler : ActionCompletionHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired + var beforeRunHandler: ActionBeforeRunHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired - let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerVC) - viewController.navigationController?.present(pickerNavigationController, animated: true) - }, type: .regular) + // MARK: - Action implementation + func run() { + if beforeRunHandler != nil { + beforeRunHandler!() + } - return action + if completionHandler != nil { + completionHandler!(Result.success(true)) + } } - func delete(completion: (() -> Void)? = nil) -> Action { - let action = Action(with: "Delete".localized, completion: { (item, core, viewController) in - let alertController = UIAlertController( - with: item.name!, - message: "Are you sure you want to delete this item from the server?".localized, - destructiveLabel: "Delete".localized, - preferredStyle: UIDevice.current.isIpad() ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet, - destructiveAction: { - if let progress = core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") - } - completion?() - }) { - if let viewController = viewController as? ClientQueryViewController { - viewController.progressSummarizer?.startTracking(progress: progress) - } - } - }) - - viewController.present(alertController, animated: true) - }, type: .destructive) - - return action + // MARK: - Action UI elements + func provideStaticRow() -> StaticTableViewRow? { + return StaticTableViewRow(buttonWithAction: { (_ row, _ sender) in + self.run() + }, title: actionExtension.name, style: actionExtension.category == .destructive ? .destructive : .plain, identifier: actionExtension.identifier.rawValue) } - func rename(completion: (() -> Void)? = nil) -> Action { - let action = Action(with: "Rename".localized, completion: { (item, core, viewController) in - guard let viewController = viewController as? ClientQueryViewController else { - return - } - - let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in - if name.contains("/") || name.contains("\\") { - return (false, "File name cannot contain / or \\") - } else { - return (true, nil) - } - }, completion: { newName, _ in - - guard newName != nil else { - return - } - - if let progress = core.move(item, to: viewController.query.rootItem, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") - } - completion?() - }) { - viewController.progressSummarizer?.startTracking(progress: progress) - } - }) + func provideContextualAction() -> UIContextualAction? { + return UIContextualAction(style: actionExtension.category == .destructive ? .destructive : .normal, title: self.actionExtension.name, handler: { (_ action, _ view, uiCompletionHandler) in + uiCompletionHandler(true) + self.run() + }) + } - renameViewController.navigationItem.title = "Rename".localized + // MARK: - Action metadata + func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + return nil + } - let navigationController = ThemeNavigationController(rootViewController: renameViewController) - navigationController.modalPresentationStyle = .overFullScreen + var icon : UIImage? { + if let locationIdentifier = context.location?.identifier { + return self.iconForLocation(locationIdentifier) + } - viewController.present(navigationController, animated: true) - }, type: .regular) - return action + return nil } -} -extension ActionsMoreViewController: UIDocumentInteractionControllerDelegate { - func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) { - self.interactionController = nil + var position : ActionPosition { + return type(of: self).applicablePosition(forContext: context) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift new file mode 100644 index 000000000..e0aeefbab --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -0,0 +1,54 @@ +// +// DeleteAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 12/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import ownCloudSDK + +class DeleteAction : Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.delete") } + override class var category : ActionCategory? { return .destructive } + override class var name : String? { return "Delete".localized } + + // MARK: - Extension matching + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + // Examine items in context + return .last + } + + // MARK: - Action implementation + override func run() { + guard context.items.count > 0, let viewController = context.viewController else { + completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + return + } + + beforeRunHandler?() + + let item = context.items[0] + + let alertController = UIAlertController( + with: item.name!, + message: "Are you sure you want to delete this item from the server?".localized, + destructiveLabel: "Delete".localized, + preferredStyle: UIDevice.current.isIpad() ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet, + destructiveAction: { + if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") + self.completionHandler?(Result.failure(error!)) + } else { + self.completionHandler?(Result.success(true)) + } + }) { + self.progressHandler?(progress) + } + }) + + viewController.present(alertController, animated: true) + + } +} diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift new file mode 100644 index 000000000..85da32b12 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -0,0 +1,49 @@ +// +// MoveAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 12/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import ownCloudSDK + +class MoveAction : Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.move") } + override class var category : ActionCategory? { return .normal } + override class var name : String? { return "Move".localized } + + // MARK: - Extension matching + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + // Examine items in context + return .middle + } + + // MARK: - Action implementation + override func run() { + guard context.items.count > 0, let viewController = context.viewController else { + completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + return + } + + beforeRunHandler?() + + let item = context.items[0] + + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in + if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + self.completionHandler?(Result.failure(error!)) + } else { + self.completionHandler?(Result.success(true)) + } + + }) { + self.progressHandler?(progress) + } + }) + + let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerViewController) + viewController.navigationController?.present(pickerNavigationController, animated: true) + } +} diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift new file mode 100644 index 000000000..ff5cb8798 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -0,0 +1,66 @@ +// +// OpenInAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 12/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import ownCloudSDK + +class OpenInAction: Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.openin") } + override class var category : ActionCategory? { return .normal } + override class var name : String { return "Open in".localized } + + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + return .first + } + + private var interactionController: UIDocumentInteractionController? + + override func run() { + guard context.items.count > 0, let viewController = context.viewController else { + completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + return + } + + beforeRunHandler?() + + let item = context.items[0] + + let controller = DownloadFileProgressHUDViewController() + + OnMainThread { + controller.present(on: viewController) { + if let progress = self.core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in + if error != nil { + Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") + self.completionHandler?(Result.failure(error!)) + } else { + + controller.dismiss(animated: true, completion: { + self.completionHandler?(Result.success(file!)) + self.interactionController = UIDocumentInteractionController(url: file!.url) + self.interactionController?.delegate = self + OnMainThread { + self.interactionController?.presentOptionsMenu(from: .zero, in: viewController.view, animated: true) + } + }) + } + }) { + OnMainThread { + controller.attach(progress: progress) + } + } + } + } + } +} + +extension OpenInAction: UIDocumentInteractionControllerDelegate { + + func documentInteractionControllerDidDismissOptionsMenu(_ controller: UIDocumentInteractionController) { + self.interactionController = nil + } +} diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 05e0f4970..d03cd08b3 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -839,16 +839,23 @@ extension ClientQueryViewController: ClientItemCellDelegate { // if let item = cell.item { // // let tableViewController = MoreStaticTableViewController(style: .grouped) -// let header = MoreViewHeader(for: item, with: core!) -// let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) +// let header = MoreViewHeader(for: item, with: core) +// let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) // // let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) // -// let deleteRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { (_, _) in -// moreViewController.dismiss(animated: true, completion: { -// self.delete(item) -// }) -// }, title: "Delete".localized, style: .destructive) +// let deleteLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.delete")) +// let deleteActionContext = ActionContext(viewController: self, core: core, items: [item], location: deleteLocation) +// let deleteAction = DeleteAction(for: DeleteAction.actionExtension, with: deleteActionContext) +// +// deleteAction.progressHandler = { [weak self, weak moreViewController] (progress) in +// OnMainThread { +// self?.progressSummarizer?.startTracking(progress: progress) +// moreViewController?.dismiss(animated: true, completion: nil) +// } +// } +// +// let deleteRow: StaticTableViewRow = deleteAction.provideStaticRow()! // // let renameRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in // moreViewController.dismiss(animated: true, completion: { @@ -856,25 +863,38 @@ extension ClientQueryViewController: ClientItemCellDelegate { // }) // }, title: "Rename".localized, style: .plainNonOpaque) // -// let moveRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in -// moreViewController.dismiss(animated: true, completion: { -// self?.move(item) -// }) -// }, title: "Move".localized, style: .plainNonOpaque) +//// let moveRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in +//// moreViewController.dismiss(animated: true, completion: { +//// self?.move(item) +//// }) +//// }, title: "Move".localized, style: .plainNonOpaque) +// +// let moveLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.move")) +// let moveActionContext = ActionContext(viewController: self, core: core, items: [item], location: moveLocation) +// let moveAction = MoveAction(for: MoveAction.actionExtension, with: moveActionContext) +// +// moveAction.progressHandler = { [weak self, weak moreViewController] (progress) in +// OnMainThread { +// self?.progressSummarizer?.startTracking(progress: progress) +// moreViewController?.dismiss(animated: true, completion: nil) +// } +// } +// let moveRow = moveAction.provideStaticRow()! // // var rows = [renameRow, moveRow, deleteRow] // // if item.type == .file { -// let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in -// moreViewController.dismiss(animated: true, completion: { -// if UIDevice.current.isIpad() { +// let openInLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.openin")) +// let openInActionContext = ActionContext(viewController: self, core: core, items: [item], location: openInLocation) +// let openInAction = OpenInAction(for: OpenInAction.actionExtension, with: openInActionContext) // -// self?.openInRow(item, cell: cell) -// } else { -// self?.openInRow(item) -// } -// }) -// }, title: "Open in".localized, style: .plainNonOpaque) +// openInAction.beforeRunHandler = { [weak moreViewController] in +// OnMainThread { +// moreViewController?.dismiss(animated: true, completion: nil) +// } +// } +// +// let openInRow: StaticTableViewRow = openInAction.provideStaticRow()! // // rows.insert(openInRow, at: 0) // } @@ -890,11 +910,58 @@ extension ClientQueryViewController: ClientItemCellDelegate { return } - let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core, into: self) - actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.duplicate(), actionsObject.rename(), actionsObject.move(), actionsObject.delete()]) { - print("LOG ---> presented") + let tableViewController = MoreStaticTableViewController(style: .grouped) + let header = MoreViewHeader(for: item, with: core) + let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) + + let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + + var extensionsMatch: [OCExtensionMatch]? + + let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) + let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) + do { + try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) + } catch { + print("LOG ---> There is no extensions") + } + + if extensionsMatch!.count <= 0 { + print("LOG ---> There is no extensions") + } else { + + let extensions: [OCExtension] = extensionsMatch!.map({return $0.extension}) + let actionExtensions: [ActionExtension] = extensions.compactMap { + return $0 as? ActionExtension + } + let actions: [Action] = actionExtensions.compactMap({ + return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action + }) + + actions.forEach({ + $0.beforeRunHandler = { + moreViewController.dismiss(animated: true) + } + }) + + let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) + + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) + + self.present(asCard: moreViewController, animated: true) } } + +// func moreButtonTapped(cell: ClientItemCell) { +// guard let item = cell.item else { +// return +// } +// +// let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core, into: self) +// actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.duplicate(), actionsObject.rename(), actionsObject.move(), actionsObject.delete()]) { +// print("LOG ---> presented") +// } +// } } extension ClientQueryViewController: UITableViewDropDelegate { diff --git a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift index af3c7623f..29973e163 100644 --- a/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift +++ b/ownCloud/UI Elements/DownloadFileProgressHUDViewController.swift @@ -137,8 +137,8 @@ class DownloadFileProgressHUDViewController: UIViewController { // MARK: - Public API extension DownloadFileProgressHUDViewController { - func present(on viewController: UIViewController) { - viewController.present(self, animated: true) + func present(on viewController: UIViewController, completion: (() -> Void)? = nil) { + viewController.present(self, animated: true, completion: completion) } func attach(progress: Progress) { diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 276519b49..02352291f 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -330,12 +330,12 @@ class DisplayViewController: UIViewController { // tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [openInRow])) // // self.present(asCard: moreViewController, animated: true)let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) - let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) - actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.delete(completion: { - self.parent?.dismiss(animated: true) - })]) { - print("LOG ---> presented") - } +// let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) +// actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.delete(completion: { +// self.parent?.dismiss(animated: true) +// })]) { +// print("LOG ---> presented") +// } } // MARK: - Actions From e0ebbc5b6b65d7d2fe04f6f0528d589019048fce Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Wed, 14 Nov 2018 09:42:38 +0100 Subject: [PATCH 13/34] - Coded some debug logs - Reversed the extensions array because the pction's priority mechanism needs more work. --- ownCloud/Client/ClientQueryViewController.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index d03cd08b3..6115eaafa 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -923,17 +923,19 @@ extension ClientQueryViewController: ClientItemCellDelegate { do { try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) } catch { - print("LOG ---> There is no extensions") + Log.debug("There is no extensions for the actions required") + return } if extensionsMatch!.count <= 0 { - print("LOG ---> There is no extensions") + Log.debug("There is no extensions for the actions required") } else { - let extensions: [OCExtension] = extensionsMatch!.map({return $0.extension}) + let extensions: [OCExtension] = extensionsMatch!.reversed().map({return $0.extension}) let actionExtensions: [ActionExtension] = extensions.compactMap { return $0 as? ActionExtension } + let actions: [Action] = actionExtensions.compactMap({ return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action }) From 3224f69aaa369cf4d89e7543e8206ba4da8b9105 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Thu, 15 Nov 2018 09:40:41 +0100 Subject: [PATCH 14/34] - Removed unused Result type. - Code new Rename Action. --- ownCloud.xcodeproj/project.pbxproj | 4 ++ ownCloud/AppDelegate.swift | 1 + ownCloud/Client/Actions/Action.swift | 11 +--- .../Actions+Extensions/DeleteAction.swift | 6 +- .../Actions+Extensions/MoveAction.swift | 6 +- .../Actions+Extensions/OpenInAction.swift | 6 +- .../Actions+Extensions/RenameAction.swift | 66 +++++++++++++++++++ .../Client/ClientQueryViewController.swift | 8 ++- 8 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1af788bd8..cadaeb526 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ 6D107AA0B21417432C72755A /* EarlGrey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F3B3E74D4B04F9CAF95C09 /* EarlGrey.swift */; }; 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */; }; 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; + 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */; }; 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */; }; 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFB2199A72600F680C4 /* OpenInAction.swift */; }; 6E586CFE2199A75900F680C4 /* MoveAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFD2199A75900F680C4 /* MoveAction.swift */; }; @@ -440,6 +441,7 @@ 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileProgressHUDViewController.swift; sourceTree = ""; }; 6E216A632112F58700ED21BD /* NamingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingViewController.swift; sourceTree = ""; }; 6E37F48A2188B27D00CF16CA /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; + 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameAction.swift; sourceTree = ""; }; 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDisplayViewController.swift; sourceTree = ""; }; 6E586CFB2199A72600F680C4 /* OpenInAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAction.swift; sourceTree = ""; }; 6E586CFD2199A75900F680C4 /* MoveAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveAction.swift; sourceTree = ""; }; @@ -841,6 +843,7 @@ 6E586CFB2199A72600F680C4 /* OpenInAction.swift */, 6E586CFD2199A75900F680C4 /* MoveAction.swift */, 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */, + 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -1599,6 +1602,7 @@ DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */, 23EC775A2137F3DD0032D4E6 /* DisplayHostViewController.swift in Sources */, 4C464BF62187AF1500D30602 /* PDFTocItem.swift in Sources */, + 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */, DC018F8320A0F56300135198 /* UIView+Animation.swift in Sources */, DC42244A207CAFAA0006A2A6 /* Theme.swift in Sources */, 4C464BF52187AF1500D30602 /* PDFThumbnailsCollectionViewController.swift in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 154c0a027..dc6046099 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -56,6 +56,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(OpenInAction.actionExtension) OCExtensionManager.shared.addExtension(DeleteAction.actionExtension) OCExtensionManager.shared.addExtension(MoveAction.actionExtension) + OCExtensionManager.shared.addExtension(RenameAction.actionExtension) Theme.shared.activeCollection = ThemeCollection(with: ThemeStyle.preferredStyle) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index ae65e64e9..d500b2b1f 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -35,12 +35,7 @@ enum ActionPosition : Int { } } -enum Result { - case success(T) - case failure(Error) -} - -typealias ActionCompletionHandler = ((Result) -> Void) +typealias ActionCompletionHandler = ((Error?) -> Void) typealias ActionProgressHandler = ((Progress) -> Void) typealias ActionBeforeRunHandler = () -> Void @@ -172,7 +167,7 @@ class Action : NSObject { // MARK: - Execution metadata var progressHandler : ActionProgressHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired - var completionHandler : ActionCompletionHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired + var completionHandler : ActionCompletionHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired var beforeRunHandler: ActionBeforeRunHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired // MARK: - Action implementation @@ -182,7 +177,7 @@ class Action : NSObject { } if completionHandler != nil { - completionHandler!(Result.success(true)) + completionHandler!(nil) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index e0aeefbab..71ec9cc77 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -22,7 +22,7 @@ class DeleteAction : Action { // MARK: - Action implementation override func run() { guard context.items.count > 0, let viewController = context.viewController else { - completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + completionHandler?(NSError(ocError: .errorInsufficientParameters)) return } @@ -39,9 +39,9 @@ class DeleteAction : Action { if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") - self.completionHandler?(Result.failure(error!)) + self.completionHandler?(error!) } else { - self.completionHandler?(Result.success(true)) + self.completionHandler?(nil) } }) { self.progressHandler?(progress) diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 85da32b12..b4e9bf1eb 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -22,7 +22,7 @@ class MoveAction : Action { // MARK: - Action implementation override func run() { guard context.items.count > 0, let viewController = context.viewController else { - completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + completionHandler?(NSError(ocError: .errorInsufficientParameters)) return } @@ -33,9 +33,9 @@ class MoveAction : Action { let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in if error != nil { - self.completionHandler?(Result.failure(error!)) + self.completionHandler?(error) } else { - self.completionHandler?(Result.success(true)) + self.completionHandler?(nil) } }) { diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index ff5cb8798..5c6735b4a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -21,7 +21,7 @@ class OpenInAction: Action { override func run() { guard context.items.count > 0, let viewController = context.viewController else { - completionHandler?(Result.failure(NSError(ocError: .errorInsufficientParameters))) + completionHandler?(NSError(ocError: .errorInsufficientParameters)) return } @@ -36,11 +36,11 @@ class OpenInAction: Action { if let progress = self.core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in if error != nil { Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") - self.completionHandler?(Result.failure(error!)) + self.completionHandler?(error!) } else { controller.dismiss(animated: true, completion: { - self.completionHandler?(Result.success(file!)) + self.completionHandler?(nil) self.interactionController = UIDocumentInteractionController(url: file!.url) self.interactionController?.delegate = self OnMainThread { diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift new file mode 100644 index 000000000..c2e35794a --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -0,0 +1,66 @@ +// +// RenameAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 15/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import ownCloudSDK + +class RenameAction : Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.rename") } + override class var category : ActionCategory? { return .normal } + override class var name : String? { return "Rename".localized } + + // MARK: - Extension matching + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + // Examine items in context + return .last + } + + // MARK: - Action implementation + override func run() { + guard context.items.count > 0, let viewController = context.viewController else { + completionHandler?(NSError(ocError: .errorInsufficientParameters)) + return + } + + beforeRunHandler?() + + let item = context.items[0] + let rootItem = context.items[1] + + let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in + if name.contains("/") || name.contains("\\") { + return (false, "File name cannot contain / or \\") + } else { + return (true, nil) + } + }, completion: { newName, _ in + + guard newName != nil else { + return + } + + if let progress = self.core.move(item, to: rootItem, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") + + self.completionHandler?(error!) + } else { + self.completionHandler?(nil) + } + }) { + self.progressHandler?(progress) + } + }) + + renameViewController.navigationItem.title = "Rename".localized + + let navigationController = ThemeNavigationController(rootViewController: renameViewController) + navigationController.modalPresentationStyle = .overFullScreen + + viewController.present(navigationController, animated: true) + } +} diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 6115eaafa..489b77fe4 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -919,7 +919,7 @@ extension ClientQueryViewController: ClientItemCellDelegate { var extensionsMatch: [OCExtensionMatch]? let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) - let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) + let actionContext = ActionContext(viewController: self, core: core, items: [item, query.rootItem], location: actionsLocation) do { try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) } catch { @@ -937,13 +937,17 @@ extension ClientQueryViewController: ClientItemCellDelegate { } let actions: [Action] = actionExtensions.compactMap({ - return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action + return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item, query.rootItem], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action }) actions.forEach({ $0.beforeRunHandler = { moreViewController.dismiss(animated: true) } + + $0.progressHandler = { [weak self] progress in + self?.progressSummarizer?.startTracking(progress: progress) + } }) let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) From 20de88d5e8ff4ec101d8d4f95ec30f9a8d1308b5 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Thu, 15 Nov 2018 10:38:55 +0100 Subject: [PATCH 15/34] - Made de Duplicate action. - Made the actions inside the preview to use the extensions mechanism. --- ownCloud.xcodeproj/project.pbxproj | 4 + ownCloud/AppDelegate.swift | 1 + ownCloud/Client/Actions/Action.swift | 18 +- .../Actions+Extensions/DuplicateAction.swift | 60 +++++ .../Client/ClientQueryViewController.swift | 253 +----------------- .../Viewer/DisplayHostViewController.swift | 11 +- ownCloud/Viewer/DisplayViewController.swift | 103 +++---- 7 files changed, 128 insertions(+), 322 deletions(-) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index cadaeb526..ddbe7027e 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -67,6 +67,7 @@ 6E0A569E218702400056B7B4 /* DownloadFileProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A569D218702400056B7B4 /* DownloadFileProgressHUDViewController.swift */; }; 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */; }; + 6E3A104D219D6F0100F90C96 /* DuplicateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3A104C219D6F0100F90C96 /* DuplicateAction.swift */; }; 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */; }; 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFB2199A72600F680C4 /* OpenInAction.swift */; }; 6E586CFE2199A75900F680C4 /* MoveAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E586CFD2199A75900F680C4 /* MoveAction.swift */; }; @@ -442,6 +443,7 @@ 6E216A632112F58700ED21BD /* NamingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingViewController.swift; sourceTree = ""; }; 6E37F48A2188B27D00CF16CA /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameAction.swift; sourceTree = ""; }; + 6E3A104C219D6F0100F90C96 /* DuplicateAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuplicateAction.swift; sourceTree = ""; }; 6E4F1733217749910049A71B /* ImageDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDisplayViewController.swift; sourceTree = ""; }; 6E586CFB2199A72600F680C4 /* OpenInAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAction.swift; sourceTree = ""; }; 6E586CFD2199A75900F680C4 /* MoveAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveAction.swift; sourceTree = ""; }; @@ -844,6 +846,7 @@ 6E586CFD2199A75900F680C4 /* MoveAction.swift */, 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */, 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */, + 6E3A104C219D6F0100F90C96 /* DuplicateAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -1663,6 +1666,7 @@ 6E37F48B2188B27D00CF16CA /* Action.swift in Sources */, DC3BE0DE2077CC14002A0AC0 /* ClientQueryViewController.swift in Sources */, 23C56538212167BE00BD4B47 /* CardTransitionDelegate.swift in Sources */, + 6E3A104D219D6F0100F90C96 /* DuplicateAction.swift in Sources */, DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */, DC0B37972051681600189B9A /* ThemeButton.swift in Sources */, DCF4F17B20519F9D00189B9A /* StaticTableViewSection.swift in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index dc6046099..67417332a 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -57,6 +57,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(DeleteAction.actionExtension) OCExtensionManager.shared.addExtension(MoveAction.actionExtension) OCExtensionManager.shared.addExtension(RenameAction.actionExtension) + OCExtensionManager.shared.addExtension(DuplicateAction.actionExtension) Theme.shared.activeCollection = ThemeCollection(with: ThemeStyle.preferredStyle) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index d500b2b1f..7da0bceed 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -108,20 +108,24 @@ class Action : NSObject { } let customMatcher : OCExtensionCustomContextMatcher = { (context, priority) -> OCExtensionPriority in - if let actionContext = context as? ActionContext, - self.applicablePosition(forContext: actionContext) == .none { + + guard let actionContext = context as? ActionContext else { + return priority + } + + if self.applicablePosition(forContext: actionContext) == .none { // Exclude actions whose applicablePosition returns .none return .noMatch } - if let actionContext = context as? ActionContext { - let priority = OCExtensionPriority(rawValue: priority.rawValue + UInt(self.applicablePosition(forContext:actionContext).rawValue))! - print("LOG ---> priority = \(priority.rawValue) for extension \(context.location?.identifier?.rawValue)") - return priority + if actionContext.items[0].type == OCItemType.collection, identifier!.rawValue == "com.owncloud.action.openin" { + return .noMatch } - // Additional filtering (f.ex. via OCClassSettings, Settings) goes here + let priority = OCExtensionPriority(rawValue: priority.rawValue + UInt(self.applicablePosition(forContext:actionContext).rawValue))! return priority + + // Additional filtering (f.ex. via OCClassSettings, Settings) goes here } return ActionExtension(name: name!, category: category!, identifier: identifier!, locations: locations, features: features, objectProvider: objectProvider, customMatcher: customMatcher) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift new file mode 100644 index 000000000..c24222ebd --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -0,0 +1,60 @@ +// +// DuplicateAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 15/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +import Foundation + +import ownCloudSDK + +class DuplicateAction : Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.duplicate") } + override class var category : ActionCategory? { return .normal } + override class var name : String? { return "Duplicate".localized } + + // MARK: - Extension matching + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + // Examine items in context + return .last + } + + // MARK: - Action implementation + override func run() { + guard context.items.count > 0 else { + completionHandler?(NSError(ocError: .errorInsufficientParameters)) + return + } + + beforeRunHandler?() + + let item = context.items[0] + let rootItem = context.items[1] + + var name: String = "\(item.name!) copy" + + if item.type != .collection { + let itemName = item.nameWithoutExtension() + var fileExtension = item.fileExtension() + + if fileExtension != "" { + fileExtension = ".\(fileExtension)" + } + + name = "\(itemName) copy\(fileExtension)" + } + + if let progress = self.core.copy(item, to: rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in + if error != nil { + Log.log("Error \(String(describing: error)) duplicating \(String(describing: item?.path))") + self.completionHandler?(error!) + } else { + self.completionHandler?(nil) + } + }) { + progressHandler?(progress) + } + } +} diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 489b77fe4..3b2d161b1 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -277,56 +277,13 @@ class ClientQueryViewController: UITableViewController, Themeable { self.navigationController?.pushViewController(ClientQueryViewController(core: self.core, query: OCQuery(forPath: rowItem.path)), animated: true) case .file: - let itemViewController = DisplayHostViewController(for: rowItem, with: core) + let itemViewController = DisplayHostViewController(for: rowItem, with: core, root: query.rootItem!) self.navigationController?.pushViewController(itemViewController, animated: true) } tableView.deselectRow(at: indexPath, animated: true) } - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let item: OCItem = itemAtIndexPath(indexPath) - - guard item.isPlaceholder == false else { - return UISwipeActionsConfiguration(actions: []) - } - - let deleteContextualAction: UIContextualAction = UIContextualAction(style: .destructive, title: "Delete".localized) { (_, _, actionPerformed) in - self.delete(item, viewDidAppearHandler: { - actionPerformed(false) - }) - } - - let renameContextualAction = UIContextualAction(style: .normal, title: "Rename") { [weak self] (_, _, actionPerformed) in - self?.rename(item, viewDidAppearHandler: { - actionPerformed(false) - }) - } - - let moveContextualAction = UIContextualAction(style: .normal, title: "Move") { (_, _, actionPerformed) in - - let directoryPickerVC = ClientDirectoryPickerViewController(core: self.core, path: "/", completion: { (selectedDirectory) in - if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - }) - - let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerVC) - self.navigationController?.present(pickerNavigationController, animated: true) - - actionPerformed(false) - } - - let actions: [UIContextualAction] = [deleteContextualAction, renameContextualAction, moveContextualAction] - let actionsConfigurator: UISwipeActionsConfiguration = UISwipeActionsConfiguration(actions: actions) - - return actionsConfigurator - } - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { return true } @@ -513,84 +470,6 @@ class ClientQueryViewController: UITableViewController, Themeable { // MARK: - Search var searchController: UISearchController? - // MARK: - Actions - func rename(_ item: OCItem, viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { - let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in - if name.contains("/") || name.contains("\\") { - return (false, "File name cannot contain / or \\") - } else { - return (true, nil) - } - }, completion: { newName, _ in - - guard newName != nil else { - return - } - - if let progress = self.core.move(item, to: self.query.rootItem, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") - - completionHandler?(false) - } else { - completionHandler?(true) - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - }) - - renameViewController.navigationItem.title = "Rename".localized - - let navigationController = ThemeNavigationController(rootViewController: renameViewController) - navigationController.modalPresentationStyle = .overFullScreen - - self.present(navigationController, animated: true, completion: viewDidAppearHandler) - } - - func delete(_ item: OCItem, viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { - let alertController = UIAlertController( - with: item.name!, - message: "Are you sure you want to delete this item from the server?".localized, - destructiveLabel: "Delete".localized, - preferredStyle: UIDevice.current.isIpad() ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet, - destructiveAction: { - if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") - - completionHandler?(false) - } else { - completionHandler?(true) - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - } - ) - - self.present(alertController, animated: true, completion: viewDidAppearHandler) - } - - func move(_ item: OCItem, viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { - let directoryPickerVC = ClientDirectoryPickerViewController(core: self.core, path: "/", completion: { (selectedDirectory) in - - if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") - completionHandler?(false) - } else { - completionHandler?(true) - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - }) - - let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerVC) - self.navigationController?.present(pickerNavigationController, animated: true) - } - func createFolder(viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { let createFolderVC = NamingViewController( with: core, defaultName: "New Folder".localized, stringValidator: { name in if name.contains("/") || name.contains("\\") { @@ -624,51 +503,6 @@ class ClientQueryViewController: UITableViewController, Themeable { self.present(createFolderNavigationVC, animated: true, completion: viewDidAppearHandler) } - func duplicate(_ item: OCItem, viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { - var name: String = "\(item.name!) copy" - - if item.type != .collection { - let itemName = item.nameWithoutExtension() - var fileExtension = item.fileExtension() - - if fileExtension != "" { - fileExtension = ".\(fileExtension)" - } - - name = "\(itemName) copy\(fileExtension)" - } - - if let progress = self.core.copy(item, to: self.query.rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in - if error != nil { - Log.log("Error \(String(describing: error)) duplicating \(String(describing: item?.path))") - - completionHandler?(false) - } else { - completionHandler?(true) - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - - } - - func upload(itemURL: URL, name: String, completionHandler: ClientActionCompletionHandler? = nil) { - if let progress = core.importFileNamed(name, at: query.rootItem, from: itemURL, isSecurityScoped: false, options: nil, placeholderCompletionHandler: nil, resultHandler: { [weak self](error, _ core, _ item, _) in - if error != nil { - Log.debug("Error uploading \(Log.mask(name)) file to \(Log.mask(self?.query.rootItem.path))") - completionHandler?(false) - } else { - Log.debug("Success uploading \(Log.mask(name)) file to \(Log.mask(self?.query.rootItem.path))") - completionHandler?(true) - } - } else { - OnMainThread { - let alert = UIAlertController(with: "No Network connection", message: "No network connection") - self.present(alert, animated: true) - } - } - } - // MARK: - Navigation Bar Actions @objc func uploadsBarButtonPressed(_ sender: UIBarButtonItem) { @@ -835,76 +669,6 @@ extension ClientQueryViewController: UISearchResultsUpdating { // MARK: - ClientItemCell Delegate extension ClientQueryViewController: ClientItemCellDelegate { -// func moreButtonTapped(cell: ClientItemCell) { -// if let item = cell.item { -// -// let tableViewController = MoreStaticTableViewController(style: .grouped) -// let header = MoreViewHeader(for: item, with: core) -// let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) -// -// let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) -// -// let deleteLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.delete")) -// let deleteActionContext = ActionContext(viewController: self, core: core, items: [item], location: deleteLocation) -// let deleteAction = DeleteAction(for: DeleteAction.actionExtension, with: deleteActionContext) -// -// deleteAction.progressHandler = { [weak self, weak moreViewController] (progress) in -// OnMainThread { -// self?.progressSummarizer?.startTracking(progress: progress) -// moreViewController?.dismiss(animated: true, completion: nil) -// } -// } -// -// let deleteRow: StaticTableViewRow = deleteAction.provideStaticRow()! -// -// let renameRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in -// moreViewController.dismiss(animated: true, completion: { -// self?.rename(item) -// }) -// }, title: "Rename".localized, style: .plainNonOpaque) -// -//// let moveRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in -//// moreViewController.dismiss(animated: true, completion: { -//// self?.move(item) -//// }) -//// }, title: "Move".localized, style: .plainNonOpaque) -// -// let moveLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.move")) -// let moveActionContext = ActionContext(viewController: self, core: core, items: [item], location: moveLocation) -// let moveAction = MoveAction(for: MoveAction.actionExtension, with: moveActionContext) -// -// moveAction.progressHandler = { [weak self, weak moreViewController] (progress) in -// OnMainThread { -// self?.progressSummarizer?.startTracking(progress: progress) -// moreViewController?.dismiss(animated: true, completion: nil) -// } -// } -// let moveRow = moveAction.provideStaticRow()! -// -// var rows = [renameRow, moveRow, deleteRow] -// -// if item.type == .file { -// let openInLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: OCExtensionLocationIdentifier(rawValue: "com.owncloud.actions.openin")) -// let openInActionContext = ActionContext(viewController: self, core: core, items: [item], location: openInLocation) -// let openInAction = OpenInAction(for: OpenInAction.actionExtension, with: openInActionContext) -// -// openInAction.beforeRunHandler = { [weak moreViewController] in -// OnMainThread { -// moreViewController?.dismiss(animated: true, completion: nil) -// } -// } -// -// let openInRow: StaticTableViewRow = openInAction.provideStaticRow()! -// -// rows.insert(openInRow, at: 0) -// } -// -// tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: rows)) -// -// self.present(asCard: moreViewController, animated: true) -// } -// } - func moreButtonTapped(cell: ClientItemCell) { guard let item = cell.item else { return @@ -957,17 +721,6 @@ extension ClientQueryViewController: ClientItemCellDelegate { self.present(asCard: moreViewController, animated: true) } } - -// func moreButtonTapped(cell: ClientItemCell) { -// guard let item = cell.item else { -// return -// } -// -// let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core, into: self) -// actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.duplicate(), actionsObject.rename(), actionsObject.move(), actionsObject.delete()]) { -// print("LOG ---> presented") -// } -// } } extension ClientQueryViewController: UITableViewDropDelegate { @@ -1009,7 +762,11 @@ extension ClientQueryViewController: UITableViewDropDelegate { } +<<<<<<< HEAD if let progress = self.core.move(item, to: destinationItem, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in +======= + if let progress = self.core.move(item, to: destinationItem!, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in +>>>>>>> 3aded8b... - Made de Duplicate action. if error != nil { Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") } diff --git a/ownCloud/Viewer/DisplayHostViewController.swift b/ownCloud/Viewer/DisplayHostViewController.swift index 603453a52..a99880fce 100644 --- a/ownCloud/Viewer/DisplayHostViewController.swift +++ b/ownCloud/Viewer/DisplayHostViewController.swift @@ -25,19 +25,22 @@ class DisplayHostViewController: UIViewController { // MARK: - Instance Properties private var itemsToDisplay: [OCItem] = [] private var core: OCCore + private var rootItem: OCItem // MARK: - Init & deinit - init(for item: OCItem, with core: OCCore) { + init(for item: OCItem, with core: OCCore, root: OCItem) { itemsToDisplay.append(item) self.core = core + self.rootItem = root super.init(nibName: nil, bundle: nil) Theme.shared.register(client: self) } - init(for items: [OCItem], with core: OCCore) { + init(for items: [OCItem], with core: OCCore, root: OCItem) { itemsToDisplay = items self.core = core + self.rootItem = root super.init(nibName: nil, bundle: nil) } @@ -61,9 +64,9 @@ class DisplayHostViewController: UIViewController { var configuration: DisplayViewConfiguration if !shouldDownload { - configuration = DisplayViewConfiguration(item: itemToDisplay, core: core, state: .notSupportedMimeType) + configuration = DisplayViewConfiguration(rootItem: rootItem, item: itemToDisplay, core: core, state: .notSupportedMimeType) } else { - configuration = DisplayViewConfiguration(item: itemToDisplay, core: core, state: .hasNetworkConnection) + configuration = DisplayViewConfiguration(rootItem: rootItem, item: itemToDisplay, core: core, state: .hasNetworkConnection) } viewController.configure(configuration) diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 02352291f..6dc13ed5d 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -20,6 +20,7 @@ import UIKit import ownCloudSDK struct DisplayViewConfiguration { + weak var rootItem: OCItem! weak var item: OCItem! weak var core: OCCore! let state: DisplayViewState @@ -45,6 +46,7 @@ class DisplayViewController: UIViewController { private var interactionController: UIDocumentInteractionController? // MARK: - Configuration + weak var rootItem: OCItem! weak var item: OCItem! weak var core: OCCore! { didSet { @@ -312,74 +314,48 @@ class DisplayViewController: UIViewController { } @objc func optionsBarButtonPressed() { -// let tableViewController = MoreStaticTableViewController(style: .grouped) -// let header = MoreViewHeader(for: item, with: core!) -// let moreViewController = MoreViewController(item: item, core: core!, header: header, viewController: tableViewController) -// -// let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) -// -// let openInRow: StaticTableViewRow = StaticTableViewRow(buttonWithAction: { [weak self] (_, _) in -// if UIDevice.current.isIpad() { -// self?.openInRow(self!.item, button: self!.parent!.navigationItem.rightBarButtonItem!) -// } else { -// self?.openInRow(self!.item) -// } -// moreViewController.dismiss(animated: true) -// }, title: "Open in".localized, style: .plainNonOpaque) -// -// tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: [openInRow])) -// - // self.present(asCard: moreViewController, animated: true)let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) -// let actionsObject: ActionsMoreViewController = ActionsMoreViewController(item: item, core: core!, into: self) -// actionsObject.presentActionsCard(with: [actionsObject.openIn(), actionsObject.delete(completion: { -// self.parent?.dismiss(animated: true) -// })]) { -// print("LOG ---> presented") -// } - } - // MARK: - Actions - func openInRow(_ item: OCItem, button: UIBarButtonItem? = nil) { + let tableViewController = MoreStaticTableViewController(style: .grouped) + let header = MoreViewHeader(for: item, with: core) + let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) - if source == nil { - if !core.reachabilityMonitor.available { - OnMainThread { - let alert = UIAlertController(with: "No Network connection", message: "No network connection") - self.present(alert, animated: true) - } - } else { - let controller = DownloadFileProgressHUDViewController() - - if let progress = core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in - if error == nil { - self.source = file!.url - controller.dismiss(animated: true, completion: { - self.openDocumentInteractionController(with: file!.url, button: button) - }) - } else { - controller.dismiss(animated: true) - } - }) { - OnMainThread { - controller.present(on: self) - controller.attach(progress: progress) - } - } - } - } else { - openDocumentInteractionController(with: source, button: button) + let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + + var extensionsMatch: [OCExtensionMatch]? + + let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) + let actionContext = ActionContext(viewController: self, core: core, items: [item, rootItem], location: actionsLocation) + do { + try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) + } catch { + Log.debug("There is no extensions for the actions required") + return } - } - private func openDocumentInteractionController(with source: URL, button: UIBarButtonItem?) { - OnMainThread { - self.interactionController = UIDocumentInteractionController(url: source) - self.interactionController?.delegate = self - if button != nil { - self.interactionController?.presentOptionsMenu(from: button!, animated: true) - } else { - self.interactionController?.presentOptionsMenu(from: .zero, in: self.view, animated: true) + if extensionsMatch!.count <= 0 { + Log.debug("There is no extensions for the actions required") + } else { + + let extensions: [OCExtension] = extensionsMatch!.reversed().map({return $0.extension}) + let actionExtensions: [ActionExtension] = extensions.compactMap { + return $0 as? ActionExtension } + + let actions: [Action] = actionExtensions.compactMap({ + return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item, rootItem], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action + }) + + actions.forEach({ + $0.beforeRunHandler = { + moreViewController.dismiss(animated: true) + } + }) + + let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) + + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) + + self.present(asCard: moreViewController, animated: true) } } } @@ -394,6 +370,7 @@ extension DisplayViewController: UIDocumentInteractionControllerDelegate { // MARK: - Public API extension DisplayViewController { func configure(_ configuration: DisplayViewConfiguration) { + self.rootItem = configuration.rootItem self.core = configuration.core self.item = configuration.item self.state = configuration.state From 28470d469399846ad0bdab0632619a2adde4ba48 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Thu, 15 Nov 2018 13:36:30 +0100 Subject: [PATCH 16/34] - Changes from code review. - Use the correct API for get the actions. --- ownCloud/AppDelegate.swift | 3 ++ ownCloud/Client/Actions/Action.swift | 5 --- .../Actions+Extensions/DuplicateAction.swift | 2 +- .../Actions+Extensions/OpenInAction.swift | 3 ++ .../Actions+Extensions/RenameAction.swift | 2 +- .../Client/ClientQueryViewController.swift | 42 +++++-------------- ownCloud/Viewer/DisplayViewController.swift | 36 ++++------------ 7 files changed, 27 insertions(+), 66 deletions(-) diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 67417332a..b6a4fd6a8 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -50,9 +50,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum + 10) + // Display Extensions OCExtensionManager.shared.addExtension(WebViewDisplayViewController.displayExtension) OCExtensionManager.shared.addExtension(PDFViewerViewController.displayExtension) OCExtensionManager.shared.addExtension(ImageDisplayViewController.displayExtension) + + // Action Extensions OCExtensionManager.shared.addExtension(OpenInAction.actionExtension) OCExtensionManager.shared.addExtension(DeleteAction.actionExtension) OCExtensionManager.shared.addExtension(MoveAction.actionExtension) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 7da0bceed..5bbb47d2a 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -118,11 +118,6 @@ class Action : NSObject { return .noMatch } - if actionContext.items[0].type == OCItemType.collection, identifier!.rawValue == "com.owncloud.action.openin" { - return .noMatch - } - - let priority = OCExtensionPriority(rawValue: priority.rawValue + UInt(self.applicablePosition(forContext:actionContext).rawValue))! return priority // Additional filtering (f.ex. via OCClassSettings, Settings) goes here diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index c24222ebd..52bc0118b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -18,7 +18,7 @@ class DuplicateAction : Action { // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { // Examine items in context - return .last + return .middle } // MARK: - Action implementation diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 5c6735b4a..7d4beb081 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -14,6 +14,9 @@ class OpenInAction: Action { override class var name : String { return "Open in".localized } override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + if forContext.items[0].type == .collection { + return .none + } return .first } diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index c2e35794a..41448477f 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -16,7 +16,7 @@ class RenameAction : Action { // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { // Examine items in context - return .last + return .middle } // MARK: - Action implementation diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 3b2d161b1..a0a78cb24 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -680,46 +680,26 @@ extension ClientQueryViewController: ClientItemCellDelegate { let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - var extensionsMatch: [OCExtensionMatch]? - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) let actionContext = ActionContext(viewController: self, core: core, items: [item, query.rootItem], location: actionsLocation) - do { - try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) - } catch { - Log.debug("There is no extensions for the actions required") - return - } - if extensionsMatch!.count <= 0 { - Log.debug("There is no extensions for the actions required") - } else { + let actions = Action.sortedApplicableActions(for: actionContext) - let extensions: [OCExtension] = extensionsMatch!.reversed().map({return $0.extension}) - let actionExtensions: [ActionExtension] = extensions.compactMap { - return $0 as? ActionExtension + actions.forEach({ + $0.beforeRunHandler = { + moreViewController.dismiss(animated: true) } - let actions: [Action] = actionExtensions.compactMap({ - return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item, query.rootItem], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action - }) - - actions.forEach({ - $0.beforeRunHandler = { - moreViewController.dismiss(animated: true) - } - - $0.progressHandler = { [weak self] progress in - self?.progressSummarizer?.startTracking(progress: progress) - } - }) + $0.progressHandler = { [weak self] progress in + self?.progressSummarizer?.startTracking(progress: progress) + } + }) - let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) + let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) - self.present(asCard: moreViewController, animated: true) - } + self.present(asCard: moreViewController, animated: true) } } diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 6dc13ed5d..4ad0fd5d1 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -321,42 +321,22 @@ class DisplayViewController: UIViewController { let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - var extensionsMatch: [OCExtensionMatch]? - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) let actionContext = ActionContext(viewController: self, core: core, items: [item, rootItem], location: actionsLocation) - do { - try extensionsMatch = OCExtensionManager.shared.provideExtensions(for: actionContext) - } catch { - Log.debug("There is no extensions for the actions required") - return - } - - if extensionsMatch!.count <= 0 { - Log.debug("There is no extensions for the actions required") - } else { + let actions = Action.sortedApplicableActions(for: actionContext) - let extensions: [OCExtension] = extensionsMatch!.reversed().map({return $0.extension}) - let actionExtensions: [ActionExtension] = extensions.compactMap { - return $0 as? ActionExtension + actions.forEach({ + $0.beforeRunHandler = { + moreViewController.dismiss(animated: true) } + }) - let actions: [Action] = actionExtensions.compactMap({ - return $0.provideObject(for: ActionContext(viewController: self, core: core, items: [item, rootItem], location: OCExtensionLocation(ofType: .action, identifier: nil))) as? Action - }) - - actions.forEach({ - $0.beforeRunHandler = { - moreViewController.dismiss(animated: true) - } - }) + let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) - let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) + self.present(asCard: moreViewController, animated: true) - self.present(asCard: moreViewController, animated: true) - } } } From fbb64422fdf0d881a9438cae65b717751d98ca06 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Fri, 16 Nov 2018 11:00:22 +0100 Subject: [PATCH 17/34] - Changed BeforeRunHandler to its own method called willRun() --- ownCloud/Client/Actions/Action.swift | 16 ++++++++++------ .../Actions+Extensions/DeleteAction.swift | 2 -- .../Actions+Extensions/DuplicateAction.swift | 2 -- .../Actions/Actions+Extensions/MoveAction.swift | 2 -- .../Actions+Extensions/OpenInAction.swift | 2 -- .../Actions+Extensions/RenameAction.swift | 2 -- ownCloud/Client/ClientQueryViewController.swift | 2 +- ownCloud/Viewer/DisplayViewController.swift | 2 +- 8 files changed, 12 insertions(+), 18 deletions(-) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 5bbb47d2a..5dad256bc 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -37,7 +37,7 @@ enum ActionPosition : Int { typealias ActionCompletionHandler = ((Error?) -> Void) typealias ActionProgressHandler = ((Progress) -> Void) -typealias ActionBeforeRunHandler = () -> Void +typealias ActionWillRunHandler = () -> Void extension OCExtensionType { static let action: OCExtensionType = OCExtensionType("app.action") @@ -167,14 +167,16 @@ class Action : NSObject { // MARK: - Execution metadata var progressHandler : ActionProgressHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired var completionHandler : ActionCompletionHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired - var beforeRunHandler: ActionBeforeRunHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired + var actionWillRunHandler: ActionWillRunHandler? // to be filled before calling run(), provideStaticRow(), provideContextualAction(), etc. if desired // MARK: - Action implementation - func run() { - if beforeRunHandler != nil { - beforeRunHandler!() + func willRun() { + if actionWillRunHandler != nil { + actionWillRunHandler!() } + } + func run() { if completionHandler != nil { completionHandler!(nil) } @@ -183,13 +185,15 @@ class Action : NSObject { // MARK: - Action UI elements func provideStaticRow() -> StaticTableViewRow? { return StaticTableViewRow(buttonWithAction: { (_ row, _ sender) in - self.run() + self.willRun() + self.run() }, title: actionExtension.name, style: actionExtension.category == .destructive ? .destructive : .plain, identifier: actionExtension.identifier.rawValue) } func provideContextualAction() -> UIContextualAction? { return UIContextualAction(style: actionExtension.category == .destructive ? .destructive : .normal, title: self.actionExtension.name, handler: { (_ action, _ view, uiCompletionHandler) in uiCompletionHandler(true) + self.willRun() self.run() }) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 71ec9cc77..4da13dc2d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -26,8 +26,6 @@ class DeleteAction : Action { return } - beforeRunHandler?() - let item = context.items[0] let alertController = UIAlertController( diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 52bc0118b..70745bd07 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -28,8 +28,6 @@ class DuplicateAction : Action { return } - beforeRunHandler?() - let item = context.items[0] let rootItem = context.items[1] diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index b4e9bf1eb..c1a250bfe 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -26,8 +26,6 @@ class MoveAction : Action { return } - beforeRunHandler?() - let item = context.items[0] let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 7d4beb081..915ec14b2 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -28,8 +28,6 @@ class OpenInAction: Action { return } - beforeRunHandler?() - let item = context.items[0] let controller = DownloadFileProgressHUDViewController() diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 41448477f..9d0f1bc15 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -26,8 +26,6 @@ class RenameAction : Action { return } - beforeRunHandler?() - let item = context.items[0] let rootItem = context.items[1] diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index a0a78cb24..bb17117cf 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -686,7 +686,7 @@ extension ClientQueryViewController: ClientItemCellDelegate { let actions = Action.sortedApplicableActions(for: actionContext) actions.forEach({ - $0.beforeRunHandler = { + $0.actionWillRunHandler = { moreViewController.dismiss(animated: true) } diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 4ad0fd5d1..b9907cb4c 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -326,7 +326,7 @@ class DisplayViewController: UIViewController { let actions = Action.sortedApplicableActions(for: actionContext) actions.forEach({ - $0.beforeRunHandler = { + $0.actionWillRunHandler = { moreViewController.dismiss(animated: true) } }) From 32438a34086e456b1ebea0fa2ba61d75154436d3 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Fri, 16 Nov 2018 11:14:29 +0100 Subject: [PATCH 18/34] - Use Felix's implementation to be able to get the parent item with a OCItem extension. - Make sure the actions uses all the items when it makes sense. - Removed some of main thread calls. --- .../Actions+Extensions/DeleteAction.swift | 38 +++++++++++++------ .../Actions+Extensions/DuplicateAction.swift | 2 +- .../Actions+Extensions/OpenInAction.swift | 21 +++++----- .../Actions+Extensions/RenameAction.swift | 2 +- .../Client/ClientQueryViewController.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 2 + .../SDK Extensions/OCItem+Extension.swift | 26 +++++++++++++ 7 files changed, 67 insertions(+), 26 deletions(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 4da13dc2d..b5f56c35b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -26,24 +26,40 @@ class DeleteAction : Action { return } - let item = context.items[0] + let items = context.items + + let message: String + if items.count > 1 { + message = "Are you sure you want to delete this items from the server?".localized + } else { + message = "Are you sure you want to delete this item from the server?".localized + } + + let name: String + if items.count > 1 { + name = "Multiple items".localized + } else { + name = items[0].name + } let alertController = UIAlertController( - with: item.name!, - message: "Are you sure you want to delete this item from the server?".localized, + with: name, + message: message, destructiveLabel: "Delete".localized, preferredStyle: UIDevice.current.isIpad() ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet, destructiveAction: { - if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in - if error != nil { - Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") - self.completionHandler?(error!) - } else { - self.completionHandler?(nil) + for item in items { + if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") + self.completionHandler?(error!) + } + }) { + self.progressHandler?(progress) } - }) { - self.progressHandler?(progress) } + + self.completionHandler?(nil) }) viewController.present(alertController, animated: true) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 70745bd07..8f868345f 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -29,7 +29,7 @@ class DuplicateAction : Action { } let item = context.items[0] - let rootItem = context.items[1] + let rootItem = item.parentItem(from: core)! var name: String = "\(item.name!) copy" diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 915ec14b2..c694a0990 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -14,7 +14,7 @@ class OpenInAction: Action { override class var name : String { return "Open in".localized } override class func applicablePosition(forContext: ActionContext) -> ActionPosition { - if forContext.items[0].type == .collection { + if forContext.items.contains(where: {$0.type == .collection}) { return .none } return .first @@ -39,20 +39,17 @@ class OpenInAction: Action { Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") self.completionHandler?(error!) } else { - - controller.dismiss(animated: true, completion: { - self.completionHandler?(nil) - self.interactionController = UIDocumentInteractionController(url: file!.url) - self.interactionController?.delegate = self - OnMainThread { + OnMainThread { + controller.dismiss(animated: true, completion: { + self.completionHandler?(nil) + self.interactionController = UIDocumentInteractionController(url: file!.url) + self.interactionController?.delegate = self self.interactionController?.presentOptionsMenu(from: .zero, in: viewController.view, animated: true) - } - }) + }) + } } }) { - OnMainThread { - controller.attach(progress: progress) - } + controller.attach(progress: progress) } } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 9d0f1bc15..64686c7c1 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -27,7 +27,7 @@ class RenameAction : Action { } let item = context.items[0] - let rootItem = context.items[1] + let rootItem = item.parentItem(from: core)! let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in if name.contains("/") || name.contains("\\") { diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index bb17117cf..cbcd7c130 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -681,7 +681,7 @@ extension ClientQueryViewController: ClientItemCellDelegate { let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) - let actionContext = ActionContext(viewController: self, core: core, items: [item, query.rootItem], location: actionsLocation) + let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) let actions = Action.sortedApplicableActions(for: actionContext) diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index b971005e0..b10afb90f 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -78,6 +78,8 @@ "Folder removed" = "Folder removed"; "This folder no longer exists on the server." = "This folder no longer exists on the server."; "Are you sure you want to delete this item from the server?" = "Are you sure you want to delete this item from the server?"; +"Are you sure you want to delete this items from the server?" = "Are you sure you want to delete this items from the server?"; +"Multiple items" = "Multiple items"; "Deleting '%@'" = "Deleting '%@'"; "Renaming to %@" = "Renaming to %@"; "Upload from your photo library" = "Upload from your photo library"; diff --git a/ownCloud/SDK Extensions/OCItem+Extension.swift b/ownCloud/SDK Extensions/OCItem+Extension.swift index 5d689ab43..a3cf6eff1 100644 --- a/ownCloud/SDK Extensions/OCItem+Extension.swift +++ b/ownCloud/SDK Extensions/OCItem+Extension.swift @@ -245,4 +245,30 @@ extension OCItem { dateFormatter.doesRelativeDateFormatting = true return dateFormatter }() + + func parentItem(from core: OCCore, completionHandler: ((_ error: Error?, _ parentItem: OCItem?) -> Void)? = nil) -> OCItem? { + var parentItem : OCItem? + + if let parentItemIdentifier = self.parentFileID { + var waitGroup : DispatchGroup? + + if completionHandler == nil { + waitGroup = DispatchGroup() + waitGroup?.enter() + } + + core.retrieveItemFromDatabase(forFileID: parentItemIdentifier) { (error, _, item) in + if completionHandler == nil { + parentItem = item + waitGroup?.leave() + } else { + completionHandler?(error, item) + } + } + + waitGroup?.wait() + } + + return parentItem + } } From b8f7dc8728109d42e12412b0e2039f82791b5769 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Fri, 16 Nov 2018 11:23:55 +0100 Subject: [PATCH 19/34] - Coded some left legal headers. --- ownCloud/Client/Actions/Action.swift | 10 ++++++++++ .../Actions/Actions+Extensions/DeleteAction.swift | 10 ++++++++++ .../Actions+Extensions/DuplicateAction.swift | 10 ++++++++++ .../Actions/Actions+Extensions/MoveAction.swift | 10 ++++++++++ .../Actions/Actions+Extensions/OpenInAction.swift | 10 ++++++++++ .../Actions/Actions+Extensions/RenameAction.swift | 13 +++++++++++++ .../ClientDirectoryPickerViewController.swift | 10 ++++++++++ ownCloud/Client/Actions/NamingViewController.swift | 11 +++++++++++ 8 files changed, 84 insertions(+) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 5dad256bc..cc505785c 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import UIKit import ownCloudSDK diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index b5f56c35b..075fc293b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import ownCloudSDK class DeleteAction : Action { diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 8f868345f..57af85940 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import Foundation import ownCloudSDK diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index c1a250bfe..efb38399c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import ownCloudSDK class MoveAction : Action { diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index c694a0990..b03427738 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import ownCloudSDK class OpenInAction: Action { diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 64686c7c1..e5f858f65 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import ownCloudSDK class RenameAction : Action { @@ -15,6 +25,9 @@ class RenameAction : Action { // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + if forContext.items.count > 1 { + return .none + } // Examine items in context return .middle } diff --git a/ownCloud/Client/Actions/ClientDirectoryPickerViewController.swift b/ownCloud/Client/Actions/ClientDirectoryPickerViewController.swift index a66ce0166..43ed5c7f0 100644 --- a/ownCloud/Client/Actions/ClientDirectoryPickerViewController.swift +++ b/ownCloud/Client/Actions/ClientDirectoryPickerViewController.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import UIKit import ownCloudSDK diff --git a/ownCloud/Client/Actions/NamingViewController.swift b/ownCloud/Client/Actions/NamingViewController.swift index 2f1f88868..104d3a22b 100644 --- a/ownCloud/Client/Actions/NamingViewController.swift +++ b/ownCloud/Client/Actions/NamingViewController.swift @@ -6,6 +6,17 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // + +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + import UIKit import ownCloudSDK From e192f85b5e380b7bcd55b6d8f150c7b0ca2e4268 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 19 Nov 2018 08:31:11 +0100 Subject: [PATCH 20/34] - Decoupled the creation of the actions card view controller. - Removed some unused code. --- ownCloud/Client/Actions/Action.swift | 33 +++++++++++++++++++ .../Actions+Extensions/OpenInAction.swift | 31 ++++++++--------- .../Client/ClientQueryViewController.swift | 22 ++----------- ownCloud/Viewer/DisplayViewController.swift | 29 ++-------------- 4 files changed, 52 insertions(+), 63 deletions(-) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index cc505785c..9db5d1def 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -160,6 +160,34 @@ class Action : NSObject { return sortedActions } + // MARK: - Provide Card view controller + class func cardViewController(for item: OCItem, with context: ActionContext, progressHandler: ((Progress) -> Void)? = nil, completionHandler: ((Error?)-> Void)? = nil) -> UIViewController { + + let tableViewController = MoreStaticTableViewController(style: .grouped) + let header = MoreViewHeader(for: item, with: context.core!) + let moreViewController = MoreViewController(item: item, core: context.core!, header: header, viewController: tableViewController) + + let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) + + let actions = Action.sortedApplicableActions(for: context) + + actions.forEach({ + $0.actionWillRunHandler = { + moreViewController.dismiss(animated: true) + } + + $0.progressHandler = progressHandler + + $0.completionHandler = completionHandler + }) + + let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) + + tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) + + return moreViewController + } + // MARK: - Action metadata var context : ActionContext var actionExtension: ActionExtension @@ -181,6 +209,11 @@ class Action : NSObject { // MARK: - Action implementation func willRun() { + + if Thread.isMainThread == false { + Log.warning("The Run method of the action \(Action.identifier!.rawValue) is not called inside the main thread") + } + if actionWillRunHandler != nil { actionWillRunHandler!() } diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index b03427738..5952b343f 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -42,25 +42,22 @@ class OpenInAction: Action { let controller = DownloadFileProgressHUDViewController() - OnMainThread { - controller.present(on: viewController) { - if let progress = self.core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in - if error != nil { - Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") - self.completionHandler?(error!) - } else { - OnMainThread { - controller.dismiss(animated: true, completion: { - self.completionHandler?(nil) - self.interactionController = UIDocumentInteractionController(url: file!.url) - self.interactionController?.delegate = self - self.interactionController?.presentOptionsMenu(from: .zero, in: viewController.view, animated: true) - }) - } + controller.present(on: viewController) { + if let progress = self.core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in + if error != nil { + Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") + self.completionHandler?(error!) + } else { + OnMainThread { + controller.dismiss(animated: true, completion: { + self.interactionController = UIDocumentInteractionController(url: file!.url) + self.interactionController?.delegate = self + self.interactionController?.presentOptionsMenu(from: .zero, in: viewController.view, animated: true) + }) } - }) { - controller.attach(progress: progress) } + }) { + controller.attach(progress: progress) } } } diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index cbcd7c130..00de39b16 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -674,31 +674,13 @@ extension ClientQueryViewController: ClientItemCellDelegate { return } - let tableViewController = MoreStaticTableViewController(style: .grouped) - let header = MoreViewHeader(for: item, with: core) - let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) - - let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) - let actions = Action.sortedApplicableActions(for: actionContext) - - actions.forEach({ - $0.actionWillRunHandler = { - moreViewController.dismiss(animated: true) - } - - $0.progressHandler = { [weak self] progress in - self?.progressSummarizer?.startTracking(progress: progress) - } + let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: { [weak self] progress in + self?.progressSummarizer?.startTracking(progress: progress) }) - let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) - - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) - self.present(asCard: moreViewController, animated: true) } } diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index b9907cb4c..09e68a005 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -43,8 +43,6 @@ class DisplayViewController: UIViewController { private let iconImageSize: CGSize = CGSize(width: 200.0, height: 200.0) - private var interactionController: UIDocumentInteractionController? - // MARK: - Configuration weak var rootItem: OCItem! weak var item: OCItem! @@ -315,35 +313,14 @@ class DisplayViewController: UIViewController { @objc func optionsBarButtonPressed() { - let tableViewController = MoreStaticTableViewController(style: .grouped) - let header = MoreViewHeader(for: item, with: core) - let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) - - let title = NSAttributedString(string: "Actions", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) - let actionContext = ActionContext(viewController: self, core: core, items: [item, rootItem], location: actionsLocation) - let actions = Action.sortedApplicableActions(for: actionContext) + let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) - actions.forEach({ - $0.actionWillRunHandler = { - moreViewController.dismiss(animated: true) - } + let moreViewController = Action.cardViewController(for: item, with: actionContext, completionHandler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) }) - let actionsRows: [StaticTableViewRow] = actions.compactMap({return $0.provideStaticRow()}) - - tableViewController.addSection(MoreStaticTableViewSection(headerAttributedTitle: title, identifier: "actions-section", rows: actionsRows)) - self.present(asCard: moreViewController, animated: true) - - } -} - -// MARK: - UIDocumentInteractionControllerDelegate -extension DisplayViewController: UIDocumentInteractionControllerDelegate { - func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) { - self.interactionController = nil } } From 4b98aabf3687e195688c7f3ea4b389e768c467de Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Mon, 19 Nov 2018 08:47:26 +0100 Subject: [PATCH 21/34] - Coded the move action for multiple items. - Removed empty white line to fix the linter warning. --- .../Actions+Extensions/MoveAction.swift | 23 +++++++++++-------- .../Client/Actions/NamingViewController.swift | 1 - 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index efb38399c..470ca7f21 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -36,19 +36,22 @@ class MoveAction : Action { return } - let item = context.items[0] + let items = context.items let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", completion: { (selectedDirectory) in - if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - self.completionHandler?(error) - } else { - self.completionHandler?(nil) - } - }) { - self.progressHandler?(progress) - } + items.forEach({ (item) in + if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + self.completionHandler?(error) + } else { + self.completionHandler?(nil) + } + + }) { + self.progressHandler?(progress) + } + }) }) let pickerNavigationController = ThemeNavigationController(rootViewController: directoryPickerViewController) diff --git a/ownCloud/Client/Actions/NamingViewController.swift b/ownCloud/Client/Actions/NamingViewController.swift index 104d3a22b..ec5b54fa9 100644 --- a/ownCloud/Client/Actions/NamingViewController.swift +++ b/ownCloud/Client/Actions/NamingViewController.swift @@ -6,7 +6,6 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // - /* * Copyright (C) 2018, ownCloud GmbH. * From 13163d5595404186cd2a1992ee95edf35c752c0b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 20 Nov 2018 08:19:18 +0100 Subject: [PATCH 22/34] - Check to return .none when there is more than one items in the context for OpenInAction Co-Authored-By: pablocarmu --- ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 5952b343f..7033b7327 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -27,6 +27,9 @@ class OpenInAction: Action { if forContext.items.contains(where: {$0.type == .collection}) { return .none } + if forContext.items.count > 1 { + return .none + } return .first } From c652187bb4dc144f3f8086a1e7f4752ce1cd5b32 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 20 Nov 2018 08:50:06 +0100 Subject: [PATCH 23/34] Code location identifier for actions ClientQueryViewController Co-Authored-By: pablocarmu --- ownCloud/Client/ClientQueryViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 00de39b16..d2544e005 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -674,7 +674,7 @@ extension ClientQueryViewController: ClientItemCellDelegate { return } - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreItem) let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: { [weak self] progress in From 3b6abd38c765ae6c5631ae620eaabae538932fec Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 20 Nov 2018 08:50:25 +0100 Subject: [PATCH 24/34] Update ownCloud/Viewer/DisplayViewController.swift Co-Authored-By: pablocarmu --- ownCloud/Viewer/DisplayViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 09e68a005..245c5f2d2 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -313,7 +313,7 @@ class DisplayViewController: UIViewController { @objc func optionsBarButtonPressed() { - let actionsLocation = OCExtensionLocation(ofType: OCExtensionType.action, identifier: nil) + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreItem) let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) let moreViewController = Action.cardViewController(for: item, with: actionContext, completionHandler: { [weak self] _ in From fca4f2b19470e59cf402b48fcb35b845a669f3a6 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 08:17:53 +0100 Subject: [PATCH 25/34] - Fix little typo this -> these in the confirmation message to delete items. --- ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift | 2 +- ownCloud/Resources/en.lproj/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 075fc293b..df3cd1daf 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -40,7 +40,7 @@ class DeleteAction : Action { let message: String if items.count > 1 { - message = "Are you sure you want to delete this items from the server?".localized + message = "Are you sure you want to delete these items from the server?".localized } else { message = "Are you sure you want to delete this item from the server?".localized } diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index b10afb90f..b6c911eb8 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -78,7 +78,7 @@ "Folder removed" = "Folder removed"; "This folder no longer exists on the server." = "This folder no longer exists on the server."; "Are you sure you want to delete this item from the server?" = "Are you sure you want to delete this item from the server?"; -"Are you sure you want to delete this items from the server?" = "Are you sure you want to delete this items from the server?"; +"Are you sure you want to delete these items from the server?" = "Are you sure you want to delete these items from the server?"; "Multiple items" = "Multiple items"; "Deleting '%@'" = "Deleting '%@'"; "Renaming to %@" = "Renaming to %@"; From a76dfcab922b7627d290a91b3d441a21d5646177 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 08:22:36 +0100 Subject: [PATCH 26/34] - Dismiss the DocumentInteractionController when there is an error downloading the item. --- ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 7033b7327..ed2bfb099 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -49,7 +49,9 @@ class OpenInAction: Action { if let progress = self.core.downloadItem(item, options: nil, resultHandler: { (error, _, _, file) in if error != nil { Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") - self.completionHandler?(error!) + controller.dismiss(animated: true, completion: { + self.completionHandler?(error!) + }) } else { OnMainThread { controller.dismiss(animated: true, completion: { From 32c464042c84cc1d7c916a0d772067a69300ef6f Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 08:26:43 +0100 Subject: [PATCH 27/34] - Coded a security check for when the root item is now found. --- .../Actions/Actions+Extensions/DuplicateAction.swift | 9 +++++++-- .../Client/Actions/Actions+Extensions/RenameAction.swift | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 57af85940..688b7faf1 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -39,7 +39,12 @@ class DuplicateAction : Action { } let item = context.items[0] - let rootItem = item.parentItem(from: core)! + let rootItem = item.parentItem(from: core) + + guard rootItem != nil else { + completionHandler?(NSError(ocError: OCError.errorItemNotFound)) + return + } var name: String = "\(item.name!) copy" @@ -54,7 +59,7 @@ class DuplicateAction : Action { name = "\(itemName) copy\(fileExtension)" } - if let progress = self.core.copy(item, to: rootItem, withName: name, options: nil, resultHandler: { (error, _, item, _) in + if let progress = self.core.copy(item, to: rootItem!, withName: name, options: nil, resultHandler: { (error, _, item, _) in if error != nil { Log.log("Error \(String(describing: error)) duplicating \(String(describing: item?.path))") self.completionHandler?(error!) diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index e5f858f65..e03fb744d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -40,7 +40,12 @@ class RenameAction : Action { } let item = context.items[0] - let rootItem = item.parentItem(from: core)! + let rootItem = item.parentItem(from: core) + + guard rootItem != nil else { + self.completionHandler?(NSError(ocError: OCError.errorItemNotFound)) + return + } let renameViewController = NamingViewController(with: item, core: self.core, stringValidator: { name in if name.contains("/") || name.contains("\\") { @@ -54,7 +59,7 @@ class RenameAction : Action { return } - if let progress = self.core.move(item, to: rootItem, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in + if let progress = self.core.move(item, to: rootItem!, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") From 31f367d82b102c0aa30d03797739ec4688053763 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 08:30:05 +0100 Subject: [PATCH 28/34] - Remove left-over extension from ClientQueryViewController. - Removed left-over code from DisplayViewController. --- ownCloud/Client/ClientQueryViewController.swift | 2 -- ownCloud/Viewer/DisplayHostViewController.swift | 4 ++-- ownCloud/Viewer/DisplayViewController.swift | 3 --- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index d2544e005..71986238f 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -49,8 +49,6 @@ class ClientQueryViewController: UITableViewController, Themeable { var initialAppearance : Bool = true var refreshController: UIRefreshControl? - var interactionController: UIDocumentInteractionController? - // MARK: - Init & Deinit public init(core inCore: OCCore, query inQuery: OCQuery) { diff --git a/ownCloud/Viewer/DisplayHostViewController.swift b/ownCloud/Viewer/DisplayHostViewController.swift index a99880fce..7b51551a9 100644 --- a/ownCloud/Viewer/DisplayHostViewController.swift +++ b/ownCloud/Viewer/DisplayHostViewController.swift @@ -64,9 +64,9 @@ class DisplayHostViewController: UIViewController { var configuration: DisplayViewConfiguration if !shouldDownload { - configuration = DisplayViewConfiguration(rootItem: rootItem, item: itemToDisplay, core: core, state: .notSupportedMimeType) + configuration = DisplayViewConfiguration(item: itemToDisplay, core: core, state: .notSupportedMimeType) } else { - configuration = DisplayViewConfiguration(rootItem: rootItem, item: itemToDisplay, core: core, state: .hasNetworkConnection) + configuration = DisplayViewConfiguration(item: itemToDisplay, core: core, state: .hasNetworkConnection) } viewController.configure(configuration) diff --git a/ownCloud/Viewer/DisplayViewController.swift b/ownCloud/Viewer/DisplayViewController.swift index 245c5f2d2..657ace175 100644 --- a/ownCloud/Viewer/DisplayViewController.swift +++ b/ownCloud/Viewer/DisplayViewController.swift @@ -20,7 +20,6 @@ import UIKit import ownCloudSDK struct DisplayViewConfiguration { - weak var rootItem: OCItem! weak var item: OCItem! weak var core: OCCore! let state: DisplayViewState @@ -44,7 +43,6 @@ class DisplayViewController: UIViewController { private let iconImageSize: CGSize = CGSize(width: 200.0, height: 200.0) // MARK: - Configuration - weak var rootItem: OCItem! weak var item: OCItem! weak var core: OCCore! { didSet { @@ -327,7 +325,6 @@ class DisplayViewController: UIViewController { // MARK: - Public API extension DisplayViewController { func configure(_ configuration: DisplayViewConfiguration) { - self.rootItem = configuration.rootItem self.core = configuration.core self.item = configuration.item self.state = configuration.state From b878cb2fd7bc232e7b6ddd2a5ed167a5a4e308f0 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 08:46:49 +0100 Subject: [PATCH 29/34] - Coded publish(progress: Progress) and completed(with error: Error? = nil) as suggested by @felix-schwarz - Replace old calls to completionHandlers with these two new functions. --- ownCloud/Client/Actions/Action.swift | 12 +++++++++++- .../Actions/Actions+Extensions/DeleteAction.swift | 6 +++--- .../Actions/Actions+Extensions/DuplicateAction.swift | 10 +++++----- .../Actions/Actions+Extensions/MoveAction.swift | 6 +++--- .../Actions/Actions+Extensions/OpenInAction.swift | 3 ++- .../Actions/Actions+Extensions/RenameAction.swift | 7 +++---- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 9db5d1def..779dab2e8 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -220,8 +220,18 @@ class Action : NSObject { } func run() { + completed() + } + + func completed(with error: Error? = nil) { if completionHandler != nil { - completionHandler!(nil) + completionHandler!(error) + } + } + + func publish(progress: Progress) { + if progressHandler != nil { + progressHandler!(progress) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index df3cd1daf..91802486c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -62,14 +62,14 @@ class DeleteAction : Action { if let progress = self.core.delete(item, requireMatch: true, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) deleting \(String(describing: item.path))") - self.completionHandler?(error!) + self.completed(with: error) } }) { - self.progressHandler?(progress) + self.publish(progress: progress) } } - self.completionHandler?(nil) + self.completed() }) viewController.present(alertController, animated: true) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 688b7faf1..d2cc446dd 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -34,7 +34,7 @@ class DuplicateAction : Action { // MARK: - Action implementation override func run() { guard context.items.count > 0 else { - completionHandler?(NSError(ocError: .errorInsufficientParameters)) + completed(with: NSError(ocError: OCError.errorItemNotFound)) return } @@ -42,7 +42,7 @@ class DuplicateAction : Action { let rootItem = item.parentItem(from: core) guard rootItem != nil else { - completionHandler?(NSError(ocError: OCError.errorItemNotFound)) + completed(with: NSError(ocError: OCError.errorItemNotFound)) return } @@ -62,12 +62,12 @@ class DuplicateAction : Action { if let progress = self.core.copy(item, to: rootItem!, withName: name, options: nil, resultHandler: { (error, _, item, _) in if error != nil { Log.log("Error \(String(describing: error)) duplicating \(String(describing: item?.path))") - self.completionHandler?(error!) + self.completed(with: error) } else { - self.completionHandler?(nil) + self.completed() } }) { - progressHandler?(progress) + publish(progress: progress) } } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 470ca7f21..234e07e6a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -43,13 +43,13 @@ class MoveAction : Action { items.forEach({ (item) in if let progress = self.core.move(item, to: selectedDirectory, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in if error != nil { - self.completionHandler?(error) + self.completed(with: error) } else { - self.completionHandler?(nil) + self.completed() } }) { - self.progressHandler?(progress) + self.publish(progress: progress) } }) }) diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index ed2bfb099..9c1ff1224 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -50,7 +50,7 @@ class OpenInAction: Action { if error != nil { Log.log("Error \(String(describing: error)) downloading \(String(describing: item.path)) in openIn function") controller.dismiss(animated: true, completion: { - self.completionHandler?(error!) + self.completed(with: error) }) } else { OnMainThread { @@ -63,6 +63,7 @@ class OpenInAction: Action { } }) { controller.attach(progress: progress) + self.publish(progress: progress) } } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index e03fb744d..d66c667a5 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -62,13 +62,12 @@ class RenameAction : Action { if let progress = self.core.move(item, to: rootItem!, withName: newName!, options: nil, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) renaming \(String(describing: item.path))") - - self.completionHandler?(error!) + self.completed(with: error) } else { - self.completionHandler?(nil) + self.completed() } }) { - self.progressHandler?(progress) + self.publish(progress: progress) } }) From fd606d86a1020261d1d9484367aa427ea2384fc8 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 09:40:50 +0100 Subject: [PATCH 30/34] - Make detele as trailing action in the item list. --- ownCloud/Client/Actions/Action.swift | 1 + .../Actions+Extensions/DeleteAction.swift | 1 + .../Actions+Extensions/DuplicateAction.swift | 1 + .../Actions+Extensions/MoveAction.swift | 1 + .../Actions+Extensions/OpenInAction.swift | 1 + .../Actions+Extensions/RenameAction.swift | 1 + .../Client/ClientQueryViewController.swift | 18 ++++++++++++++++++ 7 files changed, 24 insertions(+) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 779dab2e8..d1232fe41 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -58,6 +58,7 @@ extension OCExtensionLocationIdentifier { static let moreItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreItem") //!< Present in "more" card view for a single item static let moreFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreFolder") //!< Present in "more" options for a whole folder static let toolbar: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("toolbar") //!< Present in a toolbar + static let sortBar: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("sortBar") //!< Present in the sort bar on top of file lists } class ActionExtension: OCExtension { diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 91802486c..c21263b70 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -22,6 +22,7 @@ class DeleteAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.delete") } override class var category : ActionCategory? { return .destructive } override class var name : String? { return "Delete".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .tableRow] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index d2cc446dd..ac2db0b3c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -24,6 +24,7 @@ class DuplicateAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.duplicate") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Duplicate".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 234e07e6a..86b785958 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -22,6 +22,7 @@ class MoveAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.move") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Move".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 9c1ff1224..540efde11 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -22,6 +22,7 @@ class OpenInAction: Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.openin") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Open in".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } override class func applicablePosition(forContext: ActionContext) -> ActionPosition { if forContext.items.contains(where: {$0.type == .collection}) { diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index d66c667a5..2a00e45a6 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -22,6 +22,7 @@ class RenameAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.rename") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Rename".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 71986238f..e103acb87 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -286,6 +286,24 @@ class ClientQueryViewController: UITableViewController, Themeable { return true } + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + guard let cell = tableView.cellForRow(at: indexPath) as? ClientItemCell, let item = cell.item else { + return nil + } + + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .tableRow) + let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation) + let actions = Action.sortedApplicableActions(for: actionContext) + actions.forEach({$0.progressHandler = { [weak self] progress in + self?.progressSummarizer?.startTracking(progress: progress) + } + }) + + let contextualActions = actions.compactMap({$0.provideContextualAction()}) + let configuration = UISwipeActionsConfiguration(actions: contextualActions) + return configuration + } + func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { let item: OCItem = itemAtIndexPath(indexPath) From c2c3bf931ae089a4f0212939f3f9eb1325821d9a Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 10:09:32 +0100 Subject: [PATCH 31/34] - CrateFolder is now an Action --- ownCloud.xcodeproj/project.pbxproj | 4 + ownCloud/Client/Actions/Action.swift | 2 +- .../CreateFolderAction.swift | 91 +++++++++++++++++++ .../Actions+Extensions/DeleteAction.swift | 2 +- .../Actions+Extensions/DuplicateAction.swift | 2 +- .../Actions+Extensions/MoveAction.swift | 2 +- .../Actions+Extensions/RenameAction.swift | 2 +- .../Client/ClientQueryViewController.swift | 34 ------- 8 files changed, 100 insertions(+), 39 deletions(-) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index ddbe7027e..7fb56599d 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -77,6 +77,7 @@ 6EA78B8F2179B55400A5216A /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA78B8E2179B55400A5216A /* ImageScrollView.swift */; }; 6EADE9372192E235006821B3 /* UIImagePickerController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EADE9362192E235006821B3 /* UIImagePickerController+Extension.swift */; }; 6EB8EDC52114358400C2BF44 /* folder-create.tvg in Resources */ = {isa = PBXBuildFile; fileRef = 6EB8EDBE2114358300C2BF44 /* folder-create.tvg */; }; + 6ED1B80B21A4004900E16C95 /* CreateFolderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED1B80A21A4004900E16C95 /* CreateFolderAction.swift */; }; 75AC0B4AD332C8CC785FE349 /* Pods_ownCloudTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A56EA84D8AD331FFA604138B /* Pods_ownCloudTests.framework */; }; A45A8D98137C902524B84E6D /* EarlGrey.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = D0D9C062DD1E85A838608B0F /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DC018F8320A0F56300135198 /* UIView+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8220A0F56300135198 /* UIView+Animation.swift */; }; @@ -453,6 +454,7 @@ 6EA78B8E2179B55400A5216A /* ImageScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; 6EADE9362192E235006821B3 /* UIImagePickerController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImagePickerController+Extension.swift"; sourceTree = ""; }; 6EB8EDBE2114358300C2BF44 /* folder-create.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "folder-create.tvg"; path = "img/filetypes-tvg/folder-create.tvg"; sourceTree = SOURCE_ROOT; }; + 6ED1B80A21A4004900E16C95 /* CreateFolderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateFolderAction.swift; sourceTree = ""; }; A56EA84D8AD331FFA604138B /* Pods_ownCloudTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ownCloudTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0D9C062DD1E85A838608B0F /* EarlGrey.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EarlGrey.framework; path = Pods/EarlGrey/EarlGrey/EarlGrey.framework; sourceTree = SOURCE_ROOT; }; D7F3B3E74D4B04F9CAF95C09 /* EarlGrey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EarlGrey.swift; sourceTree = ""; }; @@ -847,6 +849,7 @@ 6E586CFF2199A78E00F680C4 /* DeleteAction.swift */, 6E3A103D219D5BBA00F90C96 /* RenameAction.swift */, 6E3A104C219D6F0100F90C96 /* DuplicateAction.swift */, + 6ED1B80A21A4004900E16C95 /* CreateFolderAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -1611,6 +1614,7 @@ 4C464BF52187AF1500D30602 /* PDFThumbnailsCollectionViewController.swift in Sources */, 4C464BF02187AF1500D30602 /* PDFTocTableViewController.swift in Sources */, DC4FEAEA209E48E800D4476B /* DispatchQueueTools.swift in Sources */, + 6ED1B80B21A4004900E16C95 /* CreateFolderAction.swift in Sources */, DC1B2708209CF0D3004715E1 /* IssuesPresentationAnimator.swift in Sources */, DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */, DC854936218331CF00782BA8 /* UserInterfaceSettingsSection.swift in Sources */, diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index d1232fe41..f88047f11 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -220,7 +220,7 @@ class Action : NSObject { } } - func run() { + @objc func run() { completed() } diff --git a/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift new file mode 100644 index 000000000..e7543ebb9 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift @@ -0,0 +1,91 @@ +// +// CreateFolderAction.swift +// ownCloud +// +// Created by Pablo Carrascal on 20/11/2018. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* +* Copyright (C) 2018, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + +import ownCloudSDK + +class CreateFolderAction : Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.crateFolder") } + override class var category : ActionCategory? { return .normal } + override class var name : String? { return "Create Folder".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.sortBar] } + + // MARK: - Extension matching + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + // Examine items in context + return .first + } + + // MARK: - Action implementation + override func run() { + guard context.items.count > 0 else { + completed(with: NSError(ocError: OCError.errorItemNotFound)) + return + } + + let item = context.items[0] + let rootItem = item.parentItem(from: core) + + guard rootItem != nil else { + completed(with: NSError(ocError: OCError.errorItemNotFound)) + return + } + + guard let viewController = context.viewController else { + return + } + + let createFolderVC = NamingViewController( with: core, defaultName: "New Folder".localized, stringValidator: { name in + if name.contains("/") || name.contains("\\") { + return (false, "File name cannot contain / or \\") + } else { + return (true, nil) + } + }, completion: { newName, _ in + + guard newName != nil else { + return + } + + if let progress = self.core.createFolder(newName!, inside: rootItem!, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + Log.error("Error \(String(describing: error)) creating folder \(String(describing: newName))") + self.completed(with: error) + } else { + self.completed() + } + }) { + self.publish(progress: progress) + } + }) + + createFolderVC.navigationItem.title = "Create folder".localized + + let createFolderNavigationVC = ThemeNavigationController(rootViewController: createFolderVC) + createFolderNavigationVC.modalPresentationStyle = .overFullScreen + + viewController.present(createFolderNavigationVC, animated: true) + } + + override func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + if location == .sortBar || location == .toolbar { + return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) + } + + return nil + } +} diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index c21263b70..4b21d3fa8 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -22,7 +22,7 @@ class DeleteAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.delete") } override class var category : ActionCategory? { return .destructive } override class var name : String? { return "Delete".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .tableRow] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .tableRow, .moreFolder] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index ac2db0b3c..149487950 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -24,7 +24,7 @@ class DuplicateAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.duplicate") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Duplicate".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 86b785958..46e044aa8 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -22,7 +22,7 @@ class MoveAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.move") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Move".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 2a00e45a6..fb04dad34 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -22,7 +22,7 @@ class RenameAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.rename") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Rename".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index e103acb87..69f6e5bfe 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -486,39 +486,6 @@ class ClientQueryViewController: UITableViewController, Themeable { // MARK: - Search var searchController: UISearchController? - func createFolder(viewDidAppearHandler: ClientActionVieDidAppearHandler? = nil, completionHandler: ClientActionCompletionHandler? = nil) { - let createFolderVC = NamingViewController( with: core, defaultName: "New Folder".localized, stringValidator: { name in - if name.contains("/") || name.contains("\\") { - return (false, "File name cannot contain / or \\") - } else { - return (true, nil) - } - }, completion: { newName, _ in - - guard newName != nil else { - return - } - - if let progress = self.core.createFolder(newName!, inside: self.query.rootItem, options: nil, resultHandler: { (error, _, _, _) in - if error != nil { - Log.error("Error \(String(describing: error)) creating folder \(String(describing: newName))") - completionHandler?(false) - } else { - completionHandler?(true) - } - }) { - self.progressSummarizer?.startTracking(progress: progress) - } - }) - - createFolderVC.navigationItem.title = "Create folder".localized - - let createFolderNavigationVC = ThemeNavigationController(rootViewController: createFolderVC) - createFolderNavigationVC.modalPresentationStyle = .overFullScreen - - self.present(createFolderNavigationVC, animated: true, completion: viewDidAppearHandler) - } - // MARK: - Navigation Bar Actions @objc func uploadsBarButtonPressed(_ sender: UIBarButtonItem) { @@ -637,7 +604,6 @@ extension ClientQueryViewController : OCQueryDelegate { // MARK: - SortBar Delegate extension ClientQueryViewController : SortBarDelegate { func sortBar(_ sortBar: SortBar, leftButtonPressed: UIButton) { - self.createFolder() } func sortBar(_ sortBar: SortBar, rightButtonPressed: UIButton) { From 13d58f8ed0e9d6424a1f674fd9b3418e0e13915d Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 10:47:57 +0100 Subject: [PATCH 32/34] - Create folder as an action --- ownCloud/AppDelegate.swift | 1 + ownCloud/Client/Actions/Action.swift | 13 +++++++++++++ .../Actions+Extensions/CreateFolderAction.swift | 13 ++++++++----- .../Actions/Actions+Extensions/OpenInAction.swift | 1 + ownCloud/Client/ClientQueryViewController.swift | 11 +++++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index b6a4fd6a8..34b9433b7 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -61,6 +61,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(MoveAction.actionExtension) OCExtensionManager.shared.addExtension(RenameAction.actionExtension) OCExtensionManager.shared.addExtension(DuplicateAction.actionExtension) + OCExtensionManager.shared.addExtension(CreateFolderAction.actionExtension) Theme.shared.activeCollection = ThemeCollection(with: ThemeStyle.preferredStyle) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index f88047f11..2c35b91a5 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -129,6 +129,19 @@ class Action : NSObject { return .noMatch } + // Filter by location identifier i.e. .sortbar + let matches = self.locations?.filter({ (identifier) in + identifier == context.location?.identifier + }) + + guard matches != nil else { + return .noMatch + } + + if matches!.count == 0 { + return .noMatch + } + return priority // Additional filtering (f.ex. via OCClassSettings, Settings) goes here diff --git a/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift index e7543ebb9..a0df46636 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CreateFolderAction.swift @@ -26,7 +26,11 @@ class CreateFolderAction : Action { // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { - // Examine items in context + + if forContext.items.count > 1 { + return .none + } + return .first } @@ -37,10 +41,9 @@ class CreateFolderAction : Action { return } - let item = context.items[0] - let rootItem = item.parentItem(from: core) + let item = context.items.first - guard rootItem != nil else { + guard item != nil else { completed(with: NSError(ocError: OCError.errorItemNotFound)) return } @@ -61,7 +64,7 @@ class CreateFolderAction : Action { return } - if let progress = self.core.createFolder(newName!, inside: rootItem!, options: nil, resultHandler: { (error, _, _, _) in + if let progress = self.core.createFolder(newName!, inside: item!, options: nil, resultHandler: { (error, _, _, _) in if error != nil { Log.error("Error \(String(describing: error)) creating folder \(String(describing: newName))") self.completed(with: error) diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 540efde11..f71d68384 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -28,6 +28,7 @@ class OpenInAction: Action { if forContext.items.contains(where: {$0.type == .collection}) { return .none } + if forContext.items.count > 1 { return .none } diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 69f6e5bfe..0980c3023 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -604,6 +604,17 @@ extension ClientQueryViewController : OCQueryDelegate { // MARK: - SortBar Delegate extension ClientQueryViewController : SortBarDelegate { func sortBar(_ sortBar: SortBar, leftButtonPressed: UIButton) { + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .sortBar) + let actionContext = ActionContext(viewController: self, core: core, items: [query.rootItem], location: actionsLocation) + + let actions = Action.sortedApplicableActions(for: actionContext) + + let createFolderAction = actions.first + createFolderAction?.progressHandler = { [weak self] progess in + self?.progressSummarizer?.startTracking(progress: progess) + } + + actions.first?.run() } func sortBar(_ sortBar: SortBar, rightButtonPressed: UIButton) { From af94881c7c1af3eb6c92daf705310bcde3b49d11 Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Tue, 20 Nov 2018 16:40:12 +0100 Subject: [PATCH 33/34] - SDK update. - Removed unneeded code (fixed in the SDK). --- ownCloud/Client/Actions/Action.swift | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index 2c35b91a5..f88047f11 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -129,19 +129,6 @@ class Action : NSObject { return .noMatch } - // Filter by location identifier i.e. .sortbar - let matches = self.locations?.filter({ (identifier) in - identifier == context.location?.identifier - }) - - guard matches != nil else { - return .noMatch - } - - if matches!.count == 0 { - return .noMatch - } - return priority // Additional filtering (f.ex. via OCClassSettings, Settings) goes here From 7edde848763795201267006cf655565691a45cbd Mon Sep 17 00:00:00 2001 From: Pablo Carrascal Date: Fri, 30 Nov 2018 12:56:43 +0100 Subject: [PATCH 34/34] - SDK updated. - Added code removed while rebasing against. --- ios-sdk | 2 +- .../Client/Actions/MoreViewController.swift | 7 ++++++- .../Client/ClientQueryViewController.swift | 20 ++++++++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ios-sdk b/ios-sdk index 8173316d4..2663fbca7 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 8173316d4254c8a398a337c9678e57682be29f76 +Subproject commit 2663fbca7c051fec04723767ea6c808acb233f91 diff --git a/ownCloud/Client/Actions/MoreViewController.swift b/ownCloud/Client/Actions/MoreViewController.swift index 9057d5d29..8616fa952 100644 --- a/ownCloud/Client/Actions/MoreViewController.swift +++ b/ownCloud/Client/Actions/MoreViewController.swift @@ -21,6 +21,9 @@ import ownCloudSDK class MoreViewController: UIViewController { + private var item: OCItem + private var core: OCCore + private var headerView: UIView private var viewController: UIViewController @@ -32,7 +35,9 @@ class MoreViewController: UIViewController { } } - init(header: UIView, viewController: UIViewController) { + init(item: OCItem, core: OCCore, header: UIView, viewController: UIViewController) { + self.item = item + self.core = core self.headerView = header self.viewController = viewController diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 0980c3023..841320ce9 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -486,6 +486,20 @@ class ClientQueryViewController: UITableViewController, Themeable { // MARK: - Search var searchController: UISearchController? + func upload(itemURL: URL, name: String, completionHandler: ClientActionCompletionHandler? = nil) { + if let progress = core.importFileNamed(name, at: query.rootItem, from: itemURL, isSecurityScoped: false, options: nil, placeholderCompletionHandler: nil, resultHandler: { [weak self](error, _ core, _ item, _) in + if error != nil { + Log.debug("Error uploading \(Log.mask(name)) file to \(Log.mask(self?.query.rootItem.path))") + completionHandler?(false) + } else { + Log.debug("Success uploading \(Log.mask(name)) file to \(Log.mask(self?.query.rootItem.path))") + completionHandler?(true) + } + }) { + self.progressSummarizer?.startTracking(progress: progress) + } + } + // MARK: - Navigation Bar Actions @objc func uploadsBarButtonPressed(_ sender: UIBarButtonItem) { @@ -717,11 +731,7 @@ extension ClientQueryViewController: UITableViewDropDelegate { } -<<<<<<< HEAD - if let progress = self.core.move(item, to: destinationItem, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in -======= - if let progress = self.core.move(item, to: destinationItem!, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in ->>>>>>> 3aded8b... - Made de Duplicate action. + if let progress = self.core.move(item, to: destinationItem, withName: item.name, options: nil, resultHandler: { (error, _, _, _) in if error != nil { Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") }