From a5f596806be19ebfb9156bb238c1a95888d4e3ef Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 19 Jan 2021 15:19:17 +0100 Subject: [PATCH 1/6] Add support for TLS certificate change diff: - ConnectionIssueViewController: - compare against certificate stored in the bookmark where available - remove text-based rendering via CertificateViewController (and remove the class), replace it with the standard certificate view controller - adaptions to new ThemeCertificateViewController initialization where needed - update SDK --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 4 -- .../Bookmarks/BookmarkViewController.swift | 7 +- .../CertificateViewController.swift | 68 ------------------- .../ConnectionIssueViewController.swift | 23 +++---- .../Client/ClientRootViewController.swift | 2 +- .../CertificateManagementViewController.swift | 5 +- 7 files changed, 17 insertions(+), 94 deletions(-) delete mode 100644 ownCloud/Bookmarks/Issues/Issues Subclasses/CertificateViewController.swift diff --git a/ios-sdk b/ios-sdk index 8440e0259..40de8fa56 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 8440e0259ca630edcfba640031434eb81fd3107f +Subproject commit 40de8fa5660447ea1cc6601bd9bb52c4e4848376 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 91b6cf555..a08610cea 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -267,7 +267,6 @@ DC18898E218A773700CFB3F9 /* ownCloudMocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; DC1B2707209CF0D3004715E1 /* IssuesDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B26FE209CF0D2004715E1 /* IssuesDismissalAnimator.swift */; }; DC1B2708209CF0D3004715E1 /* IssuesPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B26FF209CF0D2004715E1 /* IssuesPresentationAnimator.swift */; }; - DC1B2709209CF0D3004715E1 /* CertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2705209CF0D3004715E1 /* CertificateViewController.swift */; }; DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */; }; DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */; }; DC20DE5C21C01A3D0096000B /* ownCloudMocking.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; @@ -1156,7 +1155,6 @@ DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; DC1B26FE209CF0D2004715E1 /* IssuesDismissalAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuesDismissalAnimator.swift; sourceTree = ""; }; DC1B26FF209CF0D2004715E1 /* IssuesPresentationAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuesPresentationAnimator.swift; sourceTree = ""; }; - DC1B2705209CF0D3004715E1 /* CertificateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificateViewController.swift; sourceTree = ""; }; DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionIssueViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreReceipt.h; sourceTree = ""; }; @@ -2151,7 +2149,6 @@ DC1B2704209CF0D3004715E1 /* Issues Subclasses */ = { isa = PBXGroup; children = ( - DC1B2705209CF0D3004715E1 /* CertificateViewController.swift */, DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */, ); path = "Issues Subclasses"; @@ -3816,7 +3813,6 @@ DCD1300A23A191C000255779 /* LicenseOfferButton.swift in Sources */, 0269F589244DED02002E9D99 /* UIAlertController+UniversalLinks.swift in Sources */, 4C9BFA2323158C3F0059CA3E /* PreviewViewController.swift in Sources */, - DC1B2709209CF0D3004715E1 /* CertificateViewController.swift in Sources */, 4C3E17DB234DBF9A000D7BA8 /* PendingMediaUploadTaskExtension.swift in Sources */, DCC832DE242C0C3700153F8C /* DisplaySleepPreventer.swift in Sources */, 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */, diff --git a/ownCloud/Bookmarks/BookmarkViewController.swift b/ownCloud/Bookmarks/BookmarkViewController.swift index 43aefb1e3..482551d2a 100644 --- a/ownCloud/Bookmarks/BookmarkViewController.swift +++ b/ownCloud/Bookmarks/BookmarkViewController.swift @@ -185,11 +185,10 @@ class BookmarkViewController: StaticTableViewController { certificateRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in if let certificate = self?.bookmark?.certificate { - if let certificateViewController : ThemeCertificateViewController = ThemeCertificateViewController(certificate: certificate) { - let navigationController = ThemeNavigationController(rootViewController: certificateViewController) + let certificateViewController : ThemeCertificateViewController = ThemeCertificateViewController(certificate: certificate, compare: nil) + let navigationController = ThemeNavigationController(rootViewController: certificateViewController) - self?.present(navigationController, animated: true, completion: nil) - } + self?.present(navigationController, animated: true, completion: nil) } }, title: "Certificate Details".localized, accessoryType: .disclosureIndicator, accessoryView: BorderedLabel(), identifier: "row-url-certificate") diff --git a/ownCloud/Bookmarks/Issues/Issues Subclasses/CertificateViewController.swift b/ownCloud/Bookmarks/Issues/Issues Subclasses/CertificateViewController.swift deleted file mode 100644 index 37d94ec0e..000000000 --- a/ownCloud/Bookmarks/Issues/Issues Subclasses/CertificateViewController.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// CertificatesViewController.swift -// ownCloud -// -// Created by Pablo Carrascal on 05/04/2018. -// Copyright © 2018 ownCloud GmbH. All rights reserved. -// - -/* -* Copyright (C) 2019, 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 CertificateViewController: IssuesViewController { - - var localizedDescription: NSAttributedString? { - didSet { - if self.tableView?.window != nil { - self.tableView?.reloadData() - } - } - } - - init(buttons: [IssueButton]? = nil) { - if buttons != nil { - super.init(buttons: buttons, title: "Certificate Details".localized) - } else { - super.init(buttons: nil, title: "Certificate Details".localized) - self.buttons = [IssueButton(title: "OK".localized, type: .plain, action: { - self.dismiss(animated: true)}, accessibilityIdentifier: "ok-button-certificate-details")] - } - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - super.viewDidLoad() - self.tableView?.dataSource = self - } -} - -extension CertificateViewController: UITableViewDataSource { - - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = UITableViewCell(style: .default, reuseIdentifier: nil) - cell.textLabel?.attributedText = localizedDescription - cell.textLabel?.numberOfLines = 0 - cell.selectionStyle = .none - return cell - } -} diff --git a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift b/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift index 03dee6e83..184d261a4 100644 --- a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift +++ b/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift @@ -32,6 +32,8 @@ class ConnectionIssueViewController: IssuesViewController { private var displayIssues : DisplayIssues? private var dismissedHandler : (() -> Void)? + var bookmark : OCBookmark? + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @@ -42,12 +44,13 @@ class ConnectionIssueViewController: IssuesViewController { displayIssues = issues } - convenience init(displayIssues issues: DisplayIssues?, title: String? = nil, completion:@escaping (ConnectionResponse) -> Void, dismissedHandler dismissedHandlerBlock: (() -> Void)? = nil) { + convenience init(displayIssues issues: DisplayIssues?, title: String? = nil, bookmark: OCBookmark? = nil, completion:@escaping (ConnectionResponse) -> Void, dismissedHandler dismissedHandlerBlock: (() -> Void)? = nil) { var useButtons : [IssueButton]? var useTitle = title self.init(displayIssues: issues, buttons: nil, title: useTitle) + self.bookmark = bookmark self.dismissedHandler = dismissedHandlerBlock if let displayLevel = issues?.displayLevel { @@ -111,20 +114,14 @@ class ConnectionIssueViewController: IssuesViewController { extension ConnectionIssueViewController { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let issue = displayIssues?.displayIssues[indexPath.row], issue.type == OCIssueType.certificate { - let certificateViewController = CertificateViewController() - certificateViewController.modalPresentationStyle = .overCurrentContext - - if let certificateNodes = OCCertificateDetailsViewNode.certificateDetailsViewNodes(for: issue.certificate, withValidationCompletionHandler: { (certificateNodes) in - let certDetails: NSAttributedString = OCCertificateDetailsViewNode.attributedString(withCertificateDetails: certificateNodes) + if let issue = displayIssues?.displayIssues[indexPath.row], issue.type == .certificate, let certificate = issue.certificate { + let certificateViewController = ThemeCertificateViewController(certificate: certificate, compare: bookmark?.certificate) - OnMainThread { - certificateViewController.localizedDescription = certDetails - } - }) { - certificateViewController.localizedDescription = OCCertificateDetailsViewNode.attributedString(withCertificateDetails: certificateNodes) - self.present(certificateViewController, animated: true, completion: nil) + if bookmark?.certificate != nil { + certificateViewController.showDifferences = true } + + self.present(ThemeNavigationController(rootViewController: certificateViewController), animated: true, completion: nil) } } } diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index c95f84a03..0d3236fc0 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -591,7 +591,7 @@ extension ClientRootViewController : OCCoreDelegate { if presentIssue?.type == .multipleChoice { presentViewController = ThemedAlertController(with: presentIssue!, completion: queueCompletionHandler) } else { - presentViewController = ConnectionIssueViewController(displayIssues: presentIssue?.prepareForDisplay(), completion: { (response) in + presentViewController = ConnectionIssueViewController(displayIssues: presentIssue?.prepareForDisplay(), bookmark: self?.bookmark, completion: { (response) in switch response { case .cancel: presentIssue?.reject() diff --git a/ownCloud/Settings/Certificate Management/CertificateManagementViewController.swift b/ownCloud/Settings/Certificate Management/CertificateManagementViewController.swift index b3d2bb8d1..0a4d99326 100644 --- a/ownCloud/Settings/Certificate Management/CertificateManagementViewController.swift +++ b/ownCloud/Settings/Certificate Management/CertificateManagementViewController.swift @@ -49,9 +49,8 @@ class CertificateManagementViewController: StaticTableViewController { let approvalDate = shortReason + " " + ((certificate.userAcceptedDate==nil) ? " \("undated".localized)" : DateFormatter.localizedString(from: certificate.userAcceptedDate!, dateStyle: .medium, timeStyle: .short)) let certificateRow = CertificateManagementRow(subtitleRowWithAction: { (row, _) in - if let certificateDetailsViewController = ThemeCertificateViewController(certificate: certificate) { - row.viewController?.navigationController?.pushViewController(certificateDetailsViewController, animated: true) - } + let certificateDetailsViewController = ThemeCertificateViewController(certificate: certificate, compare: nil) + row.viewController?.navigationController?.pushViewController(certificateDetailsViewController, animated: true) }, title: certificate.hostName ?? "", subtitle: approvalDate, accessoryType: .disclosureIndicator) certificateRow.certificate = certificate From 2500527380ff02cf98a23f7728e676b80730a50f Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 19 Jan 2021 15:31:34 +0100 Subject: [PATCH 2/6] - update sdk --- ios-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index 40de8fa56..3a0227356 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 40de8fa5660447ea1cc6601bd9bb52c4e4848376 +Subproject commit 3a02273565681ac57eb8ac5bbb083cd88da31565 From b320840628dad86dcea24a8b7c4395f5a6c4b8af Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Sat, 23 Jan 2021 12:50:40 +0100 Subject: [PATCH 3/6] - Issues presentation - remove IssuesViewController, ConnectionIssueViewController, IssuesPresentationAnimator and IssuesDismissalAnimator - create modern replacement leveraging existing code: IssuesCardViewController - migrate code from ConnectionIssueViewController to IssuesCardViewController - add new helper function to compare DisplayIssues issue levels without having to use .rawValue - MoreViewController - becomes FrameViewController - adds the ability to also add a footer view - cleanup: removal of unused properties and initializers - StaticTableViewRow: add .messageStyle support - ThemeTableViewCell: add .messageStyle support and add additional CellStyler block API based on also new CellStyleSet - AlertView: - add support for .accessibilityIdentifier - add option for custom .contentPadding - add button-only mode - CardPresentationController: - ensure .dismissable is actually respected - fix premature release of CardTransitionDelegate that could lead to normal presentation rather than as a card --- ownCloud.xcodeproj/project.pbxproj | 60 ++--- .../Bookmarks/BookmarkViewController.swift | 24 +- .../IssuesDismissalAnimator.swift | 51 ---- .../IssuesPresentationAnimator.swift | 56 ----- .../ConnectionIssueViewController.swift | 181 ------------- .../Issues/IssuesViewController.swift | 236 ----------------- .../Client/Actions/Action+UserInterface.swift | 6 +- .../Client/ClientRootViewController.swift | 34 +-- ownCloud/Diagnostic/DiagnosticManager.swift | 2 +- ownCloud/Import/ImportFilesController.swift | 4 +- .../Issues/IssuesCardViewController.swift | 238 ++++++++++++++++++ ownCloud/Key Commands/KeyCommands.swift | 56 ++--- .../LicenseInAppPurchaseFeatureView.swift | 2 +- ownCloud/Messages/AlertView.swift | 57 +++-- ownCloud/Migration/Migration.swift | 5 +- .../Resources/en.lproj/Localizable.strings | 1 + .../SDK Extensions/OCIssue+Extension.swift | 4 + .../StaticLoginSetupViewController.swift | 63 ++--- .../CardPresentationController.swift | 13 +- ...roller.swift => FrameViewController.swift} | 53 ++-- .../StaticTableView/StaticTableViewRow.swift | 7 +- .../Theme/UI/ThemeTableViewCell.swift | 56 ++++- 22 files changed, 490 insertions(+), 719 deletions(-) delete mode 100644 ownCloud/Bookmarks/Issues/Issues Animators/IssuesDismissalAnimator.swift delete mode 100644 ownCloud/Bookmarks/Issues/Issues Animators/IssuesPresentationAnimator.swift delete mode 100644 ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift delete mode 100644 ownCloud/Bookmarks/Issues/IssuesViewController.swift create mode 100644 ownCloud/Issues/IssuesCardViewController.swift rename ownCloudAppShared/User Interface/More/{MoreViewController.swift => FrameViewController.swift} (75%) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index a08610cea..8e0622217 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -33,7 +33,6 @@ 233BDEB5204FEFE500C06732 /* OwnCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233BDEB4204FEFE500C06732 /* OwnCloudTests.swift */; }; 233E0FD82099F11D00C3D8D5 /* SecuritySettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E0FD72099F11D00C3D8D5 /* SecuritySettingsSection.swift */; }; 23957A6D209AFFE8003C8537 /* MoreSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23957A6C209AFFE8003C8537 /* MoreSettingsSection.swift */; }; - 23BEF1182076667F00DD2E6F /* IssuesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23BEF1172076667F00DD2E6F /* IssuesViewController.swift */; }; 23D5241521491C670002C566 /* DisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D5241421491C670002C566 /* DisplayViewController.swift */; }; 23EC77582137F3DD0032D4E6 /* PDFViewerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EC77542137F3DC0032D4E6 /* PDFViewerViewController.swift */; }; 23EC77592137F3DD0032D4E6 /* DisplayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EC77552137F3DC0032D4E6 /* DisplayExtension.swift */; }; @@ -265,15 +264,13 @@ DC0A5C432550C70800E6674B /* class-settings-sdk in Resources */ = {isa = PBXBuildFile; fileRef = DC0A5C422550C70800E6674B /* class-settings-sdk */; }; DC0B379420514E4700189B9A /* ServerListBookmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */; }; DC18898E218A773700CFB3F9 /* ownCloudMocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; - DC1B2707209CF0D3004715E1 /* IssuesDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B26FE209CF0D2004715E1 /* IssuesDismissalAnimator.swift */; }; - DC1B2708209CF0D3004715E1 /* IssuesPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B26FF209CF0D2004715E1 /* IssuesPresentationAnimator.swift */; }; - DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */; }; DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */; }; DC20DE5C21C01A3D0096000B /* ownCloudMocking.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; DC20DE6A21C01B210096000B /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; DC20DE6B21C01B210096000B /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; DC23D1D9238F390A00423F62 /* OCLicenseAppStoreReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */; }; DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC24B31D25BB6FC4005783E2 /* IssuesCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24B31C25BB6FC4005783E2 /* IssuesCardViewController.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC26ADDE2550C0B20059680D /* MetadataDocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -415,7 +412,7 @@ DCE4E43924C19AB20051722F /* MoreStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232B01F52126B10900366FA0 /* MoreStaticTableViewController.swift */; }; DCE4E43A24C19ADC0051722F /* MoreViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */; }; DCE4E43B24C19B4F0051722F /* NSLayoutConstraint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */; }; - DCE4E43C24C19B660051722F /* MoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236735A521217C3500E5834A /* MoreViewController.swift */; }; + DCE4E43C24C19B660051722F /* FrameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236735A521217C3500E5834A /* FrameViewController.swift */; }; DCE4E43E24C19C3E0051722F /* Action+UserInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE4E43D24C19C3E0051722F /* Action+UserInterface.swift */; }; DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC434D1220D7A8F100740056 /* UIAlertController+OCIssue.swift */; }; DCE4E44124C1A07E0051722F /* UITableViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */; }; @@ -874,11 +871,10 @@ 233BDEB6204FEFE500C06732 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ownCloudSDK.xcodeproj; path = "ios-sdk/ownCloudSDK.xcodeproj"; sourceTree = ""; }; 233E0FD72099F11D00C3D8D5 /* SecuritySettingsSection.swift */ = {isa = PBXFileReference; indentWidth = 8; lastKnownFileType = sourcecode.swift; path = SecuritySettingsSection.swift; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; - 236735A521217C3500E5834A /* MoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoreViewController.swift; sourceTree = ""; }; + 236735A521217C3500E5834A /* FrameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameViewController.swift; sourceTree = ""; }; 23957A6C209AFFE8003C8537 /* MoreSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreSettingsSection.swift; sourceTree = ""; }; 239F1318205A693A0029F186 /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 239F437D20D0EE6300B1276D /* icon-search.tvg */ = {isa = PBXFileReference; lastKnownFileType = text; name = "icon-search.tvg"; path = "../img/filetypes-tvg/icon-search.tvg"; sourceTree = ""; }; - 23BEF1172076667F00DD2E6F /* IssuesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuesViewController.swift; sourceTree = ""; }; 23C5652F212167BD00BD4B47 /* CardPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentationController.swift; sourceTree = ""; }; 23C56536212167BE00BD4B47 /* CardTransitionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardTransitionDelegate.swift; sourceTree = ""; }; 23D5241421491C670002C566 /* DisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayViewController.swift; sourceTree = ""; }; @@ -1153,14 +1149,12 @@ DC0B37962051681600189B9A /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = ""; }; DC136581208223F000FC0F60 /* OCBookmark+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCBookmark+Extension.swift"; sourceTree = ""; }; DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; - DC1B26FE209CF0D2004715E1 /* IssuesDismissalAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuesDismissalAnimator.swift; sourceTree = ""; }; - DC1B26FF209CF0D2004715E1 /* IssuesPresentationAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuesPresentationAnimator.swift; sourceTree = ""; }; - DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionIssueViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreReceipt.h; sourceTree = ""; }; DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseAppStoreReceipt.m; sourceTree = ""; }; DC243BF92317B446004FBB5C /* ThemeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeWindow.swift; sourceTree = ""; }; DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Extension.swift"; sourceTree = ""; }; + DC24B31C25BB6FC4005783E2 /* IssuesCardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuesCardViewController.swift; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataDocumentationTests.swift; sourceTree = ""; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -1541,16 +1535,6 @@ path = Migration; sourceTree = ""; }; - 23031768205AA1DB006C4DAF /* Issues */ = { - isa = PBXGroup; - children = ( - 23BEF1172076667F00DD2E6F /* IssuesViewController.swift */, - DC1B26FD209CF0D2004715E1 /* Issues Animators */, - DC1B2704209CF0D3004715E1 /* Issues Subclasses */, - ); - path = Issues; - sourceTree = ""; - }; 233BDE93204FEFE500C06732 = { isa = PBXGroup; children = ( @@ -1598,6 +1582,7 @@ 3968C878239C54AC00AC28AC /* Release Notes */, 233BDE9F204FEFE500C06732 /* AppDelegate.swift */, 3961281522F8730A0087BD3A /* SceneDelegate.swift */, + DCD502B325BC3432007F9087 /* Issues */, DCF4F1612051925A00189B9A /* Bookmarks */, DC8EB26F239308C3009148F9 /* Licensing */, 4C51727422DE04BD001BC97F /* Tasks */, @@ -2137,23 +2122,6 @@ path = "Cursor Support"; sourceTree = ""; }; - DC1B26FD209CF0D2004715E1 /* Issues Animators */ = { - isa = PBXGroup; - children = ( - DC1B26FE209CF0D2004715E1 /* IssuesDismissalAnimator.swift */, - DC1B26FF209CF0D2004715E1 /* IssuesPresentationAnimator.swift */, - ); - path = "Issues Animators"; - sourceTree = ""; - }; - DC1B2704209CF0D3004715E1 /* Issues Subclasses */ = { - isa = PBXGroup; - children = ( - DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */, - ); - path = "Issues Subclasses"; - sourceTree = ""; - }; DC23D1D0238F38DF00423F62 /* Receipt */ = { isa = PBXGroup; children = ( @@ -2691,6 +2659,14 @@ path = Tools; sourceTree = ""; }; + DCD502B325BC3432007F9087 /* Issues */ = { + isa = PBXGroup; + children = ( + DC24B31C25BB6FC4005783E2 /* IssuesCardViewController.swift */, + ); + path = Issues; + sourceTree = ""; + }; DCDC208923991296003CFF5B /* Transactions */ = { isa = PBXGroup; children = ( @@ -2763,7 +2739,7 @@ DCE4E43624C19A3B0051722F /* More */ = { isa = PBXGroup; children = ( - 236735A521217C3500E5834A /* MoreViewController.swift */, + 236735A521217C3500E5834A /* FrameViewController.swift */, 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */, 232B01F52126B10900366FA0 /* MoreStaticTableViewController.swift */, ); @@ -2858,7 +2834,6 @@ DCF4F1612051925A00189B9A /* Bookmarks */ = { isa = PBXGroup; children = ( - 23031768205AA1DB006C4DAF /* Issues */, DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */, 4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */, ); @@ -3798,7 +3773,6 @@ 4C464BF52187AF1500D30602 /* PDFThumbnailsCollectionViewController.swift in Sources */, 4C464BF02187AF1500D30602 /* PDFTocTableViewController.swift in Sources */, 3961281622F8730A0087BD3A /* SceneDelegate.swift in Sources */, - DC1B2708209CF0D3004715E1 /* IssuesPresentationAnimator.swift in Sources */, DCA35D8124D1707100DBE2B0 /* OCSyncRecordActivity+DiagnosticGenerator.swift in Sources */, DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */, 4CB8ADE022DF5EC500F1FEBC /* UIAlertViewController+SystemPermissions.swift in Sources */, @@ -3902,7 +3876,6 @@ DCEE1C9C23A0EADD00FE8D98 /* LicenseOfferView.swift in Sources */, DCB6C4DE24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift in Sources */, 6E3A104D219D6F0100F90C96 /* DuplicateAction.swift in Sources */, - DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */, DCFB74C121AD5C88005796AF /* StaticLoginStepViewController.swift in Sources */, 02F2891424BFAF0100E3D35C /* MigrationViewController.swift in Sources */, DC4D5A0A247C1398008ADDB6 /* MessageGroup.swift in Sources */, @@ -3918,8 +3891,7 @@ 02DC7C9024CB354800DCB2C6 /* ProPhotoUploadSettingsSection.swift in Sources */, DCC832F6242CC5F700153F8C /* CardIssueMessagePresenter.swift in Sources */, DCD1301123A23F4E00255779 /* OCLicenseManager+AppStore.swift in Sources */, - DC1B2707209CF0D3004715E1 /* IssuesDismissalAnimator.swift in Sources */, - 23BEF1182076667F00DD2E6F /* IssuesViewController.swift in Sources */, + DC24B31D25BB6FC4005783E2 /* IssuesCardViewController.swift in Sources */, DC62514C225D254500736874 /* UploadBaseAction.swift in Sources */, 4C6B78102226B83300C5F3DB /* PhotoAlbumTableViewController.swift in Sources */, DC3393A222E0A71100DD3DA4 /* ItemPolicyCell.swift in Sources */, @@ -4024,7 +3996,7 @@ DC0A357124C0E42700FB58FC /* StaticTableViewRow.swift in Sources */, DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */, DC0A358D24C0E44B00FB58FC /* ThemedAlertController.swift in Sources */, - DCE4E43C24C19B660051722F /* MoreViewController.swift in Sources */, + DCE4E43C24C19B660051722F /* FrameViewController.swift in Sources */, DC0A35A124C1091400FB58FC /* UserInterfaceContext.swift in Sources */, DC0A357424C0E42D00FB58FC /* PushTransition.swift in Sources */, DC0A357824C0E43700FB58FC /* CardPresentationController.swift in Sources */, diff --git a/ownCloud/Bookmarks/BookmarkViewController.swift b/ownCloud/Bookmarks/BookmarkViewController.swift index 482551d2a..3266faaa9 100644 --- a/ownCloud/Bookmarks/BookmarkViewController.swift +++ b/ownCloud/Bookmarks/BookmarkViewController.swift @@ -422,10 +422,12 @@ class BookmarkViewController: StaticTableViewController { if issue != nil { // Parse issue for display - if let displayIssues = issue?.prepareForDisplay() { - if displayIssues.displayLevel.rawValue >= OCIssueLevel.warning.rawValue { + if let issue = issue { + let displayIssues = issue.prepareForDisplay() + + if displayIssues.isAtLeast(level: .warning) { // Present issues if the level is >= warning - let issuesViewController = ConnectionIssueViewController(displayIssues: displayIssues, completion: { [weak self] (response) in + IssuesCardViewController.present(on: self, issue: issue, displayIssues: displayIssues, completion: { [weak self, weak issue] (response) in switch response { case .cancel: issue?.reject() @@ -439,11 +441,9 @@ class BookmarkViewController: StaticTableViewController { self?.bookmark?.url = nil } }) - - self.present(issuesViewController, animated: true, completion: nil) } else { // Do not present issues - issue?.approve() + issue.approve() continueToNextStep() } } @@ -502,8 +502,8 @@ class BookmarkViewController: StaticTableViewController { self.updateInputFocus(fallbackRow: self.passwordRow) } else if nsError?.isOCError(withCode: .authorizationCancelled) == true { // User cancelled authorization, no reaction needed - } else { - let issuesViewController = ConnectionIssueViewController(displayIssues: issue?.prepareForDisplay(), completion: { [weak self] (response) in + } else if let issue = issue { + IssuesCardViewController.present(on: self, issue: issue, completion: { [weak self, weak issue] (response) in switch response { case .cancel: issue?.reject() @@ -515,8 +515,6 @@ class BookmarkViewController: StaticTableViewController { case .dismiss: break } }) - - self.present(issuesViewController, animated: true, completion: nil) } }) } @@ -597,10 +595,10 @@ class BookmarkViewController: StaticTableViewController { } else { OnMainThread { hudCompletion({ - if issue != nil { + if let issue = issue { self?.bookmark?.authenticationData = nil - let issuesViewController = ConnectionIssueViewController(displayIssues: issue?.prepareForDisplay(), completion: { [weak self] (response) in + IssuesCardViewController.present(on: strongSelf, issue: issue, completion: { [weak self, weak issue] (response) in switch response { case .cancel: issue?.reject() @@ -612,8 +610,6 @@ class BookmarkViewController: StaticTableViewController { case .dismiss: break } }) - - strongSelf.present(issuesViewController, animated: true, completion: nil) } else { strongSelf.presentingViewController?.dismiss(animated: true, completion: nil) } diff --git a/ownCloud/Bookmarks/Issues/Issues Animators/IssuesDismissalAnimator.swift b/ownCloud/Bookmarks/Issues/Issues Animators/IssuesDismissalAnimator.swift deleted file mode 100644 index b552bba4f..000000000 --- a/ownCloud/Bookmarks/Issues/Issues Animators/IssuesDismissalAnimator.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// IssuesDismissalAnimator.swift -// ownCloud -// -// Created by Pablo Carrascal on 15/03/2018. -// Copyright © 2018 ownCloud GmbH. All rights reserved. -// - -import UIKit - -@objc public class IssuesDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning { - - public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.4 - } - - public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let fromVC = transitionContext.viewController(forKey: .from) - - transitionContext.containerView.addSubview(fromVC!.view) - - let initialFrame = transitionContext.initialFrame(for: fromVC!) - - let views = Array(fromVC!.view.subviews.reversed()) - var index = 0 - - let step: Double = 0.0 - - for view in views.reversed() { - let delay = step * Double(index) - UIView.animate(withDuration: self.transitionDuration(using: transitionContext) - delay, - delay: delay, - usingSpringWithDamping: 0.7, - initialSpringVelocity: 0.5, - options: [], - animations: { - view.transform = CGAffineTransform(translationX: 0, y: initialFrame.height) - }, completion: nil) - index += 1 - } - - let backgroundColor = fromVC?.view.backgroundColor! - - UIView.animate(withDuration: self.transitionDuration(using: transitionContext), - animations: { - fromVC?.view.backgroundColor = backgroundColor?.withAlphaComponent(0) - }, completion: { _ in - transitionContext.completeTransition(true) - }) - } -} diff --git a/ownCloud/Bookmarks/Issues/Issues Animators/IssuesPresentationAnimator.swift b/ownCloud/Bookmarks/Issues/Issues Animators/IssuesPresentationAnimator.swift deleted file mode 100644 index 66114b053..000000000 --- a/ownCloud/Bookmarks/Issues/Issues Animators/IssuesPresentationAnimator.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// IssuesPresentationAnimator.swift -// ownCloud -// -// Created by Pablo Carrascal on 15/03/2018. -// Copyright © 2018 ownCloud GmbH. All rights reserved. -// - -import UIKit - -@objc public class IssuesPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { - - public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.1 - } - - public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let fromVC = transitionContext.viewController(forKey: .from) - let toVC = transitionContext.viewController(forKey: .to) - - let finalFrame = transitionContext.initialFrame(for: fromVC!) - - toVC?.view.frame = finalFrame - - transitionContext.containerView.addSubview(toVC!.view) - - let views = toVC!.view.subviews - var index = 0 - - for view in views { - view.transform = CGAffineTransform(translationX: 0, y: finalFrame.height) - - let delay = 0.0 - UIView.animate(withDuration: self.transitionDuration(using: transitionContext) - delay, - delay: delay, - usingSpringWithDamping: 0.7, - initialSpringVelocity: 0.3, - options: [], - animations: { - view.transform = CGAffineTransform.identity - }, completion: nil) - - index += 1 - } - - let backgroundColor = toVC?.view.backgroundColor! - toVC?.view.backgroundColor = backgroundColor?.withAlphaComponent(0) - - UIView.animate(withDuration: self.transitionDuration(using: transitionContext), - animations: { - toVC?.view.backgroundColor = backgroundColor - }, completion: { _ in - transitionContext.completeTransition(true) - }) - } -} diff --git a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift b/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift deleted file mode 100644 index 184d261a4..000000000 --- a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// IssueViewController.swift -// ownCloud -// -// Created by Pablo Carrascal on 04/04/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 UIKit -import ownCloudSDK -import ownCloudUI -import ownCloudAppShared - -enum ConnectionResponse { - case cancel - case approve - case dismiss -} - -class ConnectionIssueViewController: IssuesViewController { - - private var displayIssues : DisplayIssues? - private var dismissedHandler : (() -> Void)? - - var bookmark : OCBookmark? - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - init(displayIssues issues: DisplayIssues?, buttons: [IssueButton]? = nil, title: String? = nil) { - super.init(buttons: buttons, title: title) - - displayIssues = issues - } - - convenience init(displayIssues issues: DisplayIssues?, title: String? = nil, bookmark: OCBookmark? = nil, completion:@escaping (ConnectionResponse) -> Void, dismissedHandler dismissedHandlerBlock: (() -> Void)? = nil) { - var useButtons : [IssueButton]? - var useTitle = title - - self.init(displayIssues: issues, buttons: nil, title: useTitle) - - self.bookmark = bookmark - self.dismissedHandler = dismissedHandlerBlock - - if let displayLevel = issues?.displayLevel { - switch displayLevel { - case .informal: - if title == nil { - useTitle = "Review Connection".localized - } - - useButtons = [ - IssueButton(title: "OK".localized, type: .approve, action: { [weak self] in - completion(.approve) - self?.dismiss(animated: true)}, accessibilityIdentifier: "ok-button") - ] - - case .warning: - if title == nil { - useTitle = "Review Connection".localized - } - - useButtons = [ - IssueButton(title: "Cancel".localized, type: .cancel, action: { [weak self] in - completion(.cancel) - self?.dismiss(animated: true)}, accessibilityIdentifier: "cancel-button"), - - IssueButton(title: "Approve".localized, type: .approve, action: { [weak self] in - completion(.approve) - self?.dismiss(animated: true)}, accessibilityIdentifier: "approve-button") - ] - - case .error: - if title == nil { - useTitle = "Error".localized - } - - useButtons = [ - IssueButton(title: "OK".localized, type: .approve, action: { [weak self] in - completion(.dismiss) - self?.dismiss(animated: true)}, accessibilityIdentifier: "ok-button") - ] - } - - self.headerTitle = useTitle - self.issueLevel = displayLevel - self.buttons = useButtons - } - } - - override func viewDidLoad() { - super.viewDidLoad() - self.tableView?.dataSource = self - } - - override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - super.dismiss(animated: flag) { - completion?() - self.dismissedHandler?() - } - } -} - -extension ConnectionIssueViewController { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let issue = displayIssues?.displayIssues[indexPath.row], issue.type == .certificate, let certificate = issue.certificate { - let certificateViewController = ThemeCertificateViewController(certificate: certificate, compare: bookmark?.certificate) - - if bookmark?.certificate != nil { - certificateViewController.showDifferences = true - } - - self.present(ThemeNavigationController(rootViewController: certificateViewController), animated: true, completion: nil) - } - } -} - -extension ConnectionIssueViewController: UITableViewDataSource { - - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return displayIssues?.displayIssues.count ?? 0 - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return super.tableView(tableView, heightForHeaderInSection: section) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: IssuesViewControllerCellIdentifier, for: indexPath) - let issue = (displayIssues?.displayIssues[indexPath.row])! - cell.detailTextLabel?.text = issue.localizedDescription - cell.textLabel?.numberOfLines = 0 - - var color: UIColor = .black - cell.selectionStyle = .none - - if issue.type == OCIssueType.certificate { - cell.accessoryType = .disclosureIndicator - cell.accessoryView?.backgroundColor = .blue - } else { - cell.accessoryType = .none - } - - switch issue.level { - case .warning: - color = Theme.shared.activeCollection.warningColor - case .informal: - color = Theme.shared.activeCollection.informativeColor - case .error: - color = Theme.shared.activeCollection.errorColor - } - - cell.textLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold) - cell.textLabel?.textColor = color - if issue.localizedTitle != headerTitle { - cell.textLabel?.text = issue.localizedTitle ?? "" - } - - cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 15, weight: .regular) - cell.detailTextLabel?.text = issue.localizedDescription ?? "" - - cell.detailTextLabel?.numberOfLines = 0 - cell.accessibilityIdentifier = "issue-row.\(indexPath.row)" - return cell - } -} diff --git a/ownCloud/Bookmarks/Issues/IssuesViewController.swift b/ownCloud/Bookmarks/Issues/IssuesViewController.swift deleted file mode 100644 index 6d8316760..000000000 --- a/ownCloud/Bookmarks/Issues/IssuesViewController.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// IssueViewController.swift -// ownCloud -// -// Created by Pablo Carrascal on 05/04/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 UIKit -import ownCloudAppShared -import ownCloudSDK - -enum IssueButtonStyle { - case plain - case approve - case cancel - case custom(backgroundColor: UIColor) -} - -struct IssueButton { - let title: String - let type: IssueButtonStyle - let action: () -> Void - let accessibilityIdentifier: String -} - -class IssuesTableViewCell : UITableViewCell, Themeable { - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) - - Theme.shared.register(client: self, applyImmediately: true) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - Theme.shared.unregister(client: self) - } - - func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - self.backgroundColor = collection.tableBackgroundColor - self.detailTextLabel?.textColor = collection.tableRowColors.labelColor - } -} - -let IssuesViewControllerCellIdentifier = "issue-cell" - -class IssuesViewController: UIViewController { - - var tableView: UITableView? - private var bottomContainer: UIStackView? - var headerTitle: String? - var issueLevel: OCIssueLevel? - var buttons:[IssueButton]? - private var tableHeighConstraint: NSLayoutConstraint? - private var modalPresentationVC: UIViewControllerTransitioningDelegate? - - init(buttons: [IssueButton]? = nil, title: String?) { - super.init(nibName: nil, bundle: nil) - self.headerTitle = title - self.buttons = buttons - setupTransitions() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5) - setupTableView() - setupBottomContainer() - tableView?.delegate = self - self.addButtons() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - setupConstraints() - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - self.tableHeighConstraint?.constant = self.tableView!.contentSize.height - } - - private func setupTableView() { - tableView = UITableView() - tableView?.layer.cornerRadius = 10 - tableView?.separatorInset = .zero - tableView?.bounces = false - tableView?.rowHeight = UITableView.automaticDimension - tableView?.register(IssuesTableViewCell.self, forCellReuseIdentifier: IssuesViewControllerCellIdentifier) - } - - private func setupBottomContainer() { - bottomContainer = UIStackView() - bottomContainer?.alignment = .fill - bottomContainer?.axis = .horizontal - bottomContainer?.distribution = .fillEqually - bottomContainer?.spacing = 20 - } - - func addButtons() { - if let buttonsToAdd = buttons { - var tag = 0 - buttonsToAdd.forEach({ - let button = UIButton(type: .system) - var color: UIColor = .blue - var backgroundColor: UIColor = .white - - switch $0.type { - case .approve: - backgroundColor = Theme.shared.activeCollection.approvalColors.normal.background - color = Theme.shared.activeCollection.approvalColors.normal.foreground - case .custom(let backColor): - backgroundColor = backColor - default: - backgroundColor = Theme.shared.activeCollection.tableRowColors.filledColorPairCollection.normal.background - color = Theme.shared.activeCollection.tableRowColors.filledColorPairCollection.normal.foreground - } - - button.backgroundColor = backgroundColor - button.setAttributedTitle(NSAttributedString(string: $0.title, attributes: [ - .foregroundColor : color, - .font : UIFont.systemFont(ofSize: 20, weight: .semibold) - ]), for: .normal) - button.setTitle($0.title, for: UIControl.State.normal) - button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside) - button.accessibilityIdentifier = $0.accessibilityIdentifier - button.layer.cornerRadius = 10 - button.tag = tag - tag += 1 - bottomContainer?.addArrangedSubview(button) - }) - } - } - - @objc func buttonPressed(_ button :UIButton) { - if let buttonPressed: IssueButton = buttons?[button.tag] { - buttonPressed.action() - } - } - - private func setupConstraints() { - bottomContainer?.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(bottomContainer!) - - bottomContainer?.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true - bottomContainer?.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 20).isActive = true - bottomContainer?.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: -20).isActive = true - bottomContainer?.heightAnchor.constraint(equalToConstant: 50).isActive = true - bottomContainer?.contentHuggingPriority(for: .vertical) - - tableView?.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(tableView!) - - tableView?.bottomAnchor.constraint(equalTo: bottomContainer!.topAnchor, constant: -20).isActive = true - tableView?.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 20).isActive = true - tableView?.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: -20).isActive = true - tableView?.topAnchor.constraint(greaterThanOrEqualTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true - tableHeighConstraint = tableView?.heightAnchor.constraint(equalToConstant: 0) - tableHeighConstraint?.priority = UILayoutPriority.defaultLow - tableHeighConstraint?.isActive = true - } - - private func setupTransitions() { - self.modalPresentationStyle = .overCurrentContext - let transitioningDLG = IssuesTransitioningDelegate() - self.modalPresentationVC = transitioningDLG - self.transitioningDelegate = transitioningDLG - } -} - -extension IssuesViewController: UITableViewDelegate { - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let cell = UITableViewCell(style: .default, reuseIdentifier: nil) - cell.textLabel?.text = headerTitle ?? "" - cell.textLabel?.font = UIFont.systemFont(ofSize: 20, weight: .semibold) - var textColor = Theme.shared.activeCollection.tableRowColors.labelColor - if issueLevel != nil { - switch issueLevel { - case .warning: - textColor = Theme.shared.activeCollection.warningColor - case .informal: - textColor = Theme.shared.activeCollection.informativeColor - case .error: - textColor = Theme.shared.activeCollection.errorColor - case .none, .some(_): break - } - } - cell.textLabel?.textColor = textColor - cell.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor - - let separatorView: UIView = UIView() - separatorView.translatesAutoresizingMaskIntoConstraints = false - separatorView.backgroundColor = Theme.shared.activeCollection.tableSeparatorColor - cell.addSubview(separatorView) - separatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true - separatorView.leftAnchor.constraint(equalTo: cell.leftAnchor, constant: 0).isActive = true - separatorView.rightAnchor.constraint(equalTo: cell.rightAnchor, constant: 0).isActive = true - separatorView.bottomAnchor.constraint(equalTo: cell.bottomAnchor, constant: 0).isActive = true - return cell - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 40 - } - -} - -internal class IssuesTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return IssuesPresentationAnimator() - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return IssuesDismissalAnimator() - } -} diff --git a/ownCloud/Client/Actions/Action+UserInterface.swift b/ownCloud/Client/Actions/Action+UserInterface.swift index 03986b644..2b7cd2ae8 100644 --- a/ownCloud/Client/Actions/Action+UserInterface.swift +++ b/ownCloud/Client/Actions/Action+UserInterface.swift @@ -29,7 +29,7 @@ extension Action { let tableViewController = MoreStaticTableViewController(style: .grouped) let header = MoreViewHeader(for: item, with: core) - let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) + let moreViewController = FrameViewController(header: header, viewController: tableViewController) if core.connectionStatus == .online { if core.connection.capabilities?.sharingAPIEnabled == 1 { @@ -103,7 +103,7 @@ extension Action { } else { let offersViewController = LicenseOffersViewController(withFeature: requirements.feature, in: core.licenseEnvironment) - viewController.present(asCard: MoreViewController(header: offersViewController.cardHeaderView!, viewController: offersViewController), animated: true) + viewController.present(asCard: FrameViewController(header: offersViewController.cardHeaderView!, viewController: offersViewController), animated: true) } } }) @@ -200,7 +200,7 @@ private extension Action { return shareRows } - private class func updateSharingSection(sectionIdentifier: String, rows: [StaticTableViewRow], tableViewController: MoreStaticTableViewController, contentViewController: MoreViewController) { + private class func updateSharingSection(sectionIdentifier: String, rows: [StaticTableViewRow], tableViewController: MoreStaticTableViewController, contentViewController: FrameViewController) { if let section = tableViewController.sectionForIdentifier(sectionIdentifier) { tableViewController.removeSection(section) } diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 0d3236fc0..dbe67e7a0 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -587,11 +587,23 @@ extension ClientRootViewController : OCCoreDelegate { if presentIssue != nil { var presentViewController : UIViewController? + var onViewController : UIViewController? - if presentIssue?.type == .multipleChoice { - presentViewController = ThemedAlertController(with: presentIssue!, completion: queueCompletionHandler) - } else { - presentViewController = ConnectionIssueViewController(displayIssues: presentIssue?.prepareForDisplay(), bookmark: self?.bookmark, completion: { (response) in + if let startViewController = self { + var hostViewController : UIViewController = startViewController + + while hostViewController.presentedViewController != nil, + hostViewController.presentedViewController?.isBeingDismissed == false { + hostViewController = hostViewController.presentedViewController! + } + + onViewController = hostViewController + } + + if let presentIssue = presentIssue, presentIssue.type == .multipleChoice { + presentViewController = ThemedAlertController(with: presentIssue, completion: queueCompletionHandler) + } else if let onViewController = onViewController, let presentIssue = presentIssue { + IssuesCardViewController.present(on: onViewController, issue: presentIssue, bookmark: self?.bookmark, completion: { [weak presentIssue] (response) in switch response { case .cancel: presentIssue?.reject() @@ -603,19 +615,13 @@ extension ClientRootViewController : OCCoreDelegate { } queueCompletionHandler() }) - } - - if presentViewController != nil, let startViewController = self { - var hostViewController : UIViewController = startViewController - - while hostViewController.presentedViewController != nil, - hostViewController.presentedViewController?.isBeingDismissed == false { - hostViewController = hostViewController.presentedViewController! - } queueCompletionHandlerScheduled = true + } - hostViewController.present(presentViewController!, animated: true, completion: nil) + if let presentViewController = presentViewController, let onViewController = onViewController { + queueCompletionHandlerScheduled = true + onViewController.present(presentViewController, animated: true, completion: nil) } } diff --git a/ownCloud/Diagnostic/DiagnosticManager.swift b/ownCloud/Diagnostic/DiagnosticManager.swift index 18dcc1f2c..9163c2178 100644 --- a/ownCloud/Diagnostic/DiagnosticManager.swift +++ b/ownCloud/Diagnostic/DiagnosticManager.swift @@ -50,7 +50,7 @@ class DiagnosticManager: NSObject, OCClassSettingsSupport, OCClassSettingsUserPr .description : "Controls whether additional diagnostic options and information is available throughout the user interface.", .category : "Diagnostics", .status : OCClassSettingsKeyStatus.advanced - ], + ] ] } diff --git a/ownCloud/Import/ImportFilesController.swift b/ownCloud/Import/ImportFilesController.swift index 87eed745f..4e9b63265 100644 --- a/ownCloud/Import/ImportFilesController.swift +++ b/ownCloud/Import/ImportFilesController.swift @@ -254,12 +254,12 @@ extension ImportFilesController { }) } - func cardViewController(for url: URL) -> MoreViewController? { + func cardViewController(for url: URL) -> FrameViewController? { let tableViewController = MoreStaticTableViewController(style: .grouped) header = MoreViewHeader(url: url) guard let header = header else { return nil } - let moreViewController = MoreViewController(header: header, viewController: tableViewController) + let moreViewController = FrameViewController(header: header, viewController: tableViewController) let title = NSAttributedString(string: "Save File".localized, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) diff --git a/ownCloud/Issues/IssuesCardViewController.swift b/ownCloud/Issues/IssuesCardViewController.swift new file mode 100644 index 000000000..bc7af305a --- /dev/null +++ b/ownCloud/Issues/IssuesCardViewController.swift @@ -0,0 +1,238 @@ +// +// IssuesCardViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 22.01.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +class CardCellBackgroundView : UIView { + init(backgroundColor: UIColor, insets: NSDirectionalEdgeInsets, cornerRadius: CGFloat) { + super.init(frame: .zero) + + let backgroundView = UIView(frame: .zero) + backgroundView.translatesAutoresizingMaskIntoConstraints = false + + backgroundView.layer.backgroundColor = backgroundColor.cgColor + backgroundView.layer.cornerRadius = cornerRadius + + addSubview(backgroundView) + + NSLayoutConstraint.activate([ + backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.leading), + backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.trailing), + backgroundView.topAnchor.constraint(equalTo: topAnchor, constant: insets.top), + backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class CardHeaderView : UIView, Themeable { + var label : UILabel + + init(title: String) { + label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + + label.text = title + label.setContentHuggingPriority(.required, for: .vertical) + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + label.setContentCompressionResistancePriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + + super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + + translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15), + label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -15), + label.topAnchor.constraint(equalTo: self.topAnchor, constant: 10), + label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + Theme.shared.unregister(client: self) + } + + override func didMoveToSuperview() { + super.didMoveToSuperview() + + if self.superview != nil { + Theme.shared.register(client: self) + } + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + label.font = UIFont.systemFont(ofSize: UIFont.systemFontSize * 1.4, weight: .bold) + label.textColor = collection.tableRowColors.labelColor + + self.backgroundColor = collection.tableBackgroundColor + } +} + +enum IssueUserResponse { + case cancel + case approve + case dismiss +} + +class IssuesCardViewController: StaticTableViewController { + typealias CompletionHandler = (IssueUserResponse) -> Void + typealias DismissHandler = () -> Void + + var issues : DisplayIssues + var headerTitle : String? + var options : [AlertOption] + weak var alertView : AlertView? + + private var completionHandler : CompletionHandler? + private var dismissHandler : DismissHandler? + + required init(with issue: OCIssue, displayIssues: DisplayIssues? = nil, bookmark: OCBookmark? = nil, completion:@escaping CompletionHandler, dismissed: DismissHandler? = nil) { + issues = (displayIssues != nil) ? displayIssues! : issue.prepareForDisplay() + options = [] + completionHandler = completion + dismissHandler = dismissed + + super.init(style: .plain) + + headerTitle = "Review Connection".localized + + switch issues.displayLevel { + case .informal: + options = [ + AlertOption(label: "OK".localized, type: .default, accessibilityIdentifier: "ok-button", handler: { [weak self] (_, _) in + self?.complete(with: .approve) + }) + ] + + case .warning: + options = [ + AlertOption(label: "Cancel".localized, type: .cancel, accessibilityIdentifier: "cancel-button", handler: { [weak self] (_, _) in + self?.complete(with: .cancel) + }), + + AlertOption(label: "Approve".localized, type: .regular, accessibilityIdentifier: "approve-button", handler: { [weak self] (_, _) in + self?.complete(with: .approve) + }) + ] + + case .error: + headerTitle = "Issues".localized + + options = [ + AlertOption(label: "OK".localized, type: .cancel, accessibilityIdentifier: "ok-button", handler: { [weak self] (_, _) in + self?.complete(with: .dismiss) + }) + ] + } + + let section = StaticTableViewSection() + + let cellStyler : ThemeTableViewCell.CellStyler = { (cell, style) in + cell.textLabel?.textColor = style.textColor + cell.textLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize * 1.1, weight: .semibold) + + cell.detailTextLabel?.textColor = style.textColor + cell.detailTextLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize, weight: .regular) + + if let backgroundColor = style.backgroundColor { + let edgeInsets = NSDirectionalEdgeInsets(top: 2, leading: 15, bottom: 2, trailing: 15) + + cell.backgroundView = CardCellBackgroundView(backgroundColor: backgroundColor, insets: edgeInsets, cornerRadius: 5) + cell.selectedBackgroundView = CardCellBackgroundView(backgroundColor: backgroundColor.darker(0.07), insets: edgeInsets, cornerRadius: 5) + } + + return true + } + + for issue in issues.displayIssues { + if let issueTitle = issue.localizedTitle { + var messageStyle : StaticTableViewRowMessageStyle? + + switch issue.level { + + case .informal: + messageStyle = .plain + + case .warning: + messageStyle = .warning + + case .error: + messageStyle = .alert + } + + let row = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in + if issue.type == .certificate, let certificate = issue.certificate { + let certificateViewController = ThemeCertificateViewController(certificate: certificate, compare: bookmark?.certificate) + + if bookmark?.certificate != nil { + certificateViewController.showDifferences = true + } + + self?.present(ThemeNavigationController(rootViewController: certificateViewController), animated: true, completion: nil) + } + }, title: issueTitle, subtitle: issue.localizedDescription, messageStyle: messageStyle, accessoryType: (issue.type == .certificate) ? .disclosureIndicator : .none ) + + (row.cell as? ThemeTableViewCell)?.cellStyler = cellStyler + + section.add(row: row) + } + } + + self.addSection(section) + + self.tableView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 25, bottom: 2, trailing: 25) + self.tableView.preservesSuperviewLayoutMargins = true + self.tableView.separatorStyle = .none + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + static func present(on hostViewController: UIViewController, issue: OCIssue, displayIssues: DisplayIssues? = nil, bookmark: OCBookmark? = nil, completion:@escaping CompletionHandler, dismissed: DismissHandler? = nil) { + let issuesViewController = self.init(with: issue, displayIssues: displayIssues, bookmark: bookmark, completion: completion, dismissed: dismissed) + + let headerView = CardHeaderView(title: issuesViewController.headerTitle ?? "") + let alertView = AlertView(localizedTitle: "", localizedDescription: "", contentPadding: 15, options: issuesViewController.options) + + let frameViewController = FrameViewController(header: headerView, footer: alertView, viewController: issuesViewController) + + alertView.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor + issuesViewController.alertView = alertView + + hostViewController.present(asCard: frameViewController, animated: true, withHandle: false, dismissable: false) { + _ = frameViewController.view + } + } + + func complete(with result: IssueUserResponse) { + completionHandler?(result) + completionHandler = nil + + self.presentingViewController?.dismiss(animated: true) + } + + override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + super.applyThemeCollection(theme: theme, collection: collection, event: event) + + tableView.backgroundColor = collection.tableBackgroundColor + alertView?.backgroundColor = collection.tableBackgroundColor + } +} diff --git a/ownCloud/Key Commands/KeyCommands.swift b/ownCloud/Key Commands/KeyCommands.swift index 274a341f9..f01dcaf12 100644 --- a/ownCloud/Key Commands/KeyCommands.swift +++ b/ownCloud/Key Commands/KeyCommands.swift @@ -123,34 +123,6 @@ extension BookmarkViewController { } } -extension IssuesViewController { - override var keyCommands: [UIKeyCommand]? { - var shortcuts = [UIKeyCommand]() - - if let buttons = buttons { - var counter = 1 - for button in buttons { - let command = UIKeyCommand(input: String(counter), modifierFlags: [.command], action: #selector(issueButtonPressed), discoverabilityTitle: button.title) - shortcuts.append(command) - counter += 1 - } - } - - return shortcuts - } - - @objc func issueButtonPressed(_ command : UIKeyCommand) { - guard let button = buttons?.first(where: {$0.title == command.discoverabilityTitle}) else { return } - - let buttonPressed: IssueButton = button - buttonPressed.action() - } - - override var canBecomeFirstResponder: Bool { - return true - } -} - extension UIAlertController { typealias AlertHandler = @convention(block) (UIAlertAction) -> Void @@ -1130,11 +1102,25 @@ extension AlertViewController { } } -extension MoreViewController { +extension FrameViewController { open override var keyCommands: [UIKeyCommand]? { var shortcuts = [UIKeyCommand]() - let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissCard), discoverabilityTitle: "Close".localized) - shortcuts.append(cancelCommand) + + if let issuesCard = self.viewController as? IssuesCardViewController { + if let buttons = issuesCard.alertView?.optionViews { + var counter = 1 + for button in buttons { + if let buttonTitle = button.currentTitle { + let command = UIKeyCommand(input: String(counter), modifierFlags: [.command], action: #selector(issueButtonPressed), discoverabilityTitle: buttonTitle) + shortcuts.append(command) + } + counter += 1 + } + } + } else { + let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissCard), discoverabilityTitle: "Close".localized) + shortcuts.append(cancelCommand) + } return shortcuts } @@ -1146,4 +1132,12 @@ extension MoreViewController { @objc func dismissCard(_ sender: Any?) { self.dismiss(animated: false, completion: nil) } + + @objc func issueButtonPressed(_ command : UIKeyCommand) { + if let issuesCard = self.viewController as? IssuesCardViewController, let alertView = issuesCard.alertView { + guard let button = alertView.optionViews.first(where: {$0.currentTitle == command.discoverabilityTitle}) else { return } + + alertView.optionSelected(sender: button) + } + } } diff --git a/ownCloud/Licensing/Product List/LicenseInAppPurchaseFeatureView.swift b/ownCloud/Licensing/Product List/LicenseInAppPurchaseFeatureView.swift index 64acc8953..37f7afd61 100644 --- a/ownCloud/Licensing/Product List/LicenseInAppPurchaseFeatureView.swift +++ b/ownCloud/Licensing/Product List/LicenseInAppPurchaseFeatureView.swift @@ -131,6 +131,6 @@ class LicenseInAppPurchaseFeatureView: UIView, Themeable { @objc func takeOffer() { let offersViewController = LicenseOffersViewController(withFeature: feature.identifier, in: environment) - baseViewController?.present(asCard: MoreViewController(header: offersViewController.cardHeaderView!, viewController: offersViewController), animated: true) + baseViewController?.present(asCard: FrameViewController(header: offersViewController.cardHeaderView!, viewController: offersViewController), animated: true) } } diff --git a/ownCloud/Messages/AlertView.swift b/ownCloud/Messages/AlertView.swift index ef6bc73c4..232039ccd 100644 --- a/ownCloud/Messages/AlertView.swift +++ b/ownCloud/Messages/AlertView.swift @@ -26,11 +26,13 @@ class AlertOption : NSObject { var label : String var handler : ChoiceHandler var type : OCIssueChoiceType + var accessibilityIdentifier : String? - init(label: String, type: OCIssueChoiceType, handler: @escaping ChoiceHandler) { + init(label: String, type: OCIssueChoiceType, accessibilityIdentifier : String? = nil, handler: @escaping ChoiceHandler) { self.label = label self.type = type self.handler = handler + self.accessibilityIdentifier = accessibilityIdentifier super.init() } @@ -53,11 +55,12 @@ class AlertView: UIView, Themeable { var optionViews : [ThemeButton] = [] - init(localizedHeader: String? = nil, localizedTitle: String, localizedDescription: String, options: [AlertOption]) { + init(localizedHeader: String? = nil, localizedTitle: String, localizedDescription: String, contentPadding: CGFloat = 20, options: [AlertOption]) { self.localizedHeader = localizedHeader self.localizedTitle = localizedTitle self.localizedDescription = localizedDescription self.options = options + self.contentPadding = contentPadding super.init(frame: .zero) @@ -83,10 +86,9 @@ class AlertView: UIView, Themeable { optionButton.setTitle(option.label, for: .normal) optionButton.tag = optionIdx optionButton.translatesAutoresizingMaskIntoConstraints = false + optionButton.accessibilityIdentifier = option.accessibilityIdentifier -// optionButton.setContentHuggingPriority(.defaultLow, for: .horizontal) optionButton.setContentHuggingPriority(.required, for: .vertical) -// optionButton.setContentCompressionResistancePriority(.required, for: .horizontal) optionButton.setContentCompressionResistancePriority(.required, for: .vertical) optionButton.addTarget(self, action: #selector(optionSelected(sender:)), for: .primaryActionTriggered) @@ -110,7 +112,7 @@ class AlertView: UIView, Themeable { private let headerTextHorizontalInset : CGFloat = 20 private let headerTextVerticalInset : CGFloat = 7 private let titleAndDescriptionSpacing : CGFloat = 5 - private let contentPadding : CGFloat = 20 + private var contentPadding : CGFloat = 20 private let optionInnerSpacing : CGFloat = 10 private let headerLabelFontSize : CGFloat = 14 @@ -176,32 +178,45 @@ class AlertView: UIView, Themeable { self.addSubview(headerContainer) NSLayoutConstraint.activate([ - headerLabel.leftAnchor.constraint(equalTo: headerContainer.leftAnchor, constant: headerTextHorizontalInset), - headerLabel.rightAnchor.constraint(equalTo: headerContainer.rightAnchor, constant: -headerTextHorizontalInset), + headerLabel.leadingAnchor.constraint(equalTo: headerContainer.leadingAnchor, constant: headerTextHorizontalInset), + headerLabel.trailingAnchor.constraint(equalTo: headerContainer.trailingAnchor, constant: -headerTextHorizontalInset), headerLabel.topAnchor.constraint(equalTo: headerContainer.topAnchor, constant: headerTextVerticalInset), headerLabel.bottomAnchor.constraint(equalTo: headerContainer.bottomAnchor, constant: -headerTextVerticalInset), headerContainer.topAnchor.constraint(equalTo: enclosure.topAnchor), - headerContainer.leftAnchor.constraint(equalTo: enclosure.leftAnchor), - headerContainer.rightAnchor.constraint(equalTo: enclosure.rightAnchor) + headerContainer.leadingAnchor.constraint(equalTo: enclosure.leadingAnchor), + headerContainer.trailingAnchor.constraint(equalTo: enclosure.trailingAnchor) ]) } - NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: ((localizedHeader != nil) ? headerContainer.bottomAnchor : enclosure.topAnchor), constant: contentPadding), - titleLabel.bottomAnchor.constraint(equalTo: descriptionLabel.topAnchor, constant: -titleAndDescriptionSpacing), - descriptionLabel.bottomAnchor.constraint(equalTo: optionStackView.topAnchor, constant: -contentPadding), - optionStackView.bottomAnchor.constraint(equalTo: enclosure.bottomAnchor, constant: -contentPadding), + if localizedHeader == nil, localizedTitle == "", localizedDescription == "" { + titleLabel.removeFromSuperview() + descriptionLabel.removeFromSuperview() - titleLabel.leftAnchor.constraint(equalTo: enclosure.leftAnchor, constant: contentPadding), - titleLabel.rightAnchor.constraint(equalTo: enclosure.rightAnchor, constant: -contentPadding), + NSLayoutConstraint.activate([ + optionStackView.topAnchor.constraint(equalTo: enclosure.topAnchor, constant: contentPadding), + optionStackView.bottomAnchor.constraint(equalTo: enclosure.bottomAnchor, constant: -contentPadding), + + optionStackView.leadingAnchor.constraint(equalTo: enclosure.leadingAnchor, constant: contentPadding), + optionStackView.trailingAnchor.constraint(equalTo: enclosure.trailingAnchor, constant: -contentPadding) + ]) + } else { + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: ((localizedHeader != nil) ? headerContainer.bottomAnchor : enclosure.topAnchor), constant: contentPadding), + titleLabel.bottomAnchor.constraint(equalTo: descriptionLabel.topAnchor, constant: -titleAndDescriptionSpacing), + descriptionLabel.bottomAnchor.constraint(equalTo: optionStackView.topAnchor, constant: -contentPadding), + optionStackView.bottomAnchor.constraint(equalTo: enclosure.bottomAnchor, constant: -contentPadding), - descriptionLabel.leftAnchor.constraint(equalTo: enclosure.leftAnchor, constant: contentPadding), - descriptionLabel.rightAnchor.constraint(equalTo: enclosure.rightAnchor, constant: -contentPadding), + titleLabel.leadingAnchor.constraint(equalTo: enclosure.leadingAnchor, constant: contentPadding), + titleLabel.trailingAnchor.constraint(equalTo: enclosure.trailingAnchor, constant: -contentPadding), - optionStackView.leftAnchor.constraint(equalTo: enclosure.leftAnchor, constant: contentPadding), - optionStackView.rightAnchor.constraint(equalTo: enclosure.rightAnchor, constant: -contentPadding) - ]) + descriptionLabel.leadingAnchor.constraint(equalTo: enclosure.leadingAnchor, constant: contentPadding), + descriptionLabel.trailingAnchor.constraint(equalTo: enclosure.trailingAnchor, constant: -contentPadding), + + optionStackView.leadingAnchor.constraint(equalTo: enclosure.leadingAnchor, constant: contentPadding), + optionStackView.trailingAnchor.constraint(equalTo: enclosure.trailingAnchor, constant: -contentPadding) + ]) + } } func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { diff --git a/ownCloud/Migration/Migration.swift b/ownCloud/Migration/Migration.swift index 5e70277e4..4a6c3ed3a 100644 --- a/ownCloud/Migration/Migration.swift +++ b/ownCloud/Migration/Migration.swift @@ -261,7 +261,7 @@ class Migration { let displayIssues = issue.prepareForDisplay() - guard displayIssues.displayLevel.rawValue >= OCIssueLevel.warning.rawValue else { + guard displayIssues.isAtLeast(level: .warning) else { connectGroup.leave() return } @@ -269,7 +269,7 @@ class Migration { if let parentViewController = parentViewController { // Present issues if the level is >= warning DispatchQueue.main.async { - let issuesViewController = ConnectionIssueViewController(displayIssues: displayIssues, completion: { (response) in + IssuesCardViewController.present(on: parentViewController, issue: issue, displayIssues: displayIssues, completion: { (response) in Log.debug(tagged: ["MIGRATION"], "User responded to the issue with \(response)") switch response { @@ -285,7 +285,6 @@ class Migration { connectGroup.leave() }) - parentViewController.present(issuesViewController, animated: true, completion: nil) Log.debug(tagged: ["MIGRATION"], "Presenting the issue to the user") } } else { diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 89a2321d7..ada0dfa1b 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -34,6 +34,7 @@ "Cancel" = "Cancel"; "Approve" = "Approve"; "Error" = "Error"; +"Issues" = "Issues"; "Review Connection" = "Review Connection"; "Authenticated via" = "Authenticated via"; "Authenticated as %@ via %@" = "Authenticated as %@ via %@"; diff --git a/ownCloud/SDK Extensions/OCIssue+Extension.swift b/ownCloud/SDK Extensions/OCIssue+Extension.swift index 7efb21681..e9cbc3d7e 100644 --- a/ownCloud/SDK Extensions/OCIssue+Extension.swift +++ b/ownCloud/SDK Extensions/OCIssue+Extension.swift @@ -24,6 +24,10 @@ struct DisplayIssues { var displayLevel : OCIssueLevel //!< The issue level to be used for display var displayIssues: [OCIssue] //!< The selection of issues to be used for display var primaryCertificate : OCCertificate? //!< The first certificate found among the issues + + func isAtLeast(level minLevel: OCIssueLevel) -> Bool { + return displayLevel.rawValue >= minLevel.rawValue + } } extension OCIssue { diff --git a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift index e50edd29f..d7d7d6c07 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift @@ -365,23 +365,25 @@ class StaticLoginSetupViewController : StaticLoginStepViewController { self.passwordRow?.textField?.becomeFirstResponder() } } else { - let issuesViewController = ConnectionIssueViewController(displayIssues: issue?.prepareForDisplay(), completion: { [weak self] (response) in - switch response { - case .cancel: - issue?.reject() + if let loginViewController = self.loginViewController, let issue = issue { - case .approve: - issue?.approve() - self?.startAuthentication(nil) - - case .dismiss: break + if let busySection = self.busySection, busySection.attached { + self.removeSection(busySection) } - }) - if let busySection = self.busySection, busySection.attached { - self.removeSection(busySection) + IssuesCardViewController.present(on: loginViewController, issue: issue, completion: { [weak self, weak issue] (response) in + switch response { + case .cancel: + issue?.reject() + + case .approve: + issue?.approve() + self?.startAuthentication(nil) + + case .dismiss: break + } + }) } - self.loginViewController?.present(issuesViewController, animated: true, completion: nil) } } }) @@ -512,28 +514,29 @@ class StaticLoginSetupViewController : StaticLoginStepViewController { func show(issue: OCIssue?, proceed: (() -> Void)? = nil, cancel: (() -> Void)? = nil) -> Bool { if let displayIssues = issue?.prepareForDisplay() { - if displayIssues.displayLevel.rawValue >= OCIssueLevel.warning.rawValue { + if displayIssues.isAtLeast(level: .warning) { // Present issues if the level is >= warning OnMainThread { - let issuesViewController = ConnectionIssueViewController(displayIssues: displayIssues, completion: { (response) in - switch response { - case .cancel: - issue?.reject() - cancel?() - - case .approve: - issue?.approve() - proceed?() - - case .dismiss: - cancel?() + if let loginViewController = self.loginViewController, let issue = issue { + if let busySection = self.busySection, busySection.attached { + self.removeSection(busySection) } - }) - if let busySection = self.busySection, busySection.attached { - self.removeSection(busySection) + IssuesCardViewController.present(on: loginViewController, issue: issue, displayIssues: displayIssues, completion: { [weak issue] (response) in + switch response { + case .cancel: + issue?.reject() + cancel?() + + case .approve: + issue?.approve() + proceed?() + + case .dismiss: + cancel?() + } + }) } - self.loginViewController?.present(issuesViewController, animated: true, completion: nil) } return false diff --git a/ownCloudAppShared/User Interface/Card Presentation Controller/CardPresentationController.swift b/ownCloudAppShared/User Interface/Card Presentation Controller/CardPresentationController.swift index d00f72881..993e91f82 100644 --- a/ownCloudAppShared/User Interface/Card Presentation Controller/CardPresentationController.swift +++ b/ownCloudAppShared/User Interface/Card Presentation Controller/CardPresentationController.swift @@ -288,7 +288,9 @@ final class CardPresentationController: UIPresentationController, Themeable { switch velocity { case _ where velocity >= 2000: - dismissView() + if dismissable { + dismissView() + } case _ where velocity < 0: if distanceFromBottom > windowFrame.height * (CardPosition.open.heightMultiplier - 0.3) { @@ -302,7 +304,7 @@ final class CardPresentationController: UIPresentationController, Themeable { nextPosition = .open } else if distanceFromBottom > windowFrame.height * (CardPosition.half.heightMultiplier - 0.1) { nextPosition = .half - } else { + } else if dismissable { dismissView() } @@ -384,9 +386,12 @@ extension UIViewController { open func present(asCard viewController: UIViewController, animated: Bool, withHandle: Bool = true, dismissable: Bool = true, completion: (() -> Void)? = nil) { let animator = CardTransitionDelegate(viewControllerToPresent: viewController, presentingViewController: self, withHandle: withHandle, dismissable: dismissable) - viewController.transitioningDelegate = animator + viewController.transitioningDelegate = animator // .transitioningDelegate is only weak! viewController.modalPresentationStyle = .custom - present(viewController, animated: animated, completion: completion) + present(viewController, animated: animated, completion: { + _ = animator // Keep reference to CardTransitionDelegate around (could be dropped prematurely otherwise) + completion?() + }) } } diff --git a/ownCloudAppShared/User Interface/More/MoreViewController.swift b/ownCloudAppShared/User Interface/More/FrameViewController.swift similarity index 75% rename from ownCloudAppShared/User Interface/More/MoreViewController.swift rename to ownCloudAppShared/User Interface/More/FrameViewController.swift index 988893f50..e9785e3e7 100644 --- a/ownCloudAppShared/User Interface/More/MoreViewController.swift +++ b/ownCloudAppShared/User Interface/More/FrameViewController.swift @@ -1,5 +1,5 @@ // -// MoreViewController.swift +// FrameViewController.swift // ownCloud // // Created by Pablo Carrascal on 25/07/2018. @@ -17,15 +17,12 @@ */ import UIKit -import ownCloudSDK -open class MoreViewController: UIViewController, CardPresentationSizing { +open class FrameViewController: UIViewController, CardPresentationSizing { - private var item: OCItem? - private weak var core: OCCore? - - private var headerView: UIView - private var viewController: UIViewController + public var headerView: UIView + public var footerView: UIView? + public var viewController: UIViewController public var fitsOnScreen : Bool = false { didSet { @@ -35,17 +32,9 @@ open class MoreViewController: UIViewController, CardPresentationSizing { } } - public init(item: OCItem, core: OCCore, header: UIView, viewController: UIViewController) { - self.item = item - self.core = core - self.headerView = header - self.viewController = viewController - - super.init(nibName: nil, bundle: nil) - } - - public init(header: UIView, viewController: UIViewController) { + public init(header: UIView, footer: UIView? = nil, viewController: UIViewController) { self.headerView = header + self.footerView = footer self.viewController = viewController super.init(nibName: nil, bundle: nil) @@ -75,13 +64,28 @@ open class MoreViewController: UIViewController, CardPresentationSizing { headerView.topAnchor.constraint(equalTo: view.topAnchor) ]) + var viewControllerBottomConstraint = view.bottomAnchor + + if let footerView = footerView { + footerView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(footerView) + NSLayoutConstraint.activate([ + footerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + footerView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + + viewControllerBottomConstraint = footerView.topAnchor + } + self.addChild(viewController) view.addSubview(viewController.view) viewController.didMove(toParent: self) viewController.view.translatesAutoresizingMaskIntoConstraints = false - let bottomConstraint = viewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + let bottomConstraint = viewController.view.bottomAnchor.constraint(equalTo: viewControllerBottomConstraint) bottomConstraint.priority = UILayoutPriority(rawValue: 999) NSLayoutConstraint.activate([ @@ -105,14 +109,19 @@ open class MoreViewController: UIViewController, CardPresentationSizing { if self.view != nil { let headerSize = headerView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow) + var footerSize : CGSize = .zero + + if let footerView = footerView { + footerSize = footerView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow) + } size.width = targetSize.width - size.height = headerSize.height + size.height = headerSize.height + footerSize.height if let scrollView = viewController.view as? UIScrollView { size.height += scrollView.contentSize.height } else { - let bodySize = viewController.view.systemLayoutSizeFitting(CGSize(width: targetSize.width, height: targetSize.height-headerSize.height), + let bodySize = viewController.view.systemLayoutSizeFitting(CGSize(width: targetSize.width, height: targetSize.height-headerSize.height-footerSize.height), withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority) size.height += bodySize.height @@ -131,7 +140,7 @@ open class MoreViewController: UIViewController, CardPresentationSizing { } } -extension MoreViewController: Themeable { +extension FrameViewController: Themeable { public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { self.headerView.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor } diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index 7c4d72a2d..eaa24d56b 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -125,7 +125,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { super.init() } - convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, messageStyle: StaticTableViewRowMessageStyle? = nil, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .row @@ -140,7 +140,10 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { cellStyle = UITableViewCell.CellStyle.subtitle } - self.cell = ThemeTableViewCell(withLabelColorUpdates: true, style: cellStyle, reuseIdentifier: nil) + let themeCell = ThemeTableViewCell(withLabelColorUpdates: true, style: cellStyle, reuseIdentifier: nil) + themeCell.messageStyle = messageStyle + + self.cell = themeCell if subtitle != nil { self.cell?.detailTextLabel?.text = subtitle self.cell?.detailTextLabel?.numberOfLines = 0 diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift index 6c4f304f8..2683f2c14 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift @@ -21,7 +21,20 @@ import UIKit open class ThemeTableViewCell: UITableViewCell, Themeable { private var themeRegistered = false - var updateLabelColors : Bool = true + public var updateLabelColors : Bool = true + + public var messageStyle : StaticTableViewRowMessageStyle? + + public struct CellStyleSet { + public var theme: Theme + public var collection: ThemeCollection + public var backgroundColor: UIColor? + public var textColor: UIColor? + } + + public typealias CellStyler = (_ cell: ThemeTableViewCell, _ styleSet: CellStyleSet) -> Bool + + public var cellStyler : CellStyler? override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -56,8 +69,45 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { let state = ThemeItemState(selected: self.isSelected) if updateLabelColors { - self.textLabel?.applyThemeCollection(collection, itemStyle: .defaultForItem, itemState: state) - self.detailTextLabel?.applyThemeCollection(collection, itemStyle: .message, itemState: state) + if let messageStyle = messageStyle { + var textColor, backgroundColor : UIColor? + var doStyle = true + + switch messageStyle { + case .plain: + textColor = collection.tintColor + backgroundColor = collection.tableRowColors.backgroundColor + + case .confirmation: + textColor = collection.approvalColors.normal.foreground + backgroundColor = collection.approvalColors.normal.background + + case .warning: + textColor = .black + backgroundColor = .systemYellow + + case .alert: + textColor = collection.destructiveColors.normal.foreground + backgroundColor = collection.destructiveColors.normal.background + + case let .custom(customTextColor, customBackgroundColor, _): + textColor = customTextColor + backgroundColor = customBackgroundColor + } + + if let cellStyler = cellStyler, cellStyler(self, CellStyleSet(theme: theme, collection: collection, backgroundColor: backgroundColor, textColor: textColor)) { + doStyle = false + } + + if doStyle { + self.textLabel?.textColor = textColor + self.detailTextLabel?.textColor = textColor + self.backgroundColor = backgroundColor + } + } else { + self.textLabel?.applyThemeCollection(collection, itemStyle: .defaultForItem, itemState: state) + self.detailTextLabel?.applyThemeCollection(collection, itemStyle: .message, itemState: state) + } } } From 6f05d217aef9394204476cc699bb483880eb9337 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Sat, 23 Jan 2021 13:03:44 +0100 Subject: [PATCH 4/6] - add missing licensing comment --- ownCloud/Issues/IssuesCardViewController.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ownCloud/Issues/IssuesCardViewController.swift b/ownCloud/Issues/IssuesCardViewController.swift index bc7af305a..acf2934ab 100644 --- a/ownCloud/Issues/IssuesCardViewController.swift +++ b/ownCloud/Issues/IssuesCardViewController.swift @@ -5,6 +5,15 @@ // Created by Felix Schwarz on 22.01.21. // Copyright © 2021 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2021, 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 b8e9aa7aaf9931bb05832fec4d6b932a933d9339 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Sat, 23 Jan 2021 13:06:12 +0100 Subject: [PATCH 5/6] - update SDK --- ios-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index 3a0227356..2c72c96ba 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 3a02273565681ac57eb8ac5bbb083cd88da31565 +Subproject commit 2c72c96baaf3d50976c4d281be5ea8125317ed92 From 83eeab121f9064387af7a2a1b40e100ff69d9d36 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 29 Jan 2021 18:24:00 +0100 Subject: [PATCH 6/6] Address finding 1 in #874 and fix missing fill color in the safe area: - FrameViewController: include table background color pin theming support - ThemeTableViewCell: - support recreation of labels and custom layout of these labels - add primaryTextLabel and primaryDetailTextLabel accessors to access text labels irrespective of whether they are system-provided or recreated - StaticTableViewRow - add support for recreated labels and their custom layout - IssuesCardViewController - replace usage of constraints in CardCellBackgroundView with auto resized views (fixes auto layout constraint warnings) - switch to using primaryTextLabel and primaryDetailTextLabel and a custom recreatedLabelLayout --- .../Issues/IssuesCardViewController.swift | 48 +++++++---- .../More/FrameViewController.swift | 1 + .../StaticTableView/StaticTableViewRow.swift | 24 +++--- .../Theme/UI/ThemeTableViewCell.swift | 82 ++++++++++++++++++- 4 files changed, 124 insertions(+), 31 deletions(-) diff --git a/ownCloud/Issues/IssuesCardViewController.swift b/ownCloud/Issues/IssuesCardViewController.swift index acf2934ab..5f14be1f7 100644 --- a/ownCloud/Issues/IssuesCardViewController.swift +++ b/ownCloud/Issues/IssuesCardViewController.swift @@ -21,22 +21,15 @@ import ownCloudAppShared class CardCellBackgroundView : UIView { init(backgroundColor: UIColor, insets: NSDirectionalEdgeInsets, cornerRadius: CGFloat) { - super.init(frame: .zero) + super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) - let backgroundView = UIView(frame: .zero) - backgroundView.translatesAutoresizingMaskIntoConstraints = false + let backgroundView = UIView(frame: CGRect(x: insets.leading, y: insets.top, width: frame.size.width - insets.leading - insets.trailing, height: frame.size.height - insets.top - insets.bottom)) backgroundView.layer.backgroundColor = backgroundColor.cgColor backgroundView.layer.cornerRadius = cornerRadius + backgroundView.autoresizingMask = [.flexibleHeight, .flexibleWidth] addSubview(backgroundView) - - NSLayoutConstraint.activate([ - backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.leading), - backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.trailing), - backgroundView.topAnchor.constraint(equalTo: topAnchor, constant: insets.top), - backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom) - ]) } required init?(coder: NSCoder) { @@ -153,18 +146,24 @@ class IssuesCardViewController: StaticTableViewController { let section = StaticTableViewSection() + let horizontalMargin : CGFloat = 25 + let verticalMargin : CGFloat = 10 + let backgroundHorizontalMargin : CGFloat = 15 + let backgroundVerticalMargin : CGFloat = 2 + let backgroundCornerRadius : CGFloat = 5 + let cellStyler : ThemeTableViewCell.CellStyler = { (cell, style) in - cell.textLabel?.textColor = style.textColor - cell.textLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize * 1.1, weight: .semibold) + cell.primaryTextLabel?.textColor = style.textColor + cell.primaryTextLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize * 1.1, weight: .semibold) - cell.detailTextLabel?.textColor = style.textColor - cell.detailTextLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize, weight: .regular) + cell.primaryDetailTextLabel?.textColor = style.textColor + cell.primaryDetailTextLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize, weight: .regular) if let backgroundColor = style.backgroundColor { - let edgeInsets = NSDirectionalEdgeInsets(top: 2, leading: 15, bottom: 2, trailing: 15) + let edgeInsets = NSDirectionalEdgeInsets(top: backgroundVerticalMargin, leading: backgroundHorizontalMargin, bottom: backgroundVerticalMargin, trailing: backgroundHorizontalMargin) - cell.backgroundView = CardCellBackgroundView(backgroundColor: backgroundColor, insets: edgeInsets, cornerRadius: 5) - cell.selectedBackgroundView = CardCellBackgroundView(backgroundColor: backgroundColor.darker(0.07), insets: edgeInsets, cornerRadius: 5) + cell.backgroundView = CardCellBackgroundView(backgroundColor: backgroundColor, insets: edgeInsets, cornerRadius: backgroundCornerRadius) + cell.selectedBackgroundView = CardCellBackgroundView(backgroundColor: backgroundColor.darker(0.07), insets: edgeInsets, cornerRadius: backgroundCornerRadius) } return true @@ -196,7 +195,20 @@ class IssuesCardViewController: StaticTableViewController { self?.present(ThemeNavigationController(rootViewController: certificateViewController), animated: true, completion: nil) } - }, title: issueTitle, subtitle: issue.localizedDescription, messageStyle: messageStyle, accessoryType: (issue.type == .certificate) ? .disclosureIndicator : .none ) + }, title: issueTitle, subtitle: issue.localizedDescription, messageStyle: messageStyle, recreatedLabelLayout: { (cell, textLabel, detailLabel) in + NSLayoutConstraint.activate([ + textLabel.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: verticalMargin), + + textLabel.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: horizontalMargin), + textLabel.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -horizontalMargin), + + detailLabel.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: horizontalMargin), + detailLabel.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -horizontalMargin), + + detailLabel.topAnchor.constraint(equalToSystemSpacingBelow: textLabel.bottomAnchor, multiplier: 1), + detailLabel.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -verticalMargin) + ]) + }, accessoryType: (issue.type == .certificate) ? .disclosureIndicator : .none) (row.cell as? ThemeTableViewCell)?.cellStyler = cellStyler diff --git a/ownCloudAppShared/User Interface/More/FrameViewController.swift b/ownCloudAppShared/User Interface/More/FrameViewController.swift index e9785e3e7..a255baa13 100644 --- a/ownCloudAppShared/User Interface/More/FrameViewController.swift +++ b/ownCloudAppShared/User Interface/More/FrameViewController.swift @@ -143,5 +143,6 @@ open class FrameViewController: UIViewController, CardPresentationSizing { extension FrameViewController: Themeable { public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { self.headerView.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor + self.view.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor } } diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index eaa24d56b..a8bcf3e77 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -125,7 +125,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { super.init() } - convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, messageStyle: StaticTableViewRowMessageStyle? = nil, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, messageStyle: StaticTableViewRowMessageStyle? = nil, recreatedLabelLayout : ThemeTableViewCell.CellLayouter? = nil, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .row @@ -140,20 +140,20 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { cellStyle = UITableViewCell.CellStyle.subtitle } - let themeCell = ThemeTableViewCell(withLabelColorUpdates: true, style: cellStyle, reuseIdentifier: nil) + let themeCell = ThemeTableViewCell(withLabelColorUpdates: true, style: cellStyle, recreatedLabelLayout: recreatedLabelLayout, reuseIdentifier: nil) themeCell.messageStyle = messageStyle self.cell = themeCell if subtitle != nil { - self.cell?.detailTextLabel?.text = subtitle - self.cell?.detailTextLabel?.numberOfLines = 0 + themeCell.primaryDetailTextLabel?.text = subtitle + themeCell.primaryDetailTextLabel?.numberOfLines = 0 } - self.cell?.textLabel?.text = title - self.cell?.textLabel?.textAlignment = alignment - self.cell?.accessoryType = accessoryType - self.cell?.imageView?.image = image + themeCell.primaryTextLabel?.text = title + themeCell.primaryTextLabel?.textAlignment = alignment + themeCell.accessoryType = accessoryType + themeCell.imageView?.image = image if accessoryView != nil { - self.cell?.accessoryView = accessoryView + themeCell.accessoryView = accessoryView } if #available(iOS 13.4, *), let cell = self.cell { @@ -163,14 +163,14 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { themeApplierToken = Theme.shared.add(applier: { [weak self] (_, themeCollection, _) in self?.cell?.imageView?.tintColor = themeCollection.tableRowColors.value(forKeyPath: imageTintColorKey) as? UIColor self?.cell?.accessoryView?.tintColor = themeCollection.tableRowColors.labelColor - }) + }) - self.cell?.accessibilityIdentifier = identifier + themeCell.accessibilityIdentifier = identifier if rowWithAction != nil { self.action = rowWithAction } else { - self.cell?.selectionStyle = .none + themeCell.selectionStyle = .none } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift index 2683f2c14..c69d53eb8 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift @@ -25,6 +25,16 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { public var messageStyle : StaticTableViewRowMessageStyle? + public var primaryTextLabel : UILabel? { + return customTextLabels?.first ?? textLabel + } + public var primaryDetailTextLabel : UILabel? { + return customDetailTextLabels?.first ?? detailTextLabel + } + + public var customTextLabels : [UILabel]? + public var customDetailTextLabels : [UILabel]? + public struct CellStyleSet { public var theme: Theme public var collection: ThemeCollection @@ -40,8 +50,54 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { super.init(style: style, reuseIdentifier: reuseIdentifier) } - convenience public init(withLabelColorUpdates labelColorUpdates: Bool, style: UITableViewCell.CellStyle = .default, reuseIdentifier: String? = nil) { + public typealias CellLayouter = (_ cell: ThemeTableViewCell, _ textLabel: UILabel, _ detailLabel : UILabel) -> Void + + static public var systemLikeLayout : CellLayouter = { (cell, textLabel, detailLabel) in + NSLayoutConstraint.activate([ + textLabel.topAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.topAnchor, multiplier: 1), + + textLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.leadingAnchor, multiplier: 1), + textLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1), + + detailLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.leadingAnchor, multiplier: 1), + detailLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1), + + detailLabel.topAnchor.constraint(equalToSystemSpacingBelow: textLabel.bottomAnchor, multiplier: 1), + detailLabel.bottomAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.bottomAnchor, multiplier: -1) + ]) + } + + convenience public init(withLabelColorUpdates labelColorUpdates: Bool, style: UITableViewCell.CellStyle = .default, recreatedLabelLayout : CellLayouter? = nil, reuseIdentifier: String? = nil) { self.init(style: style, reuseIdentifier: reuseIdentifier) + if let recreatedLabelLayout = recreatedLabelLayout { + if style == .subtitle { + let replacementTextLabel = UILabel() + let replacementDetailLabel = UILabel() + + replacementTextLabel.translatesAutoresizingMaskIntoConstraints = false + replacementTextLabel.setContentHuggingPriority(.required, for: .vertical) + replacementTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) + replacementTextLabel.numberOfLines = 0 + replacementTextLabel.lineBreakMode = .byWordWrapping + + replacementDetailLabel.translatesAutoresizingMaskIntoConstraints = false + replacementDetailLabel.setContentHuggingPriority(.required, for: .vertical) + replacementDetailLabel.setContentCompressionResistancePriority(.required, for: .vertical) + replacementDetailLabel.numberOfLines = 0 + replacementDetailLabel.lineBreakMode = .byWordWrapping + + replacementTextLabel.font = UIFont.systemFont(ofSize: UIFont.systemFontSize) + replacementDetailLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize) + + self.customTextLabels = [ replacementTextLabel ] + self.customDetailTextLabels = [ replacementDetailLabel ] + + self.contentView.addSubview(replacementTextLabel) + self.contentView.addSubview(replacementDetailLabel) + + recreatedLabelLayout(self, replacementTextLabel, replacementDetailLabel) + } + } updateLabelColors = labelColorUpdates } @@ -103,10 +159,34 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { self.textLabel?.textColor = textColor self.detailTextLabel?.textColor = textColor self.backgroundColor = backgroundColor + + if let customTextLabels = customTextLabels { + for customTextLabel in customTextLabels { + customTextLabel.textColor = textColor + } + } + + if let customMessageLabels = customDetailTextLabels { + for customMessageLabel in customMessageLabels { + customMessageLabel.textColor = textColor + } + } } } else { self.textLabel?.applyThemeCollection(collection, itemStyle: .defaultForItem, itemState: state) self.detailTextLabel?.applyThemeCollection(collection, itemStyle: .message, itemState: state) + + if let customTextLabels = customTextLabels { + for customTextLabel in customTextLabels { + customTextLabel.applyThemeCollection(collection, itemStyle: .defaultForItem, itemState: state) + } + } + + if let customMessageLabels = customDetailTextLabels { + for customMessageLabel in customMessageLabels { + customMessageLabel.applyThemeCollection(collection, itemStyle: .message, itemState: state) + } + } } } }