From af2d011ae267c27bfbd52290226423c8d1bc7752 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 10:58:31 -0300 Subject: [PATCH 01/80] Adding support for atomic private sites to image downloads. --- .../AbstractPost+PostInformation.swift | 5 +++++ .../Blog+ImageSourceInformation.swift | 5 +++++ .../ReaderCardContent+PostInformation.swift | 11 +++++++++++ .../Classes/Utility/Media/ImageLoader.swift | 2 ++ .../ViewRelated/Post/PrivateSiteURLProtocol.h | 2 ++ .../Post/PrivateSiteURLProtocol.swift | 19 +++++++++++++++++++ .../Views/WPRichText/WPRichContentView.swift | 9 ++++++--- WordPress/WordPress.xcodeproj/project.pbxproj | 4 ++++ 8 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 39bc42b56466..fd5401faf8d5 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -1,5 +1,10 @@ extension AbstractPost: ImageSourceInformation { + + var isAtomicOnWPCom: Bool { + return blog.isAtomic() + } + var isPrivateOnWPCom: Bool { return isPrivate() && blog.isHostedAtWPcom } diff --git a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift index 5e1c8f4125ed..7f8a9ffd905c 100644 --- a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift +++ b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift @@ -1,5 +1,10 @@ extension Blog: ImageSourceInformation { + + var isAtomicOnWPCom: Bool { + return isAtomic() + } + var isPrivateOnWPCom: Bool { return isHostedAtWPcom && isPrivate() } diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift index cbc965310715..2e04ec5dd15e 100644 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift @@ -6,6 +6,17 @@ class ReaderCardContent: ImageSourceInformation { init(provider: ReaderPostContentProvider) { originalProvider = provider } + + var isAtomicOnWPCom: Bool { + // Pinged @zieladam about this since the reader endpoint, ie: + // + // https://public-api.wordpress.com/rest/v1.2/read/sites/atomicdiegotravel.wpcomstaging.com/posts/ + // + // provides no info on whether the post is from an atomic site. Right now all we can do is + // assume a default here until the endpoint offers this info. + // + return false + } var isPrivateOnWPCom: Bool { return originalProvider.isPrivate() && originalProvider.isWPCom() diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 201fd9278af6..182baab56d82 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -3,6 +3,8 @@ import MobileCoreServices /// Protocol used to abstract the information needed to load post related images. /// @objc protocol ImageSourceInformation { + + var isAtomicOnWPCom: Bool { get } /// The post is private and hosted on WPcom. /// Redundant name due to naming conflict. diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h index 1a29eb914c94..b81717623c99 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h @@ -20,6 +20,8 @@ + (void)registerPrivateSiteURLProtocol; + (void)unregisterPrivateSiteURLProtocol; ++ (NSString *)bearerToken; + + (nonnull NSURLRequest *)requestForPrivateSiteFromURL:(nonnull NSURL *)url; + (BOOL)urlGoesToWPComSite:(nonnull NSURL *)url; diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift new file mode 100644 index 000000000000..340b3a6f5899 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -0,0 +1,19 @@ +import Foundation + +extension PrivateSiteURLProtocol { + func requestForPrivateAtomicSite(from url: URL) -> URLRequest { + guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), + var components = URLComponents(url: url, resolvingAgainstBaseURL: true), + let urlComponent = components.url, + let bearerToken = PrivateSiteURLProtocol.bearerToken() else { + return URLRequest(url: url) + } + + // Just in case, enforce HTTPs + components.scheme = "https" + + var request = URLRequest(url: urlComponent) + request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization") + return request + } +} diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index e4d945c34067..7436ea6ee99d 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -52,7 +52,8 @@ class WPRichContentView: UITextView { attachmentManager.layoutAttachmentViews() } } - + + @objc var isAtomic = false /// Whether the view shows private content. Used when fetching images. /// @@ -342,7 +343,7 @@ extension WPRichContentView: WPTextAttachmentManagerDelegate { attachment.maxSize = CGSize(width: finalSize.width, height: finalSize.height) } - let contentInformation = ContentInformation(isPrivateOnWPCom: isPrivate, isSelfHostedWithCredentials: false) + let contentInformation = ContentInformation(isAtomicOnWPCom: isAtomic, isPrivateOnWPCom: isPrivate, isSelfHostedWithCredentials: false) let index = mediaArray.count let indexPath = IndexPath(row: index, section: 1) weak var weakImage = image @@ -473,10 +474,12 @@ struct RichMedia { // MARK: - ContentInformation (ImageSourceInformation) class ContentInformation: ImageSourceInformation { + var isAtomicOnWPCom: Bool var isPrivateOnWPCom: Bool var isSelfHostedWithCredentials: Bool - init(isPrivateOnWPCom: Bool, isSelfHostedWithCredentials: Bool) { + init(isAtomicOnWPCom: Bool, isPrivateOnWPCom: Bool, isSelfHostedWithCredentials: Bool) { + self.isAtomicOnWPCom = isAtomicOnWPCom self.isPrivateOnWPCom = isPrivateOnWPCom self.isSelfHostedWithCredentials = isSelfHostedWithCredentials } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a7f1d50b76de..319dc0317be4 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2043,6 +2043,7 @@ F1DB8D292288C14400906E2F /* Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DB8D282288C14400906E2F /* Uploader.swift */; }; F1DB8D2B2288C24500906E2F /* UploadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DB8D2A2288C24500906E2F /* UploadsManager.swift */; }; F1E746E7242E459400C74D8A /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E746E6242E459400C74D8A /* RequestAuthenticator.swift */; }; + F1E746E924322BF800C74D8A /* PrivateSiteURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */; }; F1F083F6241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */; }; F511F8A42356A4F400895E73 /* PublishSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F511F8A32356A4F400895E73 /* PublishSettingsViewController.swift */; }; F53FF3A123E2377E001AD596 /* NewBlogDetailHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A023E2377E001AD596 /* NewBlogDetailHeaderView.swift */; }; @@ -4633,6 +4634,7 @@ F1DB8D282288C14400906E2F /* Uploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uploader.swift; sourceTree = ""; }; F1DB8D2A2288C24500906E2F /* UploadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadsManager.swift; sourceTree = ""; }; F1E746E6242E459400C74D8A /* RequestAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = ""; }; + F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateSiteURLProtocol.swift; sourceTree = ""; }; F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicAuthenticationServiceTests.swift; sourceTree = ""; }; F262DFCA1F94418CE76D1D78 /* Pods-WordPressNotificationContentExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationContentExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationContentExtension/Pods-WordPressNotificationContentExtension.release-internal.xcconfig"; sourceTree = ""; }; F373612EEEEF10E500093FF3 /* Pods-WordPress.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPress.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPress/Pods-WordPress.release-alpha.xcconfig"; sourceTree = ""; }; @@ -8349,6 +8351,7 @@ E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */, 5D17F0BC1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.h */, 5D17F0BD1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m */, + F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */, 5903AE1C19B60AB9009D5354 /* WPButtonForNavigationBar.h */, 5903AE1A19B60A98009D5354 /* WPButtonForNavigationBar.m */, FF0AAE0B1A16550D0089841D /* WPMediaProgressTableViewController.h */, @@ -12019,6 +12022,7 @@ F16C35DA23F3F76C00C81331 /* PostAutoUploadMessageProvider.swift in Sources */, 91D8364121946EFB008340B2 /* GutenbergMediaPickerHelper.swift in Sources */, F1D690161F82913F00200E30 /* FeatureFlag.swift in Sources */, + F1E746E924322BF800C74D8A /* PrivateSiteURLProtocol.swift in Sources */, 4313437B217956DB00DA2176 /* QuickStartSkipAllCell.swift in Sources */, 74989B8C2088E3650054290B /* BlogDetailsViewController+Activity.swift in Sources */, B532D4EB199D4357006E4DF6 /* NoteBlockTableViewCell.swift in Sources */, From e966c7ff942f7b7e5b33e1d53c692bc316ca7636 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 10:59:03 -0300 Subject: [PATCH 02/80] rake lint:autocorrect --- .../Classes/Extensions/AbstractPost+PostInformation.swift | 4 ++-- .../Classes/Extensions/Blog+ImageSourceInformation.swift | 4 ++-- .../Classes/Models/ReaderCardContent+PostInformation.swift | 2 +- WordPress/Classes/Utility/Media/ImageLoader.swift | 2 +- .../Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift | 4 ++-- .../ViewRelated/Views/WPRichText/WPRichContentView.swift | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index fd5401faf8d5..a9eac432dd02 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -1,10 +1,10 @@ extension AbstractPost: ImageSourceInformation { - + var isAtomicOnWPCom: Bool { return blog.isAtomic() } - + var isPrivateOnWPCom: Bool { return isPrivate() && blog.isHostedAtWPcom } diff --git a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift index 7f8a9ffd905c..9f25023ede36 100644 --- a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift +++ b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift @@ -1,10 +1,10 @@ extension Blog: ImageSourceInformation { - + var isAtomicOnWPCom: Bool { return isAtomic() } - + var isPrivateOnWPCom: Bool { return isHostedAtWPcom && isPrivate() } diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift index 2e04ec5dd15e..044dacdfe13b 100644 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift @@ -6,7 +6,7 @@ class ReaderCardContent: ImageSourceInformation { init(provider: ReaderPostContentProvider) { originalProvider = provider } - + var isAtomicOnWPCom: Bool { // Pinged @zieladam about this since the reader endpoint, ie: // diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 182baab56d82..5523690ec156 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -3,7 +3,7 @@ import MobileCoreServices /// Protocol used to abstract the information needed to load post related images. /// @objc protocol ImageSourceInformation { - + var isAtomicOnWPCom: Bool { get } /// The post is private and hosted on WPcom. diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index 340b3a6f5899..17df4cd4a977 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -8,10 +8,10 @@ extension PrivateSiteURLProtocol { let bearerToken = PrivateSiteURLProtocol.bearerToken() else { return URLRequest(url: url) } - + // Just in case, enforce HTTPs components.scheme = "https" - + var request = URLRequest(url: urlComponent) request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization") return request diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index 7436ea6ee99d..c3b900413373 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -52,7 +52,7 @@ class WPRichContentView: UITextView { attachmentManager.layoutAttachmentViews() } } - + @objc var isAtomic = false /// Whether the view shows private content. Used when fetching images. From 840d2ba7f3ade95d09024cd06bd97944ee7c9af9 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 12:48:49 -0300 Subject: [PATCH 03/80] Adds authentication for image downloads in Atomic Private Sites. --- .../AbstractPost+PostInformation.swift | 4 +++ .../Blog+ImageSourceInformation.swift | 4 +++ .../Extensions/UIImageView+SiteIcon.swift | 28 ++++++++++++++----- .../ReaderCardContent+PostInformation.swift | 4 +++ .../Models/ReaderPostContentProvider.h | 1 + .../Classes/Utility/Media/ImageLoader.swift | 2 ++ .../Post/PrivateSiteURLProtocol.swift | 28 +++++++++++++++---- .../Views/WPRichText/WPRichContentView.swift | 6 ++-- 8 files changed, 62 insertions(+), 15 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index a9eac432dd02..374be4cd48f4 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -29,4 +29,8 @@ extension AbstractPost: ImageSourceInformation { } return autosaveRevisionIdentifier > 0 } + + var siteID: NSNumber? { + return blog.dotComID + } } diff --git a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift index 9f25023ede36..15a96aea0fc0 100644 --- a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift +++ b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift @@ -12,4 +12,8 @@ extension Blog: ImageSourceInformation { var isSelfHostedWithCredentials: Bool { return !isHostedAtWPcom && isBasicAuthCredentialStored() } + + var siteID: NSNumber? { + return dotComID + } } diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 9241fd9d5f03..200dfb40faa2 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -61,22 +61,36 @@ extension UIImageView { /// @objc func downloadSiteIcon(for blog: Blog, placeholderImage: UIImage? = .siteIconPlaceholder) { + func downloadImage(with request: URLRequest) { + self.downloadImage(usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in + self?.image = image + self?.removePlaceholderBorder() + }, failure: nil) + } + guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { image = placeholderImage return } let request: URLRequest - if blog.isPrivate(), PrivateSiteURLProtocol.urlGoes(toWPComSite: siteIconURL) { - request = PrivateSiteURLProtocol.requestForPrivateSite(from: siteIconURL) + if false, blog.isPrivate(), PrivateSiteURLProtocol.urlGoes(toWPComSite: siteIconURL) { + if let dotComID = blog.dotComID?.intValue, + blog.isAtomic() { + + PrivateSiteURLProtocol.requestForPrivateAtomicSite(for: siteIconURL, siteID: dotComID, onComplete: { (request) in + + downloadImage(with: request) + }) + } else { + request = PrivateSiteURLProtocol.requestForPrivateSite(from: siteIconURL) + + downloadImage(with: request) + } } else { request = URLRequest(url: siteIconURL) + downloadImage(with: request) } - - downloadImage(usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in - self?.image = image - self?.removePlaceholderBorder() - }, failure: nil) } } diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift index 044dacdfe13b..4ac01bbfb8b6 100644 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift @@ -25,4 +25,8 @@ class ReaderCardContent: ImageSourceInformation { var isSelfHostedWithCredentials: Bool { return !originalProvider.isWPCom() && !originalProvider.isJetpack() } + + var siteID: NSNumber? { + return originalProvider.siteID() + } } diff --git a/WordPress/Classes/Models/ReaderPostContentProvider.h b/WordPress/Classes/Models/ReaderPostContentProvider.h index 5f6680181164..7c6349ae0784 100644 --- a/WordPress/Classes/Models/ReaderPostContentProvider.h +++ b/WordPress/Classes/Models/ReaderPostContentProvider.h @@ -8,6 +8,7 @@ typedef NS_ENUM(NSUInteger, SourceAttributionStyle) { }; @protocol ReaderPostContentProvider +- (NSNumber *)siteID; - (NSURL *)siteIconForDisplayOfSize:(NSInteger)size; - (SourceAttributionStyle)sourceAttributionStyle; - (NSString *)sourceAuthorNameForDisplay; diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 5523690ec156..645dd00b2740 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -14,6 +14,8 @@ import MobileCoreServices /// The blog is self-hosted and there is already a basic auth credential stored. /// var isSelfHostedWithCredentials: Bool { get } + + var siteID: NSNumber? { get } } /// Class used together with `CachedAnimatedImageView` to facilitate the loading of both diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index 17df4cd4a977..fa3fab1fa782 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -1,19 +1,35 @@ import Foundation +import AutomatticTracks extension PrivateSiteURLProtocol { - func requestForPrivateAtomicSite(from url: URL) -> URLRequest { + static func requestForPrivateAtomicSite( + for url: URL, + siteID: Int, + onComplete provide: @escaping (URLRequest) -> ()) { + guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true), let urlComponent = components.url, - let bearerToken = PrivateSiteURLProtocol.bearerToken() else { - return URLRequest(url: url) + let bearerToken = bearerToken(), + let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount() else { + return provide(URLRequest(url: url)) } - + + let authenticationService = AtomicAuthenticationService(account: account) + let cookieJar = HTTPCookieStorage.shared + // Just in case, enforce HTTPs components.scheme = "https" - + var request = URLRequest(url: urlComponent) request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization") - return request + + authenticationService.loadAuthCookies(into: cookieJar, username: account.username, siteID: siteID, success: { + return provide(request) + }) { error in + return provide(request) + } + + } } diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index c3b900413373..d055042358bf 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -343,7 +343,7 @@ extension WPRichContentView: WPTextAttachmentManagerDelegate { attachment.maxSize = CGSize(width: finalSize.width, height: finalSize.height) } - let contentInformation = ContentInformation(isAtomicOnWPCom: isAtomic, isPrivateOnWPCom: isPrivate, isSelfHostedWithCredentials: false) + let contentInformation = ContentInformation(isAtomicOnWPCom: isAtomic, isPrivateOnWPCom: isPrivate, isSelfHostedWithCredentials: false, siteID: nil) let index = mediaArray.count let indexPath = IndexPath(row: index, section: 1) weak var weakImage = image @@ -477,11 +477,13 @@ class ContentInformation: ImageSourceInformation { var isAtomicOnWPCom: Bool var isPrivateOnWPCom: Bool var isSelfHostedWithCredentials: Bool + var siteID: NSNumber? - init(isAtomicOnWPCom: Bool, isPrivateOnWPCom: Bool, isSelfHostedWithCredentials: Bool) { + init(isAtomicOnWPCom: Bool, isPrivateOnWPCom: Bool, isSelfHostedWithCredentials: Bool, siteID: NSNumber?) { self.isAtomicOnWPCom = isAtomicOnWPCom self.isPrivateOnWPCom = isPrivateOnWPCom self.isSelfHostedWithCredentials = isSelfHostedWithCredentials + self.siteID = siteID } } From c96087fac7f8544131c58462a6053161da1f1918 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 12:49:20 -0300 Subject: [PATCH 04/80] rake lint:autocorrect --- .../Extensions/AbstractPost+PostInformation.swift | 2 +- .../Extensions/Blog+ImageSourceInformation.swift | 2 +- .../Classes/Extensions/UIImageView+SiteIcon.swift | 8 ++++---- .../Models/ReaderCardContent+PostInformation.swift | 2 +- WordPress/Classes/Utility/Media/ImageLoader.swift | 2 +- .../ViewRelated/Post/PrivateSiteURLProtocol.swift | 12 ++++++------ 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 374be4cd48f4..5983f0d415b8 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -29,7 +29,7 @@ extension AbstractPost: ImageSourceInformation { } return autosaveRevisionIdentifier > 0 } - + var siteID: NSNumber? { return blog.dotComID } diff --git a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift index 15a96aea0fc0..704ad96994ac 100644 --- a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift +++ b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift @@ -12,7 +12,7 @@ extension Blog: ImageSourceInformation { var isSelfHostedWithCredentials: Bool { return !isHostedAtWPcom && isBasicAuthCredentialStored() } - + var siteID: NSNumber? { return dotComID } diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 200dfb40faa2..f4f8b53ac50d 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -67,7 +67,7 @@ extension UIImageView { self?.removePlaceholderBorder() }, failure: nil) } - + guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { image = placeholderImage return @@ -77,14 +77,14 @@ extension UIImageView { if false, blog.isPrivate(), PrivateSiteURLProtocol.urlGoes(toWPComSite: siteIconURL) { if let dotComID = blog.dotComID?.intValue, blog.isAtomic() { - + PrivateSiteURLProtocol.requestForPrivateAtomicSite(for: siteIconURL, siteID: dotComID, onComplete: { (request) in - + downloadImage(with: request) }) } else { request = PrivateSiteURLProtocol.requestForPrivateSite(from: siteIconURL) - + downloadImage(with: request) } } else { diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift index 4ac01bbfb8b6..aa7ce40a5606 100644 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift @@ -25,7 +25,7 @@ class ReaderCardContent: ImageSourceInformation { var isSelfHostedWithCredentials: Bool { return !originalProvider.isWPCom() && !originalProvider.isJetpack() } - + var siteID: NSNumber? { return originalProvider.siteID() } diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 645dd00b2740..8ba8866d583c 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -14,7 +14,7 @@ import MobileCoreServices /// The blog is self-hosted and there is already a basic auth credential stored. /// var isSelfHostedWithCredentials: Bool { get } - + var siteID: NSNumber? { get } } diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index fa3fab1fa782..acc6cd6f5233 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -6,7 +6,7 @@ extension PrivateSiteURLProtocol { for url: URL, siteID: Int, onComplete provide: @escaping (URLRequest) -> ()) { - + guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true), let urlComponent = components.url, @@ -14,22 +14,22 @@ extension PrivateSiteURLProtocol { let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount() else { return provide(URLRequest(url: url)) } - + let authenticationService = AtomicAuthenticationService(account: account) let cookieJar = HTTPCookieStorage.shared - + // Just in case, enforce HTTPs components.scheme = "https" - + var request = URLRequest(url: urlComponent) request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization") - + authenticationService.loadAuthCookies(into: cookieJar, username: account.username, siteID: siteID, success: { return provide(request) }) { error in return provide(request) } - + } } From 91b903bbd1b3f8d07cd88e89d8f7184ed8bb627f Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 17:08:12 -0300 Subject: [PATCH 05/80] Adding support for Atomic Private Sites to Photon URLs. --- .../Extensions/UIImageView+SiteIcon.swift | 18 ++--- WordPress/Classes/Models/Blog.m | 3 +- .../Services/MediaThumbnailService.swift | 5 +- .../Gutenberg/EditorMediaUtility.swift | 2 +- .../Post/PostPreviewGenerator.swift | 2 +- .../ViewRelated/Post/PrivateSiteURLProtocol.h | 2 - .../Post/PrivateSiteURLProtocol.swift | 79 ++++++++++++++++++- 7 files changed, 88 insertions(+), 23 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index f4f8b53ac50d..3eb5a56caaee 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -73,22 +73,18 @@ extension UIImageView { return } - let request: URLRequest - if false, blog.isPrivate(), PrivateSiteURLProtocol.urlGoes(toWPComSite: siteIconURL) { - if let dotComID = blog.dotComID?.intValue, - blog.isAtomic() { - - PrivateSiteURLProtocol.requestForPrivateAtomicSite(for: siteIconURL, siteID: dotComID, onComplete: { (request) in + if blog.dotComID == 172825667 { + print("Blog id \(blog.dotComID ?? 0), is atomic \(blog.isAtomic())") + } - downloadImage(with: request) - }) - } else { - request = PrivateSiteURLProtocol.requestForPrivateSite(from: siteIconURL) + if blog.isPrivate(), + let dotComID = blog.dotComID { + PrivateSiteURLProtocol.request(for: siteIconURL, siteID: dotComID.intValue, isAtomic: blog.isAtomic()) { request in downloadImage(with: request) } } else { - request = URLRequest(url: siteIconURL) + let request = URLRequest(url: siteIconURL) downloadImage(with: request) } } diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index 6c7da615c367..483a15412a75 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -326,10 +326,9 @@ - (NSString *)postFormatTextFromSlug:(NSString *)postFormatSlug return formatText; } -// WP.COM private blog. - (BOOL)isPrivate { - return (self.isHostedAtWPcom && [self.settings.privacy isEqualToNumber:@(SiteVisibilityPrivate)]); + return [self.settings.privacy isEqualToNumber:@(SiteVisibilityPrivate)]; } - (SiteVisibility)siteVisibility diff --git a/WordPress/Classes/Services/MediaThumbnailService.swift b/WordPress/Classes/Services/MediaThumbnailService.swift index 768f65a87260..3a144db68c2a 100644 --- a/WordPress/Classes/Services/MediaThumbnailService.swift +++ b/WordPress/Classes/Services/MediaThumbnailService.swift @@ -162,7 +162,8 @@ class MediaThumbnailService: LocalCoreDataService { return } // Get an expected WP URL, for sizing. - if media.blog.isPrivate() || (!media.blog.isHostedAtWPcom && media.blog.isBasicAuthCredentialStored()) { + if (media.blog.isHostedAtWPcom && media.blog.isPrivate()) + || (!media.blog.isHostedAtWPcom && media.blog.isBasicAuthCredentialStored()) { remoteURL = WPImageURLHelper.imageURLWithSize(preferredSize, forImageURL: remoteAssetURL) } else { remoteURL = PhotonImageURLHelper.photonURL(with: preferredSize, forImageURL: remoteAssetURL) @@ -187,7 +188,7 @@ class MediaThumbnailService: LocalCoreDataService { onError(error) } } - if media.blog.isPrivate() { + if media.blog.isHostedAtWPcom && media.blog.isPrivate() { let accountService = AccountService(managedObjectContext: self.managedObjectContext) guard let authToken = accountService.defaultWordPressComAccount()?.authToken else { // Don't have an auth token for some reason, return nothing. diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index af77a0ea964a..06f23eb8d351 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -62,7 +62,7 @@ class EditorMediaUtility { if url.isFileURL { request = URLRequest(url: url) - } else if post.blog.isPrivate() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + } else if post.blog.isHostedAtWPcom && post.blog.isPrivate() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { // private wpcom image needs special handling. // the size that WPImageHelper expects is pixel size size.width = size.width * scale diff --git a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift index c1d1241b00ec..c2814fa92254 100644 --- a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift +++ b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift @@ -87,7 +87,7 @@ private extension PostPreviewGenerator { case .draft, .publishPrivate, .pending, .scheduled, .publish: return true default: - return post.blog.isPrivate() + return post.blog.isHostedAtWPcom && post.blog.isPrivate() } } diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h index b81717623c99..1a29eb914c94 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h @@ -20,8 +20,6 @@ + (void)registerPrivateSiteURLProtocol; + (void)unregisterPrivateSiteURLProtocol; -+ (NSString *)bearerToken; - + (nonnull NSURLRequest *)requestForPrivateSiteFromURL:(nonnull NSURL *)url; + (BOOL)urlGoesToWPComSite:(nonnull NSURL *)url; diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index acc6cd6f5233..ca5f46b2a042 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -2,6 +2,79 @@ import Foundation import AutomatticTracks extension PrivateSiteURLProtocol { + + enum Error: Swift.Error { + case cannotBreakDownURLIntoComponents(url: URL) + case cannotFindWPContentInPhotonPath(components: URLComponents) + case cannotCreateAtomicProxyURL(components: URLComponents) + } + + static let photonHost = "i0.wp.com" + + private static func isPhoton(url: URL) -> Bool { + return url.host == photonHost + } + + static func request( + for url: URL, + siteID: Int, + isAtomic: Bool, + onComplete provide: @escaping (URLRequest) -> ()) { + + if urlGoes(toWPComSite: url) { + if isAtomic { + requestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) + } else { + let request = requestForPrivateSite(from: url) + provide(request) + } + } else if isAtomic && isPhoton(url: url) { + requestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) + } else { + let request = URLRequest(url: url) + provide(request) + } + } + + /// Photon URLs are currently not working for private atomic sites, so this is a workaround + /// to replace those URLs with working URLs. + /// + /// To know whether you can remove this method, try requesting the photon URL from an + /// atomic private site. If it works then you can remove this workaround logic. + /// + static func requestForPrivateAtomicSiteThroughPhoton( + for url: URL, + siteID: Int, + onComplete provide: @escaping (URLRequest) -> ()) { + + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + CrashLogging.logError(Error.cannotBreakDownURLIntoComponents(url: url)) + provide(URLRequest(url: url)) + return + } + + guard let wpContentRange = components.path.range(of: "/wp-content") else { + CrashLogging.logError(Error.cannotFindWPContentInPhotonPath(components: components)) + provide(URLRequest(url: url)) + return + } + + let contentPath = components.path[wpContentRange.lowerBound ..< components.path.endIndex] + + components.scheme = "https" + components.host = "public-api.wordpress.com" + components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file\(contentPath)" + + guard let finalURL = components.url else { + CrashLogging.logError(Error.cannotCreateAtomicProxyURL(components: components)) + provide(URLRequest(url: url)) + return + } + + let request = URLRequest(url: finalURL) + provide(request) + } + static func requestForPrivateAtomicSite( for url: URL, siteID: Int, @@ -10,8 +83,8 @@ extension PrivateSiteURLProtocol { guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true), let urlComponent = components.url, - let bearerToken = bearerToken(), - let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount() else { + let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), + let bearerToken = account.authToken else { return provide(URLRequest(url: url)) } @@ -29,7 +102,5 @@ extension PrivateSiteURLProtocol { }) { error in return provide(request) } - - } } From e41a1439f6829148ed2e0ad129058e02815ca131 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 30 Mar 2020 17:37:05 -0300 Subject: [PATCH 06/80] Fixes some issues with the new photon image handling. --- .../Extensions/UIImageView+SiteIcon.swift | 4 +- .../Post/PrivateSiteURLProtocol.swift | 41 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 3eb5a56caaee..234cfbee3428 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -65,7 +65,9 @@ extension UIImageView { self.downloadImage(usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in self?.image = image self?.removePlaceholderBorder() - }, failure: nil) + }) { error in + // No-op (for now!) + } } guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index ca5f46b2a042..b1ede29424e0 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -6,7 +6,9 @@ extension PrivateSiteURLProtocol { enum Error: Swift.Error { case cannotBreakDownURLIntoComponents(url: URL) case cannotFindWPContentInPhotonPath(components: URLComponents) + case cannotCreateAtomicURL(components: URLComponents) case cannotCreateAtomicProxyURL(components: URLComponents) + case cannotFindAuthenticationToken(url: URL) } static let photonHost = "i0.wp.com" @@ -39,6 +41,11 @@ extension PrivateSiteURLProtocol { /// Photon URLs are currently not working for private atomic sites, so this is a workaround /// to replace those URLs with working URLs. /// + /// By recommendation of @zieladam we'll be using the Atomic Proxy endpoint for these until + /// Photon starts working with Atomic Private Sites: + /// + /// https://public-api.wordpress.com/wpcom/v2/sites/$siteID/atomic-auth-proxy/file/$wpContentPath + /// /// To know whether you can remove this method, try requesting the photon URL from an /// atomic private site. If it works then you can remove this workaround logic. /// @@ -71,7 +78,12 @@ extension PrivateSiteURLProtocol { return } - let request = URLRequest(url: finalURL) + guard let request = tokenAuthenticatedWPComRequest(for: finalURL) else { + CrashLogging.logError(Error.cannotFindAuthenticationToken(url: finalURL)) + provide(URLRequest(url: url)) + return + } + provide(request) } @@ -82,9 +94,8 @@ extension PrivateSiteURLProtocol { guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let urlComponent = components.url, let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), - let bearerToken = account.authToken else { + let authToken = account.authToken else { return provide(URLRequest(url: url)) } @@ -94,8 +105,13 @@ extension PrivateSiteURLProtocol { // Just in case, enforce HTTPs components.scheme = "https" - var request = URLRequest(url: urlComponent) - request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization") + guard let finalURL = components.url else { + CrashLogging.logError(Error.cannotCreateAtomicURL(components: components)) + provide(URLRequest(url: url)) + return + } + + let request = tokenAuthenticatedWPComRequest(for: finalURL, authToken: authToken) authenticationService.loadAuthCookies(into: cookieJar, username: account.username, siteID: siteID, success: { return provide(request) @@ -103,4 +119,19 @@ extension PrivateSiteURLProtocol { return provide(request) } } + + private static func tokenAuthenticatedWPComRequest(for url: URL) -> URLRequest? { + guard let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), + let authToken = account.authToken else { + return nil + } + + return tokenAuthenticatedWPComRequest(for: url, authToken: authToken) + } + + private static func tokenAuthenticatedWPComRequest(for url: URL, authToken: String) -> URLRequest { + var request = URLRequest(url: url) + request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") + return request + } } From 46681b1ddc9528b1b2d6f06e4649f2d07426ba69 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 1 Apr 2020 15:54:24 -0300 Subject: [PATCH 07/80] The project builds again. Still working on media auth. --- .../Extensions/UIImageView+SiteIcon.swift | 16 +- .../ReaderCardContent+PostInformation.swift | 9 +- .../Models/ReaderPostContentProvider.h | 1 + .../MediaRequestAuthenticator.swift | 234 ++++++++++++++++++ .../Classes/Utility/Media/ImageLoader.swift | 11 +- .../AztecPostViewController.swift | 2 +- .../Gutenberg/AztecAttachmentDelegate.swift | 2 +- .../Gutenberg/EditorMediaUtility.swift | 123 ++++++++- .../Utils/GutenbergMediaEditorImage.swift | 2 +- .../ViewRelated/Post/PrivateSiteURLProtocol.h | 4 +- .../ViewRelated/Post/PrivateSiteURLProtocol.m | 8 +- .../Post/PrivateSiteURLProtocol.swift | 8 +- .../RevisionPreviewTextViewManager.swift | 2 +- .../Reader/ReaderPostCardCell.swift | 13 +- WordPress/WordPress.xcodeproj/project.pbxproj | 4 + 15 files changed, 392 insertions(+), 47 deletions(-) create mode 100644 WordPress/Classes/Networking/MediaRequestAuthenticator.swift diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 234cfbee3428..a9c1e6527add 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -79,15 +79,17 @@ extension UIImageView { print("Blog id \(blog.dotComID ?? 0), is atomic \(blog.isAtomic())") } - if blog.isPrivate(), - let dotComID = blog.dotComID { - PrivateSiteURLProtocol.request(for: siteIconURL, siteID: dotComID.intValue, isAtomic: blog.isAtomic()) { request in + let mediaRequestAuthenticator = MediaRequestAuthenticator() + + mediaRequestAuthenticator.authenticatedRequest( + for: siteIconURL, + blog: blog, + onComplete: { request in - downloadImage(with: request) - } - } else { - let request = URLRequest(url: siteIconURL) downloadImage(with: request) + }) { error in + + // No-op for now } } } diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift index aa7ce40a5606..fd5883b1d3de 100644 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift @@ -8,14 +8,7 @@ class ReaderCardContent: ImageSourceInformation { } var isAtomicOnWPCom: Bool { - // Pinged @zieladam about this since the reader endpoint, ie: - // - // https://public-api.wordpress.com/rest/v1.2/read/sites/atomicdiegotravel.wpcomstaging.com/posts/ - // - // provides no info on whether the post is from an atomic site. Right now all we can do is - // assume a default here until the endpoint offers this info. - // - return false + return originalProvider.isAtomic() } var isPrivateOnWPCom: Bool { diff --git a/WordPress/Classes/Models/ReaderPostContentProvider.h b/WordPress/Classes/Models/ReaderPostContentProvider.h index 7c6349ae0784..6386137101e0 100644 --- a/WordPress/Classes/Models/ReaderPostContentProvider.h +++ b/WordPress/Classes/Models/ReaderPostContentProvider.h @@ -23,6 +23,7 @@ typedef NS_ENUM(NSUInteger, SourceAttributionStyle) { - (BOOL)commentsOpen; - (BOOL)isFollowing; - (BOOL)isLikesEnabled; +- (BOOL)isAtomic; - (BOOL)isPrivate; - (BOOL)isLiked; - (BOOL)isExternal; diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift new file mode 100644 index 000000000000..3d00ca9290a8 --- /dev/null +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -0,0 +1,234 @@ +import AutomatticTracks +import Foundation + +fileprivate let photonHost = "i0.wp.com" + +extension URL { + func isHostedAtWPCom() -> Bool { + // I don't think this method should check for HTTPs here, and we'd rather test + // that separately. But since this code is basically a migration, I'm leaving + // the check for now to avoid breaking things. + return scheme == "https" && host?.hasSuffix(".wordpress.com") ?? false + } + + fileprivate func isPhoton() -> Bool { + return host == photonHost + } +} + +/// This class takes care of resolving any authentication necessary before +/// requesting media from WP sites (both self-hosted and WP.com). +/// +/// This also includes regular and photon URLs. +/// +class MediaRequestAuthenticator { + + enum Error: Swift.Error { + case cannotFindSiteIDForSiteAvailableThroughWPCom(blog: Blog) + case cannotBreakDownURLIntoComponents(url: URL) + case cannotCreateAtomicURL(components: URLComponents) + case cannotCreateAtomicProxyURL(components: URLComponents) + case cannotCreatePrivateURL(components: URLComponents) + case cannotFindAuthenticationToken(url: URL) + case cannotFindWPContentInPhotonPath(components: URLComponents) + } + + // MARK: - Request Authentication + + /// Pass this method a media URL, and it will handle all the necessary logic to provide the caller + /// with an authenticated request through the completion closure. + /// + /// - Parameters: + /// - url: the url for the media. + /// - blog: the blog associated with the passed media URL. + /// - onComplete: the closure that will be called once authentication is sorted out by this class. + /// The request can be executed directly without having to do anything else in terms of + /// authentication. + /// + func authenticatedRequest( + for url: URL, + blog: Blog, + onComplete provide: @escaping (URLRequest) -> (), + onFailure fail: @escaping (Error) -> ()) { + + guard blog.isAccessibleThroughWPCom() else { + let request = URLRequest(url: url) + provide(request) + return + } + + guard let siteID = blog.dotComID?.intValue else { + fail(Error.cannotFindSiteIDForSiteAvailableThroughWPCom(blog: blog)) + return + } + + authenticatedWPComRequest( + for: url, + siteID: siteID, + inPrivateBlog: blog.isPrivate(), + inAtomicBlog: blog.isAtomic(), + onComplete: provide) + } + + /// Same as above, but this method authenticates WPCom hosted requests only. + /// + /// - Parameters: + /// - url: the url for the media. + /// - siteID: the ID of the site associated with this media. It may not be the host though + /// as can be the case with photon URLs that are hosted elsewhere. + /// - inPrivateBlog: whether the owning site is private. + /// - inAtomicBlog: whether the owning site is atomic. This is relevant since atomic authentication + /// is not standard. + /// - onComplete: the closure that will be called once authentication is sorted out by this class. + /// The request can be executed directly without having to do anything else in terms of + /// authentication. + /// + func authenticatedWPComRequest( + for url: URL, + siteID: Int, + inPrivateBlog: Bool, + inAtomicBlog: Bool, + onComplete provide: @escaping (URLRequest) -> ()) { + + guard inPrivateBlog else { + let request = URLRequest(url: url) + provide(request) + return + } + + if url.isHostedAtWPCom() { + if inAtomicBlog { + authenticatedRequestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) + } else { + let request = authenticatedRequestForPrivateSite(for: url) + provide(request) + } + } else if inAtomicBlog && url.isPhoton() { + authenticatedRequestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) + } else { + let request = URLRequest(url: url) + provide(request) + } + } + + // MARK: - Request Authentication: Specific Scenarios + + func authenticatedRequestForPrivateSite(for url: URL) -> URLRequest { + guard !url.isHostedAtWPCom(), + var components = URLComponents(url: url, resolvingAgainstBaseURL: true), + let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), + let authToken = account.authToken else { + return URLRequest(url: url) + } + + // Just in case, enforce HTTPs + components.scheme = "https" + + guard let finalURL = components.url else { + CrashLogging.logError(Error.cannotCreatePrivateURL(components: components)) + return URLRequest(url: url) + } + + let request = tokenAuthenticatedWPComRequest(for: finalURL, authToken: authToken) + return request + } + + private func authenticatedRequestForPrivateAtomicSite( + for url: URL, + siteID: Int, + onComplete provide: @escaping (URLRequest) -> ()) { + + guard !url.isHostedAtWPCom(), + var components = URLComponents(url: url, resolvingAgainstBaseURL: true), + let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), + let authToken = account.authToken else { + return provide(URLRequest(url: url)) + } + + let authenticationService = AtomicAuthenticationService(account: account) + let cookieJar = HTTPCookieStorage.shared + + // Just in case, enforce HTTPs + components.scheme = "https" + + guard let finalURL = components.url else { + CrashLogging.logError(Error.cannotCreateAtomicURL(components: components)) + provide(URLRequest(url: url)) + return + } + + let request = tokenAuthenticatedWPComRequest(for: finalURL, authToken: authToken) + + authenticationService.loadAuthCookies(into: cookieJar, username: account.username, siteID: siteID, success: { + return provide(request) + }) { error in + return provide(request) + } + } + + /// Photon URLs are currently not working for private atomic sites, so this is a workaround + /// to replace those URLs with working URLs. + /// + /// By recommendation of @zieladam we'll be using the Atomic Proxy endpoint for these until + /// Photon starts working with Atomic Private Sites: + /// + /// https://public-api.wordpress.com/wpcom/v2/sites/$siteID/atomic-auth-proxy/file/$wpContentPath + /// + /// To know whether you can remove this method, try requesting the photon URL from an + /// atomic private site. If it works then you can remove this workaround logic. + /// + private func authenticatedRequestForPrivateAtomicSiteThroughPhoton( + for url: URL, + siteID: Int, + onComplete provide: @escaping (URLRequest) -> ()) { + + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + CrashLogging.logError(Error.cannotBreakDownURLIntoComponents(url: url)) + provide(URLRequest(url: url)) + return + } + + guard let wpContentRange = components.path.range(of: "/wp-content") else { + CrashLogging.logError(Error.cannotFindWPContentInPhotonPath(components: components)) + provide(URLRequest(url: url)) + return + } + + let contentPath = components.path[wpContentRange.lowerBound ..< components.path.endIndex] + + components.scheme = "https" + components.host = "public-api.wordpress.com" + components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file\(contentPath)" + + guard let finalURL = components.url else { + CrashLogging.logError(Error.cannotCreateAtomicProxyURL(components: components)) + provide(URLRequest(url: url)) + return + } + + guard let request = tokenAuthenticatedWPComRequest(for: finalURL) else { + CrashLogging.logError(Error.cannotFindAuthenticationToken(url: finalURL)) + provide(URLRequest(url: url)) + return + } + + provide(request) + } + + // MARK: - Adding the Auth Token + + private func tokenAuthenticatedWPComRequest(for url: URL) -> URLRequest? { + guard let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), + let authToken = account.authToken else { + return nil + } + + return tokenAuthenticatedWPComRequest(for: url, authToken: authToken) + } + + private func tokenAuthenticatedWPComRequest(for url: URL, authToken: String) -> URLRequest { + var request = URLRequest(url: url) + request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") + return request + } +} diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 8ba8866d583c..b9696d53cd56 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -137,8 +137,9 @@ import MobileCoreServices let request: URLRequest if url.isFileURL { request = URLRequest(url: url) - } else if let source = source, source.isPrivateOnWPCom, PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { - request = PrivateSiteURLProtocol.requestForPrivateSite(from: url) + } else if let source = source, source.isPrivateOnWPCom, url.isHostedAtWPCom() { + let mediaAuthenticator = MediaRequestAuthenticator() + request = mediaAuthenticator.authenticatedRequestForPrivateSite(for: url) } else { if let photonUrl = getPhotonUrl(for: url, size: size), source != nil { @@ -156,7 +157,7 @@ import MobileCoreServices if url.isFileURL { downloadImage(from: url) } else if let source = source { - if source.isPrivateOnWPCom && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + if source.isPrivateOnWPCom && url.isHostedAtWPCom() { loadPrivateImage(with: url, from: source, preferredSize: size) } else if source.isSelfHostedWithCredentials { downloadImage(from: url) @@ -174,7 +175,9 @@ import MobileCoreServices let scale = UIScreen.main.scale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let scaledURL = WPImageURLHelper.imageURLWithSize(scaledSize, forImageURL: url) - let request = PrivateSiteURLProtocol.requestForPrivateSite(from: scaledURL) + + let mediaAuthenticator = MediaRequestAuthenticator() + let request = mediaAuthenticator.authenticatedRequestForPrivateSite(for: scaledURL) downloadImage(from: request) } diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 28c17f692240..6f82ed3bb6fa 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -360,7 +360,7 @@ class AztecPostViewController: UIViewController, PostEditor { /// Active Downloads /// - fileprivate var activeMediaRequests = [ImageDownloader.Task]() + fileprivate var activeMediaRequests = [Operation]() /// Media Library Data Source /// diff --git a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift index 33e0eda2d8e1..09bab44e6964 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift @@ -2,7 +2,7 @@ import Aztec class AztecAttachmentDelegate: TextViewAttachmentDelegate { private let post: AbstractPost - private var activeMediaRequests = [ImageDownloader.Task]() + private var activeMediaRequests = [Operation]() private let mediaUtility = EditorMediaUtility() init(post: AbstractPost) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 06f23eb8d351..f31330034750 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -1,6 +1,67 @@ import Aztec import Gridicons +final class ImageDownload: AsyncOperation { + let request: URLRequest + + init(request: URLRequest) { + self.request = request + } + + override func main() { + ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in + guard let self = self else { + return + } + + DispatchQueue.main.async { + guard let image = image else { + DDLogError("Unable to download image for attachment with url = \(self.request.url). Details: \(String(describing: error?.localizedDescription))") + if let error = error { + //failure(error) + } else { + //failure(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)) + } + + self.cancel() + return + } + + //success(image) + self.state = .isFinished + } + } + + self.state = .isFinished + } +} + +final class ImageDownloadAuthentication: AsyncOperation { + let url: URL + let blog: Blog + + init(url: URL, blog: Blog) { + self.url = url + self.blog = blog + } + + override func main() { + let mediaRequestAuthenticator = MediaRequestAuthenticator() + + mediaRequestAuthenticator.authenticatedRequest( + for: url, + blog: blog, + onComplete: { request in + let download = ImageDownload(request: request) + self.addDependency(download) + download.start() + }, + onFailure: { error in + self.cancel() + }) + } +} + class EditorMediaUtility { private struct Constants { @@ -45,40 +106,74 @@ class EditorMediaUtility { } - func downloadImage(from url: URL, post: AbstractPost, success: @escaping (UIImage) -> Void, onFailure failure: @escaping (Error) -> Void) -> ImageDownloader.Task { + func downloadImage( + from url: URL, + post: AbstractPost, + success: @escaping (UIImage) -> Void, + onFailure failure: @escaping (Error) -> Void) -> Operation { + let imageMaxDimension = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) //use height zero to maintain the aspect ratio when fetching let size = CGSize(width: imageMaxDimension, height: 0) let scale = UIScreen.main.scale + return downloadImage(from: url, size: size, scale: scale, post: post, success: success, onFailure: failure) } - func downloadImage(from url: URL, size requestSize: CGSize, scale: CGFloat, post: AbstractPost, success: @escaping (UIImage) -> Void, onFailure failure: @escaping (Error) -> Void) -> ImageDownloader.Task { - var requestURL = url + func downloadImage( + from url: URL, + size requestSize: CGSize, + scale: CGFloat, post: AbstractPost, + success: @escaping (UIImage) -> Void, + onFailure failure: @escaping (Error) -> Void) -> Operation { //}-> ImageDownloader.Task { + let imageMaxDimension = max(requestSize.width, requestSize.height) //use height zero to maintain the aspect ratio when fetching var size = CGSize(width: imageMaxDimension, height: 0) - let request: URLRequest + let requestURL: URL if url.isFileURL { - request = URLRequest(url: url) - } else if post.blog.isHostedAtWPcom && post.blog.isPrivate() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + requestURL = url + } else if post.blog.isHostedAtWPcom && post.blog.isPrivate() && url.isHostedAtWPCom() { // private wpcom image needs special handling. // the size that WPImageHelper expects is pixel size size.width = size.width * scale - requestURL = WPImageURLHelper.imageURLWithSize(size, forImageURL: requestURL) - request = PrivateSiteURLProtocol.requestForPrivateSite(from: requestURL) + requestURL = WPImageURLHelper.imageURLWithSize(size, forImageURL: url) } else if !post.blog.isHostedAtWPcom && post.blog.isBasicAuthCredentialStored() { size.width = size.width * scale - requestURL = WPImageURLHelper.imageURLWithSize(size, forImageURL: requestURL) - request = URLRequest(url: requestURL) + requestURL = WPImageURLHelper.imageURLWithSize(size, forImageURL: url) } else { // the size that PhotonImageURLHelper expects is points size - requestURL = PhotonImageURLHelper.photonURL(with: size, forImageURL: requestURL) - request = URLRequest(url: requestURL) + requestURL = PhotonImageURLHelper.photonURL(with: size, forImageURL: url) } - return ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in + // NUEVO CODIGO + /* + let mediaRequestAuthenticator = MediaRequestAuthenticator() + + mediaRequestAuthenticator.authenticatedRequest( + for: requestURL, + blog: post.blog, + onComplete: { request in + // aca deberia llamarse el request... + }, + onFailure: { error in + failure(error) + }) + + let authOperation = AsyncOperation() + let task = URLSession(configuration: .ephemeral).dataTask(with: URL(url: "www.google.com")!) { (data, response, error) in + authOperation + }*/ + + let downloadOperation = ImageDownloadAuthentication(url: requestURL, blog: post.blog) + downloadOperation.start() + + return downloadOperation + + /* + // VIEJO CODIGO + let task = ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in guard let _ = self else { return } @@ -97,6 +192,8 @@ class EditorMediaUtility { success(image) } } + + return task*/ } static func fetchRemoteVideoURL(for media: Media, in post: AbstractPost, completion: @escaping ( Result<(videoURL: URL, posterURL: URL?), Error> ) -> Void) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift index 2f3cc16761ba..8f50f0b05db5 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift @@ -6,7 +6,7 @@ import MediaEditor We need the full high-quality image in the Media Editor. */ class GutenbergMediaEditorImage: AsyncImage { - private var tasks: [URLSessionDataTask] = [] + private var tasks: [Operation] = [] private var originalURL: URL diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h index 1a29eb914c94..35cc0999bff7 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h @@ -20,8 +20,8 @@ + (void)registerPrivateSiteURLProtocol; + (void)unregisterPrivateSiteURLProtocol; -+ (nonnull NSURLRequest *)requestForPrivateSiteFromURL:(nonnull NSURL *)url; +//+ (nonnull NSURLRequest *)requestForPrivateSiteFromURL:(nonnull NSURL *)url; -+ (BOOL)urlGoesToWPComSite:(nonnull NSURL *)url; +//+ (BOOL)urlGoesToWPComSite:(nonnull NSURL *)url; @end diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m index 236a8e9322a9..2156ee64cb48 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m @@ -100,7 +100,7 @@ + (BOOL)requestGoesToWPComSite:(NSURLRequest *)request { return [self urlGoesToWPComSite:request.URL]; } - +/* + (BOOL)urlGoesToWPComSite:(NSURL *)url { if ([url.scheme isEqualToString:@"https"] && [url.host hasSuffix:@".wordpress.com"]) { @@ -108,8 +108,8 @@ + (BOOL)urlGoesToWPComSite:(NSURL *)url } return NO; -} - +}*/ +/* + (NSURLRequest *)requestForPrivateSiteFromURL:(NSURL *)url { if (![self urlGoesToWPComSite:url]) { @@ -123,7 +123,7 @@ + (NSURLRequest *)requestForPrivateSiteFromURL:(NSURL *)url NSString *bearerToken = [NSString stringWithFormat:@"Bearer %@", [self bearerToken]]; [request addValue:bearerToken forHTTPHeaderField:@"Authorization"]; return request; -} +}*/ - (void)startLoading { diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift index b1ede29424e0..9ecde59458c8 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift @@ -2,7 +2,7 @@ import Foundation import AutomatticTracks extension PrivateSiteURLProtocol { - +/* enum Error: Swift.Error { case cannotBreakDownURLIntoComponents(url: URL) case cannotFindWPContentInPhotonPath(components: URLComponents) @@ -36,8 +36,8 @@ extension PrivateSiteURLProtocol { let request = URLRequest(url: url) provide(request) } - } - + }*/ +/* /// Photon URLs are currently not working for private atomic sites, so this is a workaround /// to replace those URLs with working URLs. /// @@ -133,5 +133,5 @@ extension PrivateSiteURLProtocol { var request = URLRequest(url: url) request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") return request - } + }*/ } diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift index 041113b144c9..44a5703a1478 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift @@ -6,7 +6,7 @@ class RevisionPreviewTextViewManager: NSObject { var post: AbstractPost? private let mediaUtility = EditorMediaUtility() - private var activeMediaRequests = [ImageDownloader.Task]() + private var activeMediaRequests = [Operation]() private enum Constants { static let mediaPlaceholderImageSize = CGSize(width: 128, height: 128) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift index 840572ad37f5..8de4c29076f4 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift @@ -295,12 +295,23 @@ import Gridicons let size = avatarImageView.frame.size.width * UIScreen.main.scale if let url = provider.siteIconForDisplay(ofSize: Int(size)) { + let mediaRequestAuthenticator = MediaRequestAuthenticator() + + mediaRequestAuthenticator.authenticatedWPComRequest( + for: url, + siteID: provider.siteID().intValue, + inPrivateBlog: provider.isPrivate(), + inAtomicBlog: false, + onComplete: { request in + self.avatarImageView.downloadImage(usingRequest: request) + }) + /* if provider.isPrivate() { let request = PrivateSiteURLProtocol.requestForPrivateSite(from: url) avatarImageView.downloadImage(usingRequest: request) } else { avatarImageView.downloadImage(from: url) - } + }*/ avatarImageView.isHidden = false } else { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 319dc0317be4..80a5f231c0a5 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2028,6 +2028,7 @@ F12FA5D92428FA8F0054DA21 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12FA5D82428FA8F0054DA21 /* AuthenticationService.swift */; }; F15A230420A3EBE300625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; F15A230520A3ECC500625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; + F16006322433E67200B32F34 /* MediaRequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */; }; F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */; }; F16601C423E9E783007950AE /* SharingAuthorizationWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16601C323E9E783007950AE /* SharingAuthorizationWebViewController.swift */; }; F16C35D623F33DE400C81331 /* PageAutoUploadMessageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16C35D523F33DE400C81331 /* PageAutoUploadMessageProvider.swift */; }; @@ -4619,6 +4620,7 @@ F14B5F74208E64F900439554 /* Version.public.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.public.xcconfig; sourceTree = ""; }; F14B5F75208E64F900439554 /* Version.internal.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.internal.xcconfig; sourceTree = ""; }; F14E844C2317252200D0C63E /* WordPress 90.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 90.xcdatamodel"; sourceTree = ""; }; + F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaRequestAuthenticator.swift; sourceTree = ""; }; F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Mbar.swift"; sourceTree = ""; }; F16601C323E9E783007950AE /* SharingAuthorizationWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingAuthorizationWebViewController.swift; sourceTree = ""; }; F16C35D523F33DE400C81331 /* PageAutoUploadMessageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PageAutoUploadMessageProvider.swift; path = ../Post/PageAutoUploadMessageProvider.swift; sourceTree = ""; }; @@ -7296,6 +7298,7 @@ children = ( E11DA4921E03E03F00CF07A8 /* Pinghub.swift */, E1C2260623901AAD0021D03C /* WordPressOrgRestApi+WordPress.swift */, + F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */, ); path = Networking; sourceTree = ""; @@ -11699,6 +11702,7 @@ 40C403F42215D66A00E8C894 /* TopViewedAuthorStatsRecordValue+CoreDataProperties.swift in Sources */, 7E4A773920F80417001C706D /* ActivityPluginRange.swift in Sources */, 7462BFD02028C49800B552D8 /* ShareNoticeViewModel.swift in Sources */, + F16006322433E67200B32F34 /* MediaRequestAuthenticator.swift in Sources */, 17A4A36920EE51870071C2CA /* Routes+Reader.swift in Sources */, 0807CB721CE670A800CDBDAC /* WPContentSearchHelper.swift in Sources */, 9A73B7152362FBAE004624A8 /* SiteStatsViewModel+AsyncBlock.swift in Sources */, From 90c054553a07cb546a0e7ae956fac99141cd4a98 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 2 Apr 2020 13:28:41 -0300 Subject: [PATCH 08/80] Builds again! WIP: improving Atomic support. --- WordPress/Classes/Models/ReaderPost.m | 6 + .../MediaRequestAuthenticator.swift | 7 +- .../AtomicAuthenticationService.swift | 2 + .../Utility/Media/ImageDownloader.swift | 5 +- .../AztecPostViewController.swift | 2 +- .../Gutenberg/AztecAttachmentDelegate.swift | 2 +- .../Gutenberg/EditorMediaUtility.swift | 116 +++++++++--------- .../Utils/GutenbergMediaEditorImage.swift | 2 +- .../ViewRelated/Post/PrivateSiteURLProtocol.m | 4 +- .../RevisionPreviewTextViewManager.swift | 2 +- 10 files changed, 83 insertions(+), 65 deletions(-) diff --git a/WordPress/Classes/Models/ReaderPost.m b/WordPress/Classes/Models/ReaderPost.m index 61f1156dbec3..0f2431fbc4d3 100644 --- a/WordPress/Classes/Models/ReaderPost.m +++ b/WordPress/Classes/Models/ReaderPost.m @@ -65,6 +65,12 @@ - (BOOL)isCrossPost return self.crossPostMeta != nil; } +- (BOOL)isAtomic +{ + // DRM: before merging I need to load this from the endpoint. + return false; +} + - (BOOL)isPrivate { return self.isBlogPrivate; diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 3d00ca9290a8..3a170bc612b4 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -10,7 +10,7 @@ extension URL { // the check for now to avoid breaking things. return scheme == "https" && host?.hasSuffix(".wordpress.com") ?? false } - + fileprivate func isPhoton() -> Bool { return host == photonHost } @@ -48,9 +48,10 @@ class MediaRequestAuthenticator { func authenticatedRequest( for url: URL, blog: Blog, + using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> (), onFailure fail: @escaping (Error) -> ()) { - + guard blog.isAccessibleThroughWPCom() else { let request = URLRequest(url: url) provide(request) @@ -88,6 +89,7 @@ class MediaRequestAuthenticator { siteID: Int, inPrivateBlog: Bool, inAtomicBlog: Bool, + using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> ()) { guard inPrivateBlog else { @@ -136,6 +138,7 @@ class MediaRequestAuthenticator { private func authenticatedRequestForPrivateAtomicSite( for url: URL, siteID: Int, + using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> ()) { guard !url.isHostedAtWPCom(), diff --git a/WordPress/Classes/Services/AtomicAuthenticationService.swift b/WordPress/Classes/Services/AtomicAuthenticationService.swift index 0a6747dc69bd..7936421976b8 100644 --- a/WordPress/Classes/Services/AtomicAuthenticationService.swift +++ b/WordPress/Classes/Services/AtomicAuthenticationService.swift @@ -20,6 +20,7 @@ class AtomicAuthenticationService { func getAuthCookie( siteID: Int, + using session: URLSession = URLSession.shared, success: @escaping (_ cookie: HTTPCookie) -> Void, failure: @escaping (Error) -> Void) { @@ -30,6 +31,7 @@ class AtomicAuthenticationService { into cookieJar: CookieJar, username: String, siteID: Int, + using session: URLSession = URLSession.shared, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { diff --git a/WordPress/Classes/Utility/Media/ImageDownloader.swift b/WordPress/Classes/Utility/Media/ImageDownloader.swift index 40c01ea0a46a..e88cdd3b39f8 100644 --- a/WordPress/Classes/Utility/Media/ImageDownloader.swift +++ b/WordPress/Classes/Utility/Media/ImageDownloader.swift @@ -1,5 +1,6 @@ import Foundation +extension URLSessionTask: CancellableTask {} // MARK: - Image Downloading Tool // @@ -26,7 +27,7 @@ class ImageDownloader { /// Downloads the UIImage resource at the specified URL. On completion the received closure will be executed. /// @discardableResult - func downloadImage(at url: URL, completion: @escaping (UIImage?, Error?) -> Void) -> Task { + func downloadImage(at url: URL, completion: @escaping (UIImage?, Error?) -> Void) -> CancellableTask { var request = URLRequest(url: url) request.httpShouldHandleCookies = false request.addValue("image/*", forHTTPHeaderField: "Accept") @@ -37,7 +38,7 @@ class ImageDownloader { /// Downloads the UIImage resource at the specified endpoint. On completion the received closure will be executed. /// @discardableResult - func downloadImage(for request: URLRequest, completion: @escaping (UIImage?, Error?) -> Void) -> Task { + func downloadImage(for request: URLRequest, completion: @escaping (UIImage?, Error?) -> Void) -> CancellableTask { let task = session.dataTask(with: request) { (data, _, error) in guard let data = data, let image = UIImage(data: data) else { let error = error ?? ImageDownloaderError.failed diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 6f82ed3bb6fa..01669a481519 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -360,7 +360,7 @@ class AztecPostViewController: UIViewController, PostEditor { /// Active Downloads /// - fileprivate var activeMediaRequests = [Operation]() + fileprivate var activeMediaRequests = [CancellableTask]() /// Media Library Data Source /// diff --git a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift index 09bab44e6964..ef6eb4c974ca 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift @@ -2,7 +2,7 @@ import Aztec class AztecAttachmentDelegate: TextViewAttachmentDelegate { private let post: AbstractPost - private var activeMediaRequests = [Operation]() + private var activeMediaRequests = [CancellableTask]() private let mediaUtility = EditorMediaUtility() init(post: AbstractPost) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index f31330034750..f7ed6e3d841e 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -1,48 +1,19 @@ import Aztec import Gridicons -final class ImageDownload: AsyncOperation { - let request: URLRequest - - init(request: URLRequest) { - self.request = request - } - - override func main() { - ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in - guard let self = self else { - return - } - - DispatchQueue.main.async { - guard let image = image else { - DDLogError("Unable to download image for attachment with url = \(self.request.url). Details: \(String(describing: error?.localizedDescription))") - if let error = error { - //failure(error) - } else { - //failure(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)) - } - - self.cancel() - return - } - - //success(image) - self.state = .isFinished - } - } - - self.state = .isFinished - } -} +extension Operation: CancellableTask {} -final class ImageDownloadAuthentication: AsyncOperation { +final class ImageDownload: AsyncOperation { let url: URL let blog: Blog + private let onSuccess: (UIImage) -> () + private let onFailure: (Error) -> () - init(url: URL, blog: Blog) { + init(url: URL, blog: Blog, onSuccess: @escaping (UIImage) -> (), onFailure: @escaping (Error) -> ()) { self.url = url self.blog = blog + self.onSuccess = onSuccess + self.onFailure = onFailure } override func main() { @@ -52,16 +23,46 @@ final class ImageDownloadAuthentication: AsyncOperation { for: url, blog: blog, onComplete: { request in - let download = ImageDownload(request: request) - self.addDependency(download) - download.start() + ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in + guard let self = self else { + return + } + + self.state = .isFinished + + DispatchQueue.main.async { + guard let image = image else { + DDLogError("Unable to download image for attachment with url = \(String(describing: request.url)). Details: \(String(describing: error?.localizedDescription))") + if let error = error { + self.onFailure(error) + } else { + self.onFailure(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)) + } + + return + } + + self.onSuccess(image) + } + } }, onFailure: { error in - self.cancel() + self.state = .isFinished + self.onFailure(error) }) } } +protocol CancellableTask { + func cancel() +} + +extension URLSession: CancellableTask { + func cancel() { + invalidateAndCancel() + } +} + class EditorMediaUtility { private struct Constants { @@ -110,7 +111,7 @@ class EditorMediaUtility { from url: URL, post: AbstractPost, success: @escaping (UIImage) -> Void, - onFailure failure: @escaping (Error) -> Void) -> Operation { + onFailure failure: @escaping (Error) -> Void) -> CancellableTask { let imageMaxDimension = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) //use height zero to maintain the aspect ratio when fetching @@ -125,7 +126,7 @@ class EditorMediaUtility { size requestSize: CGSize, scale: CGFloat, post: AbstractPost, success: @escaping (UIImage) -> Void, - onFailure failure: @escaping (Error) -> Void) -> Operation { //}-> ImageDownloader.Task { + onFailure failure: @escaping (Error) -> Void) -> CancellableTask { //}-> ImageDownloader.Task { let imageMaxDimension = max(requestSize.width, requestSize.height) //use height zero to maintain the aspect ratio when fetching @@ -147,8 +148,23 @@ class EditorMediaUtility { requestURL = PhotonImageURLHelper.photonURL(with: size, forImageURL: url) } - // NUEVO CODIGO + let imageDownload = ImageDownload( + url: requestURL, + blog: post.blog, + onSuccess: success, + onFailure: failure) + + imageDownload.start() + return imageDownload + + /* + let operationQueue = OperationQueue() + + operationQueue.addOperation(<#T##op: Operation##Operation#>) + + // NUEVO CODIGO + let mediaRequestAuthenticator = MediaRequestAuthenticator() mediaRequestAuthenticator.authenticatedRequest( @@ -160,18 +176,7 @@ class EditorMediaUtility { onFailure: { error in failure(error) }) - - let authOperation = AsyncOperation() - let task = URLSession(configuration: .ephemeral).dataTask(with: URL(url: "www.google.com")!) { (data, response, error) in - authOperation - }*/ - - let downloadOperation = ImageDownloadAuthentication(url: requestURL, blog: post.blog) - downloadOperation.start() - return downloadOperation - - /* // VIEJO CODIGO let task = ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in guard let _ = self else { @@ -193,7 +198,8 @@ class EditorMediaUtility { } } - return task*/ + return task + */ } static func fetchRemoteVideoURL(for media: Media, in post: AbstractPost, completion: @escaping ( Result<(videoURL: URL, posterURL: URL?), Error> ) -> Void) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift index 8f50f0b05db5..034e931e91f2 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift @@ -6,7 +6,7 @@ import MediaEditor We need the full high-quality image in the Media Editor. */ class GutenbergMediaEditorImage: AsyncImage { - private var tasks: [Operation] = [] + private var tasks: [CancellableTask] = [] private var originalURL: URL diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m index 2156ee64cb48..44585fff9032 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m @@ -100,7 +100,7 @@ + (BOOL)requestGoesToWPComSite:(NSURLRequest *)request { return [self urlGoesToWPComSite:request.URL]; } -/* + + (BOOL)urlGoesToWPComSite:(NSURL *)url { if ([url.scheme isEqualToString:@"https"] && [url.host hasSuffix:@".wordpress.com"]) { @@ -108,7 +108,7 @@ + (BOOL)urlGoesToWPComSite:(NSURL *)url } return NO; -}*/ +} /* + (NSURLRequest *)requestForPrivateSiteFromURL:(NSURL *)url { diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift index 44a5703a1478..e315d318e970 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift @@ -6,7 +6,7 @@ class RevisionPreviewTextViewManager: NSObject { var post: AbstractPost? private let mediaUtility = EditorMediaUtility() - private var activeMediaRequests = [Operation]() + private var activeMediaRequests = [CancellableTask]() private enum Constants { static let mediaPlaceholderImageSize = CGSize(width: 128, height: 128) From fd549ae1b8be5aabf626f17551dab3ea2f81ed5d Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 2 Apr 2020 13:30:44 -0300 Subject: [PATCH 09/80] rake lint:autocorrect --- .../Networking/MediaRequestAuthenticator.swift | 4 ++-- .../ViewRelated/Gutenberg/EditorMediaUtility.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 3a170bc612b4..5f9655e48b53 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -10,7 +10,7 @@ extension URL { // the check for now to avoid breaking things. return scheme == "https" && host?.hasSuffix(".wordpress.com") ?? false } - + fileprivate func isPhoton() -> Bool { return host == photonHost } @@ -51,7 +51,7 @@ class MediaRequestAuthenticator { using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> (), onFailure fail: @escaping (Error) -> ()) { - + guard blog.isAccessibleThroughWPCom() else { let request = URLRequest(url: url) provide(request) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index f7ed6e3d841e..70d7748d71da 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -27,9 +27,9 @@ final class ImageDownload: AsyncOperation { guard let self = self else { return } - + self.state = .isFinished - + DispatchQueue.main.async { guard let image = image else { DDLogError("Unable to download image for attachment with url = \(String(describing: request.url)). Details: \(String(describing: error?.localizedDescription))") @@ -38,7 +38,7 @@ final class ImageDownload: AsyncOperation { } else { self.onFailure(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)) } - + return } @@ -153,11 +153,11 @@ class EditorMediaUtility { blog: post.blog, onSuccess: success, onFailure: failure) - + imageDownload.start() return imageDownload - - + + /* let operationQueue = OperationQueue() From b7060331360e852f85358a350d87d103b7a25c3e Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 2 Apr 2020 14:36:37 -0300 Subject: [PATCH 10/80] WIP: implementing Atomic Private site support. --- .../MediaRequestAuthenticator.swift | 40 ++++++++++++++- .../Classes/Utility/Media/ImageLoader.swift | 49 +++++++++++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 5f9655e48b53..31f749e45ea1 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -33,6 +33,44 @@ class MediaRequestAuthenticator { case cannotFindWPContentInPhotonPath(components: URLComponents) } + enum ImageHost { + case publicSite + case privateSelfHostedSite + case privateWPComSite + case privateAtomicWPComSite(siteID: Int) + } + + func authenticatedWPComRequest( + for url: URL, + hostedIn host: ImageHost, + onComplete provide: @escaping (URLRequest) -> ()) { + + // We want to make sure we're never sending credentials + // to a URL that's not safe. + guard url.isHostedAtWPCom() || url.isPhoton() else { + let request = URLRequest(url: url) + provide(request) + return + } + + switch host { + case .publicSite: fallthrough + case .privateSelfHostedSite: + // The authentication for these is handled elsewhere + let request = URLRequest(url: url) + provide(request) + case .privateWPComSite: + let request = authenticatedRequestForPrivateSite(for: url) + provide(request) + case .privateAtomicWPComSite(let siteID): + if url.isPhoton() { + authenticatedRequestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) + } else { + authenticatedRequestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) + } + } + } + // MARK: - Request Authentication /// Pass this method a media URL, and it will handle all the necessary logic to provide the caller @@ -48,7 +86,6 @@ class MediaRequestAuthenticator { func authenticatedRequest( for url: URL, blog: Blog, - using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> (), onFailure fail: @escaping (Error) -> ()) { @@ -89,7 +126,6 @@ class MediaRequestAuthenticator { siteID: Int, inPrivateBlog: Bool, inAtomicBlog: Bool, - using session: URLSession = URLSession.shared, onComplete provide: @escaping (URLRequest) -> ()) { guard inPrivateBlog else { diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index b9696d53cd56..ea3d4381d212 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -1,15 +1,20 @@ import MobileCoreServices +import AutomatticTracks /// Protocol used to abstract the information needed to load post related images. /// @objc protocol ImageSourceInformation { - var isAtomicOnWPCom: Bool { get } + var isAtomic: Bool { get } /// The post is private and hosted on WPcom. /// Redundant name due to naming conflict. /// - var isPrivateOnWPCom: Bool { get } + var isPrivate: Bool { get } + + /// Whether the post is accessible through WPCom. + /// + var isAccessibleThroughWPCom: Bool { get } /// The blog is self-hosted and there is already a basic auth credential stored. /// @@ -134,10 +139,46 @@ import MobileCoreServices /// Load an animated image from the given URL. /// private func loadGif(with url: URL, from source: ImageSourceInformation?, preferredSize size: CGSize = .zero) { + guard !url.isFileURL, + let source = source else { + let request = URLRequest(url: url) + self.downloadGif(from: request) + return + } + + let host: MediaRequestAuthenticator.ImageHost + + if !source.isPrivate { + host = .publicSite + } else if !source.isAccessibleThroughWPCom { + host = .privateSelfHostedSite + } else if !source.isAtomic { + host = .privateWPComSite + } else if let siteID = source.siteID?.intValue { + host = .privateAtomicWPComSite(siteID: siteID) + } else { + // We're logging an error since this scenario should not be possible, and then + // just trying to load the resource as if it was WPCom private. This is to + // avoid crashing the app here, since failing to load the image is a better + // option for UX. + CrashLogging.logError(error) + host = .publicSite + } + + let mediaAuthenticator = MediaRequestAuthenticator() + mediaAuthenticator.authenticatedWPComRequest( + for: url, + hostedIn: host) { request in + self.downloadGif(from: request) + } + + /* let request: URLRequest if url.isFileURL { request = URLRequest(url: url) - } else if let source = source, source.isPrivateOnWPCom, url.isHostedAtWPCom() { + } else if let source = source, + source.isAccessibleThroughWPCom && source.isPrivate { + let mediaAuthenticator = MediaRequestAuthenticator() request = mediaAuthenticator.authenticatedRequestForPrivateSite(for: url) } else { @@ -148,7 +189,7 @@ import MobileCoreServices request = URLRequest(url: url) } } - downloadGif(from: request) + downloadGif(from: request)*/ } /// Load a static image from the given URL. From 228cc2a4071ec7d7f32739f7b906e8095f3097b5 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 2 Apr 2020 19:44:30 -0300 Subject: [PATCH 11/80] WIP: implementing Atomic Private Sites support. --- .../Blog+ImageSourceInformation.swift | 19 --- .../ReaderCardContent+PostInformation.swift | 25 --- WordPress/Classes/Networking/MediaHost.swift | 119 ++++++++++++++ .../MediaRequestAuthenticator.swift | 151 +++++------------- .../Classes/Utility/Media/ImageLoader.swift | 67 ++++---- .../Gutenberg/EditorMediaUtility.swift | 7 +- .../ViewRelated/Post/PostCompactCell.swift | 13 +- .../Reader/ReaderDetailViewController.swift | 14 +- .../Reader/ReaderPostCardCell.swift | 44 ++--- .../Views/WPRichText/WPRichContentView.swift | 25 +-- .../Views/WPRichText/WPRichTextImage.swift | 6 +- WordPress/WordPress.xcodeproj/project.pbxproj | 14 +- 12 files changed, 250 insertions(+), 254 deletions(-) delete mode 100644 WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift delete mode 100644 WordPress/Classes/Models/ReaderCardContent+PostInformation.swift create mode 100644 WordPress/Classes/Networking/MediaHost.swift diff --git a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift b/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift deleted file mode 100644 index 704ad96994ac..000000000000 --- a/WordPress/Classes/Extensions/Blog+ImageSourceInformation.swift +++ /dev/null @@ -1,19 +0,0 @@ - -extension Blog: ImageSourceInformation { - - var isAtomicOnWPCom: Bool { - return isAtomic() - } - - var isPrivateOnWPCom: Bool { - return isHostedAtWPcom && isPrivate() - } - - var isSelfHostedWithCredentials: Bool { - return !isHostedAtWPcom && isBasicAuthCredentialStored() - } - - var siteID: NSNumber? { - return dotComID - } -} diff --git a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift b/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift deleted file mode 100644 index fd5883b1d3de..000000000000 --- a/WordPress/Classes/Models/ReaderCardContent+PostInformation.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -class ReaderCardContent: ImageSourceInformation { - private let originalProvider: ReaderPostContentProvider - - init(provider: ReaderPostContentProvider) { - originalProvider = provider - } - - var isAtomicOnWPCom: Bool { - return originalProvider.isAtomic() - } - - var isPrivateOnWPCom: Bool { - return originalProvider.isPrivate() && originalProvider.isWPCom() - } - - var isSelfHostedWithCredentials: Bool { - return !originalProvider.isWPCom() && !originalProvider.isJetpack() - } - - var siteID: NSNumber? { - return originalProvider.siteID() - } -} diff --git a/WordPress/Classes/Networking/MediaHost.swift b/WordPress/Classes/Networking/MediaHost.swift new file mode 100644 index 000000000000..5c02bdb0af8d --- /dev/null +++ b/WordPress/Classes/Networking/MediaHost.swift @@ -0,0 +1,119 @@ +// +// MediaHost.swift +// WordPress +// +// Created by Diego E. Rey Mendez on 2/4/20. +// Copyright © 2020 WordPress. All rights reserved. +// + +import Foundation + +/// Defines a media host for request authentication purposes. +/// +enum MediaHost { + case publicSite + case privateSelfHostedSite + case privateWPComSite + case privateAtomicWPComSite(siteID: Int) + + enum Error: Swift.Error { + case wpComWithoutSiteID + } + + init( + isAccessibleThroughWPCom: Bool, + isPrivate: Bool, + isAtomic: Bool, + siteID: Int?, + failure: (Error) -> ()) { + + guard isPrivate else { + self = .publicSite + return + } + + guard isAccessibleThroughWPCom else { + self = .privateSelfHostedSite + return + } + + guard isAtomic else { + self = .privateWPComSite + return + } + + guard let siteID = siteID else { + // This should actually not be possible. We have no good way to + // handle this. + failure(Error.wpComWithoutSiteID) + + // If the caller wants to kill execution, they can do it in the failure block + // call above. + // + // Otherwise they'll be able to continue trying to request the image as if it + // was hosted in a private WPCom site. This is the best we can offer with the + // provided input parameters. + self = .privateWPComSite + return + } + + self = .privateAtomicWPComSite(siteID: siteID) + } +} + +/// Extends `MediaRequestAuthenticator.ImageHost` so that we can easily +/// initialize it from a given `AbstractPost`. +/// +extension MediaHost { + init(with post: AbstractPost, failure: (BlogError) -> ()) { + self.init(with: post.blog, failure: failure) + } +} + +/// Extends `MediaRequestAuthenticator.ImageHost` so that we can easily +/// initialize it from a given `Blog`. +/// +extension MediaHost { + enum BlogError: Swift.Error { + case wpComWithoutSiteID(blog: Blog) + } + + init(with blog: Blog, failure: (BlogError) -> ()) { + self.init(isAccessibleThroughWPCom: blog.isAccessibleThroughWPCom(), + isPrivate: blog.isPrivate(), + isAtomic: blog.isAtomic(), + siteID: blog.dotComID?.intValue, + failure: { error in + switch error { + case .wpComWithoutSiteID: + // We can add valuable information by replacing this error case. + failure(BlogError.wpComWithoutSiteID(blog: blog)) + } + }) + } +} + +/// Extends `MediaRequestAuthenticator.ImageHost` so that we can easily +/// initialize it from a given `Blog`. +/// +extension MediaHost { + enum ReaderPostContentProviderError: Swift.Error { + case wpComWithoutSiteID(readerPostContentProvider: ReaderPostContentProvider) + } + + init(with readerPostContentProvider: ReaderPostContentProvider, failure: (ReaderPostContentProviderError) -> ()) { + let isAccessibleThroughWPCom = readerPostContentProvider.isWPCom() || readerPostContentProvider.isJetpack() + + self.init(isAccessibleThroughWPCom: isAccessibleThroughWPCom, + isPrivate: readerPostContentProvider.isPrivate(), + isAtomic: readerPostContentProvider.isAtomic(), + siteID: readerPostContentProvider.siteID()?.intValue, + failure: { error in + switch error { + case .wpComWithoutSiteID: + // We can add valuable information by replacing this error case. + failure(ReaderPostContentProviderError.wpComWithoutSiteID(readerPostContentProvider: readerPostContentProvider)) + } + }) + } +} diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 31f749e45ea1..fd4ed3b4032a 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -1,4 +1,3 @@ -import AutomatticTracks import Foundation fileprivate let photonHost = "i0.wp.com" @@ -33,44 +32,6 @@ class MediaRequestAuthenticator { case cannotFindWPContentInPhotonPath(components: URLComponents) } - enum ImageHost { - case publicSite - case privateSelfHostedSite - case privateWPComSite - case privateAtomicWPComSite(siteID: Int) - } - - func authenticatedWPComRequest( - for url: URL, - hostedIn host: ImageHost, - onComplete provide: @escaping (URLRequest) -> ()) { - - // We want to make sure we're never sending credentials - // to a URL that's not safe. - guard url.isHostedAtWPCom() || url.isPhoton() else { - let request = URLRequest(url: url) - provide(request) - return - } - - switch host { - case .publicSite: fallthrough - case .privateSelfHostedSite: - // The authentication for these is handled elsewhere - let request = URLRequest(url: url) - provide(request) - case .privateWPComSite: - let request = authenticatedRequestForPrivateSite(for: url) - provide(request) - case .privateAtomicWPComSite(let siteID): - if url.isPhoton() { - authenticatedRequestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) - } else { - authenticatedRequestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) - } - } - } - // MARK: - Request Authentication /// Pass this method a media URL, and it will handle all the necessary logic to provide the caller @@ -78,104 +39,76 @@ class MediaRequestAuthenticator { /// /// - Parameters: /// - url: the url for the media. - /// - blog: the blog associated with the passed media URL. + /// - host: the `MediaHost` for the requested Media. This is used for authenticating the requests. /// - onComplete: the closure that will be called once authentication is sorted out by this class. /// The request can be executed directly without having to do anything else in terms of /// authentication. /// func authenticatedRequest( for url: URL, - blog: Blog, + from host: MediaHost, onComplete provide: @escaping (URLRequest) -> (), onFailure fail: @escaping (Error) -> ()) { - guard blog.isAccessibleThroughWPCom() else { + // We want to make sure we're never sending credentials + // to a URL that's not safe. + guard url.isHostedAtWPCom() || url.isPhoton() else { let request = URLRequest(url: url) provide(request) return } - guard let siteID = blog.dotComID?.intValue else { - fail(Error.cannotFindSiteIDForSiteAvailableThroughWPCom(blog: blog)) - return - } - - authenticatedWPComRequest( - for: url, - siteID: siteID, - inPrivateBlog: blog.isPrivate(), - inAtomicBlog: blog.isAtomic(), - onComplete: provide) - } - - /// Same as above, but this method authenticates WPCom hosted requests only. - /// - /// - Parameters: - /// - url: the url for the media. - /// - siteID: the ID of the site associated with this media. It may not be the host though - /// as can be the case with photon URLs that are hosted elsewhere. - /// - inPrivateBlog: whether the owning site is private. - /// - inAtomicBlog: whether the owning site is atomic. This is relevant since atomic authentication - /// is not standard. - /// - onComplete: the closure that will be called once authentication is sorted out by this class. - /// The request can be executed directly without having to do anything else in terms of - /// authentication. - /// - func authenticatedWPComRequest( - for url: URL, - siteID: Int, - inPrivateBlog: Bool, - inAtomicBlog: Bool, - onComplete provide: @escaping (URLRequest) -> ()) { - - guard inPrivateBlog else { + switch host { + case .publicSite: fallthrough + case .privateSelfHostedSite: + // The authentication for these is handled elsewhere let request = URLRequest(url: url) provide(request) - return - } - - if url.isHostedAtWPCom() { - if inAtomicBlog { - authenticatedRequestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) + case .privateWPComSite: + authenticatedRequestForPrivateSite(for: url, onComplete: provide, onFailure: fail) + case .privateAtomicWPComSite(let siteID): + if url.isPhoton() { + authenticatedRequestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide, onFailure: fail) } else { - let request = authenticatedRequestForPrivateSite(for: url) - provide(request) + authenticatedRequestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide, onFailure: fail) } - } else if inAtomicBlog && url.isPhoton() { - authenticatedRequestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) - } else { - let request = URLRequest(url: url) - provide(request) } } // MARK: - Request Authentication: Specific Scenarios - func authenticatedRequestForPrivateSite(for url: URL) -> URLRequest { - guard !url.isHostedAtWPCom(), - var components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), - let authToken = account.authToken else { - return URLRequest(url: url) + private func authenticatedRequestForPrivateSite( + for url: URL, + onComplete provide: (URLRequest) -> (), + onFailure fail: (Error) -> ()) { + + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + fail(Error.cannotBreakDownURLIntoComponents(url: url)) + return } // Just in case, enforce HTTPs components.scheme = "https" guard let finalURL = components.url else { - CrashLogging.logError(Error.cannotCreatePrivateURL(components: components)) - return URLRequest(url: url) + fail(Error.cannotCreatePrivateURL(components: components)) + return } - let request = tokenAuthenticatedWPComRequest(for: finalURL, authToken: authToken) - return request + guard let request = tokenAuthenticatedWPComRequest(for: finalURL) else { + fail(Error.cannotFindAuthenticationToken(url: finalURL)) + return + } + + provide(request) } private func authenticatedRequestForPrivateAtomicSite( for url: URL, siteID: Int, using session: URLSession = URLSession.shared, - onComplete provide: @escaping (URLRequest) -> ()) { + onComplete provide: @escaping (URLRequest) -> (), + onFailure fail: @escaping (Error) -> ()) { guard !url.isHostedAtWPCom(), var components = URLComponents(url: url, resolvingAgainstBaseURL: true), @@ -191,8 +124,7 @@ class MediaRequestAuthenticator { components.scheme = "https" guard let finalURL = components.url else { - CrashLogging.logError(Error.cannotCreateAtomicURL(components: components)) - provide(URLRequest(url: url)) + fail(Error.cannotCreateAtomicURL(components: components)) return } @@ -219,17 +151,16 @@ class MediaRequestAuthenticator { private func authenticatedRequestForPrivateAtomicSiteThroughPhoton( for url: URL, siteID: Int, - onComplete provide: @escaping (URLRequest) -> ()) { + onComplete provide: @escaping (URLRequest) -> (), + onFailure fail: @escaping (Error) -> ()) { guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - CrashLogging.logError(Error.cannotBreakDownURLIntoComponents(url: url)) - provide(URLRequest(url: url)) + fail(Error.cannotBreakDownURLIntoComponents(url: url)) return } guard let wpContentRange = components.path.range(of: "/wp-content") else { - CrashLogging.logError(Error.cannotFindWPContentInPhotonPath(components: components)) - provide(URLRequest(url: url)) + fail(Error.cannotFindWPContentInPhotonPath(components: components)) return } @@ -240,14 +171,12 @@ class MediaRequestAuthenticator { components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file\(contentPath)" guard let finalURL = components.url else { - CrashLogging.logError(Error.cannotCreateAtomicProxyURL(components: components)) - provide(URLRequest(url: url)) + fail(Error.cannotCreateAtomicProxyURL(components: components)) return } guard let request = tokenAuthenticatedWPComRequest(for: finalURL) else { - CrashLogging.logError(Error.cannotFindAuthenticationToken(url: finalURL)) - provide(URLRequest(url: url)) + fail(Error.cannotFindAuthenticationToken(url: finalURL)) return } diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index ea3d4381d212..ada10988d6db 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -1,19 +1,22 @@ import MobileCoreServices import AutomatticTracks - +/* /// Protocol used to abstract the information needed to load post related images. /// @objc protocol ImageSourceInformation { + @objc var isAtomic: Bool { get } /// The post is private and hosted on WPcom. /// Redundant name due to naming conflict. /// + @objc var isPrivate: Bool { get } /// Whether the post is accessible through WPCom. /// + @objc var isAccessibleThroughWPCom: Bool { get } /// The blog is self-hosted and there is already a basic auth credential stored. @@ -21,7 +24,7 @@ import AutomatticTracks var isSelfHostedWithCredentials: Bool { get } var siteID: NSNumber? { get } -} +}*/ /// Class used together with `CachedAnimatedImageView` to facilitate the loading of both /// still images and animated gifs. @@ -77,24 +80,24 @@ import AutomatticTracks imageView.prepForReuse() } - @objc(loadImageWithURL:fromPost:andPreferredSize:) + //@objc(loadImageWithURL:fromPost:andPreferredSize:) /// Load an image from a specific post, using the given URL. Supports animated images (gifs) as well. /// /// - Parameters: /// - url: The URL to load the image from. - /// - post: The post where the image is loaded from. + /// - host: The `MediaHost` of the image. /// - size: The preferred size of the image to load. /// - func loadImage(with url: URL, from source: ImageSourceInformation, preferredSize size: CGSize = .zero) { + func loadImage(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { if url.isGif { - loadGif(with: url, from: source, preferredSize: size) + loadGif(with: url, from: host, preferredSize: size) } else { imageView.clean() - loadStaticImage(with: url, from: source, preferredSize: size) + loadStaticImage(with: url, from: host, preferredSize: size) } } - @objc(loadImageWithURL:success:error:) + //@objc(loadImageWithURL:success:error:) /// Load an image from a specific URL. As no source is provided, we can assume /// that this is from an external source. Supports animated images (gifs) as well. /// @@ -115,17 +118,17 @@ import AutomatticTracks } } - @objc(loadImageWithURL:fromPost:preferredSize:placeholder:success:error:) + //@objc(loadImageWithURL:fromPost:preferredSize:placeholder:success:error:) /// Load an image from a specific post, using the given URL. Supports animated images (gifs) as well. /// /// - Parameters: /// - url: The URL to load the image from. - /// - post: The post where the image is loaded from. + /// - host: The host of the image. /// - size: The preferred size of the image to load. You can pass height 0 to set width and preserve aspect ratio. /// - placeholder: A placeholder to show while the image is loading. /// - success: A closure to be called if the image was loaded successfully. /// - error: A closure to be called if there was an error loading the image. - func loadImage(with url: URL, from source: ImageSourceInformation, preferredSize size: CGSize = .zero, placeholder: UIImage?, success: ImageLoaderSuccessBlock?, error: ImageLoaderFailureBlock?) { + func loadImage(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero, placeholder: UIImage?, success: ImageLoaderSuccessBlock?, error: ImageLoaderFailureBlock?) { self.placeholder = placeholder successHandler = success @@ -138,37 +141,22 @@ import AutomatticTracks /// Load an animated image from the given URL. /// - private func loadGif(with url: URL, from source: ImageSourceInformation?, preferredSize size: CGSize = .zero) { - guard !url.isFileURL, - let source = source else { - let request = URLRequest(url: url) - self.downloadGif(from: request) - return + private func loadGif(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { + func downloadGIFWithoutAuthenticating() { + let request = URLRequest(url: url) + self.downloadGif(from: request) } - let host: MediaRequestAuthenticator.ImageHost - - if !source.isPrivate { - host = .publicSite - } else if !source.isAccessibleThroughWPCom { - host = .privateSelfHostedSite - } else if !source.isAtomic { - host = .privateWPComSite - } else if let siteID = source.siteID?.intValue { - host = .privateAtomicWPComSite(siteID: siteID) - } else { - // We're logging an error since this scenario should not be possible, and then - // just trying to load the resource as if it was WPCom private. This is to - // avoid crashing the app here, since failing to load the image is a better - // option for UX. - CrashLogging.logError(error) - host = .publicSite + guard !url.isFileURL, + let host = host else { + downloadGIFWithoutAuthenticating() + return } let mediaAuthenticator = MediaRequestAuthenticator() - mediaAuthenticator.authenticatedWPComRequest( + mediaAuthenticator.authenticatedRequest( for: url, - hostedIn: host) { request in + from: host) { request in self.downloadGif(from: request) } @@ -194,7 +182,10 @@ import AutomatticTracks /// Load a static image from the given URL. /// - private func loadStaticImage(with url: URL, from source: ImageSourceInformation?, preferredSize size: CGSize = .zero) { + private func loadStaticImage(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { + + + if url.isFileURL { downloadImage(from: url) } else if let source = source { @@ -212,7 +203,7 @@ import AutomatticTracks /// Loads the image from a private post hosted in WPCom. /// - private func loadPrivateImage(with url: URL, from source: ImageSourceInformation, preferredSize size: CGSize) { + private func loadPrivateImage(with url: URL, from host: MediaHost, preferredSize size: CGSize) { let scale = UIScreen.main.scale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let scaledURL = WPImageURLHelper.imageURLWithSize(scaledSize, forImageURL: url) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 70d7748d71da..19c8220b2310 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -1,3 +1,4 @@ +import AutomatticTracks import Aztec import Gridicons @@ -18,10 +19,14 @@ final class ImageDownload: AsyncOperation { override func main() { let mediaRequestAuthenticator = MediaRequestAuthenticator() + let host = MediaHost(with: blog) { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + } mediaRequestAuthenticator.authenticatedRequest( for: url, - blog: blog, + from: host, onComplete: { request in ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in guard let self = self else { diff --git a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift index 3aeda55a9ba7..0d2224518b44 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift @@ -1,3 +1,4 @@ +import AutomatticTracks import UIKit import Gridicons @@ -14,6 +15,10 @@ class PostCompactCell: UITableViewCell, ConfigurablePostView { @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var separator: UIView! + enum Error: Swift.Error { + case cannotCreateMediaHostFromPost(post: AbstractPost) + } + private weak var actionSheetDelegate: PostActionSheetDelegate? lazy var imageLoader: ImageLoader = { @@ -100,7 +105,13 @@ class PostCompactCell: UITableViewCell, ConfigurablePostView { private func configureFeaturedImage() { if let post = post, let url = post.featuredImageURL { featuredImageView.isHidden = false - imageLoader.loadImage(with: url, from: post, preferredSize: CGSize(width: featuredImageView.frame.width, height: featuredImageView.frame.height)) + + let host = MediaHost(with: post, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) + + imageLoader.loadImage(with: url, from: host, preferredSize: CGSize(width: featuredImageView.frame.width, height: featuredImageView.frame.height)) } else { featuredImageView.isHidden = true } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift index 951f49b86ce0..9817477af516 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift @@ -1,3 +1,4 @@ +import AutomatticTracks import Foundation import CocoaLumberjack import WordPressShared @@ -681,10 +682,14 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora return } - let postInfo = ReaderCardContent(provider: post) + let host = MediaHost(with: post, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) + let maxImageWidth = min(view.frame.width, view.frame.height) let imageWidthSize = CGSize(width: maxImageWidth, height: 0) // height 0: preserves aspect ratio. - featuredImageLoader.loadImage(with: featuredImageURL, from: postInfo, preferredSize: imageWidthSize, placeholder: nil, success: { [weak self] in + featuredImageLoader.loadImage(with: featuredImageURL, from: host, preferredSize: imageWidthSize, placeholder: nil, success: { [weak self] in guard let strongSelf = self, let size = strongSelf.featuredImageView.image?.size else { return } @@ -807,7 +812,10 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora return } - textView.isPrivate = post.isPrivate() + textView.mediaHost = MediaHost(with: post, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) textView.content = post.contentForDisplay() updateRichText() diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift index 8de4c29076f4..11dcc36c96d0 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift @@ -1,8 +1,8 @@ +import AutomatticTracks import Foundation import WordPressShared import Gridicons - @objc public protocol ReaderPostCellDelegate: NSObjectProtocol { func readerCell(_ cell: ReaderPostCardCell, headerActionForProvider provider: ReaderPostContentProvider) func readerCell(_ cell: ReaderPostCardCell, commentActionForProvider provider: ReaderPostContentProvider) @@ -286,7 +286,7 @@ import Gridicons } fileprivate func configureHeader() { - guard let provider = contentProvider else { + guard let contentProvider = contentProvider else { return } @@ -294,35 +294,34 @@ import Gridicons avatarImageView.image = nil let size = avatarImageView.frame.size.width * UIScreen.main.scale - if let url = provider.siteIconForDisplay(ofSize: Int(size)) { + if let url = contentProvider.siteIconForDisplay(ofSize: Int(size)) { + let mediaRequestAuthenticator = MediaRequestAuthenticator() + let host = MediaHost(with: contentProvider, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) - mediaRequestAuthenticator.authenticatedWPComRequest( + mediaRequestAuthenticator.authenticatedRequest( for: url, - siteID: provider.siteID().intValue, - inPrivateBlog: provider.isPrivate(), - inAtomicBlog: false, + from: host, onComplete: { request in self.avatarImageView.downloadImage(usingRequest: request) + self.avatarImageView.isHidden = false + }, + onFailure: { error in + CrashLogging.logError(error) + self.avatarImageView.isHidden = true }) - /* - if provider.isPrivate() { - let request = PrivateSiteURLProtocol.requestForPrivateSite(from: url) - avatarImageView.downloadImage(usingRequest: request) - } else { - avatarImageView.downloadImage(from: url) - }*/ - avatarImageView.isHidden = false - } else { avatarImageView.isHidden = true } var arr = [String]() - if let authorName = provider.authorForDisplay() { + if let authorName = contentProvider.authorForDisplay() { arr.append(authorName) } - if let blogName = provider.blogNameForDisplay() { + if let blogName = contentProvider.blogNameForDisplay() { arr.append(blogName) } blogNameLabel.text = arr.joined(separator: ", ") @@ -357,7 +356,7 @@ import Gridicons } fileprivate func configureFeaturedImage(_ featuredImageURL: URL) { - guard let content = contentProvider else { + guard let contentProvider = contentProvider else { return } @@ -365,8 +364,11 @@ import Gridicons currentLoadedCardImageURL = featuredImageURL.absoluteString featuredImageDesiredWidth = featuredImageView.frame.width let size = CGSize(width: featuredImageDesiredWidth, height: featuredMediaHeightConstraintConstant) - let postInfo = ReaderCardContent(provider: content) - imageLoader.loadImage(with: featuredImageURL, from: postInfo, preferredSize: size) + let host = MediaHost(with: contentProvider, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) + imageLoader.loadImage(with: featuredImageURL, from: host, preferredSize: size) } fileprivate func configureTitle() { diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index d055042358bf..bb63e5ebead9 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -53,11 +53,7 @@ class WPRichContentView: UITextView { } } - @objc var isAtomic = false - - /// Whether the view shows private content. Used when fetching images. - /// - @objc var isPrivate = false + var mediaHost: MediaHost = .publicSite @objc var content: String { get { @@ -343,12 +339,11 @@ extension WPRichContentView: WPTextAttachmentManagerDelegate { attachment.maxSize = CGSize(width: finalSize.width, height: finalSize.height) } - let contentInformation = ContentInformation(isAtomicOnWPCom: isAtomic, isPrivateOnWPCom: isPrivate, isSelfHostedWithCredentials: false, siteID: nil) let index = mediaArray.count let indexPath = IndexPath(row: index, section: 1) weak var weakImage = image - image.loadImage(from: contentInformation, preferedSize: finalSize, indexPath: indexPath, onSuccess: { [weak self] indexPath in + image.loadImage(from: mediaHost, preferedSize: finalSize, indexPath: indexPath, onSuccess: { [weak self] indexPath in guard let richMedia = self?.mediaArray[indexPath.row], let img = weakImage @@ -471,22 +466,6 @@ struct RichMedia { let attachment: WPTextAttachment } -// MARK: - ContentInformation (ImageSourceInformation) - -class ContentInformation: ImageSourceInformation { - var isAtomicOnWPCom: Bool - var isPrivateOnWPCom: Bool - var isSelfHostedWithCredentials: Bool - var siteID: NSNumber? - - init(isAtomicOnWPCom: Bool, isPrivateOnWPCom: Bool, isSelfHostedWithCredentials: Bool, siteID: NSNumber?) { - self.isAtomicOnWPCom = isAtomicOnWPCom - self.isPrivateOnWPCom = isPrivateOnWPCom - self.isSelfHostedWithCredentials = isSelfHostedWithCredentials - self.siteID = siteID - } -} - // This is very much based on Aztec.LayoutManager — most of this code is pretty much copy-pasted // from there and trimmed to only contain the relevant parts. @objc fileprivate class BlockquoteBackgroundLayoutManager: NSLayoutManager { diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift index e34cd6ae92fb..07683433c471 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift @@ -71,12 +71,12 @@ open class WPRichTextImage: UIControl, WPRichTextMediaAttachment { /// Load an image with the already-set contentURL property. Supports animated images (gifs) as well. /// /// - Parameters: - /// - contentInformation: The corresponding ImageSourceInformation for the contentURL + /// - host: The host for the media. /// - preferedSize: The prefered size of the image to load. /// - indexPath: The IndexPath where this view is located — returned as a param in success and error blocks. /// - onSuccess: A closure to be called if the image was loaded successfully. /// - onError: A closure to be called if there was an error loading the image. - func loadImage(from contentInformation: ImageSourceInformation, + func loadImage(from host: MediaHost, preferedSize size: CGSize = .zero, indexPath: IndexPath, onSuccess: ((IndexPath) -> Void)?, @@ -94,7 +94,7 @@ open class WPRichTextImage: UIControl, WPRichTextMediaAttachment { onError?(indexPath, error) } - imageLoader.loadImage(with: contentURL, from: contentInformation, preferredSize: size, placeholder: nil, success: successHandler, error: errorHandler) + imageLoader.loadImage(with: contentURL, from: host, preferredSize: size, placeholder: nil, success: successHandler, error: errorHandler) } func contentSize() -> CGSize { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 80a5f231c0a5..d7720d7abc78 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -874,7 +874,6 @@ 748437F11F1D4ECC00E8DDAF /* notifications-last-seen.json in Resources */ = {isa = PBXBuildFile; fileRef = 748BD8881F1923D500813F9A /* notifications-last-seen.json */; }; 7484D94D20320DFE006E94B4 /* WordPressShare.js in Resources */ = {isa = PBXBuildFile; fileRef = E1AFA8C21E8E34230004A323 /* WordPressShare.js */; }; 748BD8851F19234300813F9A /* notifications-mark-as-read.json in Resources */ = {isa = PBXBuildFile; fileRef = 748BD8841F19234300813F9A /* notifications-mark-as-read.json */; }; - 749197EE209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 749197ED209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift */; }; 7492F78E1F9BD94500B5A04A /* ShareMediaFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7430C4481F97F23600E2673E /* ShareMediaFileManager.swift */; }; 74989B8C2088E3650054290B /* BlogDetailsViewController+Activity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74989B8B2088E3650054290B /* BlogDetailsViewController+Activity.swift */; }; 74AC1DA1200D0CC300973CAD /* UINavigationController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AC1DA0200D0CC300973CAD /* UINavigationController+Extensions.swift */; }; @@ -991,7 +990,6 @@ 7EBB4126206C388100012D98 /* StockPhotosService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EBB4125206C388100012D98 /* StockPhotosService.swift */; }; 7EC9FE0B22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC9FE0A22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift */; }; 7ECD5B8120C4D823001AEBC5 /* MediaPreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECD5B8020C4D823001AEBC5 /* MediaPreviewHelper.swift */; }; - 7ED3695520A9F091007B0D56 /* Blog+ImageSourceInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED3695420A9F091007B0D56 /* Blog+ImageSourceInformation.swift */; }; 7EDAB3F420B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EDAB3F320B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift */; }; 7EF2EEA0210A67B60007A76B /* notifications-unapproved-comment.json in Resources */ = {isa = PBXBuildFile; fileRef = 7EF2EE9F210A67B60007A76B /* notifications-unapproved-comment.json */; }; 7EF9F65722F03C9200F79BBF /* SiteSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF9F65622F03C9200F79BBF /* SiteSettingsScreen.swift */; }; @@ -2029,6 +2027,7 @@ F15A230420A3EBE300625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; F15A230520A3ECC500625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; F16006322433E67200B32F34 /* MediaRequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */; }; + F160063424368C0800B32F34 /* MediaHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = F160063324368C0800B32F34 /* MediaHost.swift */; }; F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */; }; F16601C423E9E783007950AE /* SharingAuthorizationWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16601C323E9E783007950AE /* SharingAuthorizationWebViewController.swift */; }; F16C35D623F33DE400C81331 /* PageAutoUploadMessageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16C35D523F33DE400C81331 /* PageAutoUploadMessageProvider.swift */; }; @@ -3205,7 +3204,6 @@ 748BD8841F19234300813F9A /* notifications-mark-as-read.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-mark-as-read.json"; sourceTree = ""; }; 748BD8861F19238600813F9A /* notifications-load-all.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-load-all.json"; sourceTree = ""; }; 748BD8881F1923D500813F9A /* notifications-last-seen.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-last-seen.json"; sourceTree = ""; }; - 749197ED209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReaderCardContent+PostInformation.swift"; sourceTree = ""; }; 74989B8B2088E3650054290B /* BlogDetailsViewController+Activity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlogDetailsViewController+Activity.swift"; sourceTree = ""; }; 74AC1DA0200D0CC300973CAD /* UINavigationController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extensions.swift"; sourceTree = ""; }; 74AF4D6D1FE417D200E3EBFE /* MediaUploadOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadOperation.swift; sourceTree = ""; }; @@ -3350,7 +3348,6 @@ 7EBB4125206C388100012D98 /* StockPhotosService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StockPhotosService.swift; sourceTree = ""; }; 7EC9FE0A22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorAnalyticsSessionTests.swift; sourceTree = ""; }; 7ECD5B8020C4D823001AEBC5 /* MediaPreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewHelper.swift; sourceTree = ""; }; - 7ED3695420A9F091007B0D56 /* Blog+ImageSourceInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+ImageSourceInformation.swift"; sourceTree = ""; }; 7EDAB3F320B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircularProgressView+ActivityIndicatorType.swift"; sourceTree = ""; }; 7EF2EE9F210A67B60007A76B /* notifications-unapproved-comment.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "notifications-unapproved-comment.json"; sourceTree = ""; }; 7EF9F65622F03C9200F79BBF /* SiteSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsScreen.swift; sourceTree = ""; }; @@ -4621,6 +4618,7 @@ F14B5F75208E64F900439554 /* Version.internal.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.internal.xcconfig; sourceTree = ""; }; F14E844C2317252200D0C63E /* WordPress 90.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 90.xcdatamodel"; sourceTree = ""; }; F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaRequestAuthenticator.swift; sourceTree = ""; }; + F160063324368C0800B32F34 /* MediaHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaHost.swift; sourceTree = ""; }; F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Mbar.swift"; sourceTree = ""; }; F16601C323E9E783007950AE /* SharingAuthorizationWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingAuthorizationWebViewController.swift; sourceTree = ""; }; F16C35D523F33DE400C81331 /* PageAutoUploadMessageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PageAutoUploadMessageProvider.swift; path = ../Post/PageAutoUploadMessageProvider.swift; sourceTree = ""; }; @@ -5630,7 +5628,6 @@ 745A41AF2065405600299D75 /* ReaderPost+Searchable.swift */, E6C09B3D1BF0FDEB003074CB /* ReaderCrossPostMeta.swift */, 5DE471B71B4C710E00665C44 /* ReaderPostContentProvider.h */, - 749197ED209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift */, E6A3384A1BB08E3F00371587 /* ReaderGapMarker.h */, E6A3384B1BB08E3F00371587 /* ReaderGapMarker.m */, E61084B91B9B47BA008050C5 /* ReaderAbstractTopic.swift */, @@ -7296,9 +7293,10 @@ 850BD4531922F95C0032F3AD /* Networking */ = { isa = PBXGroup; children = ( + F160063324368C0800B32F34 /* MediaHost.swift */, + F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */, E11DA4921E03E03F00CF07A8 /* Pinghub.swift */, E1C2260623901AAD0021D03C /* WordPressOrgRestApi+WordPress.swift */, - F16006312433E67200B32F34 /* MediaRequestAuthenticator.swift */, ); path = Networking; sourceTree = ""; @@ -8696,7 +8694,6 @@ 8BFE36FC230F16580061EBA8 /* AbstractPost+fixLocalMediaURLs.swift */, E1AB5A061E0BF17500574B4E /* Array.swift */, 9A2CD5362146B8C700AE5055 /* Array+Page.swift */, - 7ED3695420A9F091007B0D56 /* Blog+ImageSourceInformation.swift */, FF619DD41C75246900903B65 /* CLPlacemark+Formatting.swift */, 177CBE4F1DA3A3AC009F951E /* CollectionType+Helpers.swift */, E14DFAFA1E07E7C400494688 /* Data.swift */, @@ -11721,7 +11718,6 @@ 1E9D544D23C4C56300F6A9E0 /* GutenbergRollout.swift in Sources */, 17E24F5420FCF1D900BD70A3 /* Routes+MySites.swift in Sources */, 0828D7FA1E6E09AE00C7C7D4 /* WPAppAnalytics+Media.swift in Sources */, - 7ED3695520A9F091007B0D56 /* Blog+ImageSourceInformation.swift in Sources */, 591AA5011CEF9BF20074934F /* Post.swift in Sources */, D82253EA219A8A720014D0E2 /* SiteInformationStep.swift in Sources */, C545E0A21811B9880020844C /* ContextManager.m in Sources */, @@ -12069,6 +12065,7 @@ E66969E21B9E67A000EC9C00 /* ReaderTopicToReaderSiteTopic37to38.swift in Sources */, B543D2B520570B5A00D3D4CC /* WordPressComSyncService.swift in Sources */, E14A52371E39F43E00EE203E /* AppRatingsUtility.swift in Sources */, + F160063424368C0800B32F34 /* MediaHost.swift in Sources */, 8350E49611D2C71E00A7B073 /* Media.m in Sources */, D8B9B58F204F4EA1003C6042 /* NetworkAware.swift in Sources */, B54346961C6A707D0010B3AD /* LanguageViewController.swift in Sources */, @@ -12281,7 +12278,6 @@ 7E7947AB210BAC5E005BB851 /* NotificationCommentRange.swift in Sources */, D816C1E920E0880400C4D82F /* NotificationAction.swift in Sources */, E19B17B01E5C69A5007517C6 /* NSManagedObject.swift in Sources */, - 749197EE209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift in Sources */, FF355D981FB492DD00244E6D /* ExportableAsset.swift in Sources */, E15644EF1CE0E53B00D96E64 /* PlanListViewModel.swift in Sources */, 983002A822FA05D600F03DBB /* AddInsightTableViewController.swift in Sources */, From 3405d2acb15222cb3c4537d7409b235fefb99de8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 2 Apr 2020 20:18:28 -0300 Subject: [PATCH 12/80] WIP: implementing support for Atomic Private Sites. --- .../AbstractPost+PostInformation.swift | 4 +- .../Extensions/UIImageView+SiteIcon.swift | 10 ++- .../Models/AbstractPost+Autosave.swift | 11 +++ .../Classes/Models/AbstractPost+Local.swift | 12 +++ .../Classes/Utility/Media/ImageLoader.swift | 76 ++++++++++--------- .../ViewRelated/Post/PostCardCell.swift | 8 +- .../Reader/ReaderCommentCell.swift | 7 +- WordPress/WordPress.xcodeproj/project.pbxproj | 8 ++ 8 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 WordPress/Classes/Models/AbstractPost+Autosave.swift create mode 100644 WordPress/Classes/Models/AbstractPost+Local.swift diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 5983f0d415b8..918832b57e6f 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -1,4 +1,4 @@ - +/* extension AbstractPost: ImageSourceInformation { var isAtomicOnWPCom: Bool { @@ -33,4 +33,4 @@ extension AbstractPost: ImageSourceInformation { var siteID: NSNumber? { return blog.dotComID } -} +}*/ diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index a9c1e6527add..9a1b2ba1b836 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -1,6 +1,6 @@ +import AutomatticTracks import Foundation - /// UIImageView Helper Methods that allow us to download a SiteIcon, given a website's "Icon Path" /// extension UIImageView { @@ -79,11 +79,15 @@ extension UIImageView { print("Blog id \(blog.dotComID ?? 0), is atomic \(blog.isAtomic())") } - let mediaRequestAuthenticator = MediaRequestAuthenticator() + let host = MediaHost(with: blog) { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + } + let mediaRequestAuthenticator = MediaRequestAuthenticator() mediaRequestAuthenticator.authenticatedRequest( for: siteIconURL, - blog: blog, + from: host, onComplete: { request in downloadImage(with: request) diff --git a/WordPress/Classes/Models/AbstractPost+Autosave.swift b/WordPress/Classes/Models/AbstractPost+Autosave.swift new file mode 100644 index 000000000000..8f2ca445eaa3 --- /dev/null +++ b/WordPress/Classes/Models/AbstractPost+Autosave.swift @@ -0,0 +1,11 @@ +import Foundation + +extension AbstractPost { + /// An autosave revision may include post title, content and/or excerpt. + var hasAutosaveRevision: Bool { + guard let autosaveRevisionIdentifier = autosaveIdentifier?.intValue else { + return false + } + return autosaveRevisionIdentifier > 0 + } +} diff --git a/WordPress/Classes/Models/AbstractPost+Local.swift b/WordPress/Classes/Models/AbstractPost+Local.swift new file mode 100644 index 000000000000..aa15883d3e4b --- /dev/null +++ b/WordPress/Classes/Models/AbstractPost+Local.swift @@ -0,0 +1,12 @@ +import Foundation + +extension AbstractPost { + /// Returns true if the post is a draft and has never been uploaded to the server. + var isLocalDraft: Bool { + return self.isDraft() && !self.hasRemote() + } + + var isLocalRevision: Bool { + return self.originalIsDraft() && self.isRevision() && self.remoteStatus == .local + } +} diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index ada10988d6db..a31117944a2e 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -99,7 +99,7 @@ import AutomatticTracks //@objc(loadImageWithURL:success:error:) /// Load an image from a specific URL. As no source is provided, we can assume - /// that this is from an external source. Supports animated images (gifs) as well. + /// that this is from a public site. Supports animated images (gifs) as well. /// /// - Parameters: /// - url: The URL to load the image from. @@ -111,10 +111,10 @@ import AutomatticTracks errorHandler = error if url.isGif { - loadGif(with: url, from: nil) + loadGif(with: url, from: .publicSite) } else { imageView.clean() - loadStaticImage(with: url, from: nil) + loadStaticImage(with: url, from: .publicSite) } } @@ -134,7 +134,7 @@ import AutomatticTracks successHandler = success errorHandler = error - loadImage(with: url, from: source, preferredSize: size) + loadImage(with: url, from: host, preferredSize: size) } // MARK: - Private helpers @@ -142,23 +142,17 @@ import AutomatticTracks /// Load an animated image from the given URL. /// private func loadGif(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { - func downloadGIFWithoutAuthenticating() { - let request = URLRequest(url: url) - self.downloadGif(from: request) - } - - guard !url.isFileURL, - let host = host else { - downloadGIFWithoutAuthenticating() - return - } - let mediaAuthenticator = MediaRequestAuthenticator() mediaAuthenticator.authenticatedRequest( for: url, - from: host) { request in + from: host, + onComplete: { request in self.downloadGif(from: request) - } + }, + onFailure: { error in + CrashLogging.logError(error) + self.callErrorHandler(with: error) + }) /* let request: URLRequest @@ -183,46 +177,51 @@ import AutomatticTracks /// Load a static image from the given URL. /// private func loadStaticImage(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { + let finalURL: URL - - + // WORKINGPOINT: if url.isFileURL { - downloadImage(from: url) + finalURL = url } else if let source = source { if source.isPrivateOnWPCom && url.isHostedAtWPCom() { - loadPrivateImage(with: url, from: source, preferredSize: size) + finalURL = privateImageURL(with: url, from: source, preferredSize: size) } else if source.isSelfHostedWithCredentials { - downloadImage(from: url) + finalURL = url } else { - loadPhotonUrl(with: url, preferredSize: size) + finalURL = photonUrl(with: url, preferredSize: size) } } else { - downloadImage(from: url) + finalURL = url + } + + let mediaRequestAuthenticator = MediaRequestAuthenticator() + + mediaRequestAuthenticator.authenticatedRequest(for: finalURL, from: host, onComplete: { request in + self.downloadImage(from: request) + }) { error in + CrashLogging.logError(error) + self.callErrorHandler(with: error) } } - /// Loads the image from a private post hosted in WPCom. + /// Constructs the URL for an image from a private post hosted in WPCom. /// - private func loadPrivateImage(with url: URL, from host: MediaHost, preferredSize size: CGSize) { + private func privateImageURL(with url: URL, from host: MediaHost, preferredSize size: CGSize) -> URL { let scale = UIScreen.main.scale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let scaledURL = WPImageURLHelper.imageURLWithSize(scaledSize, forImageURL: url) - let mediaAuthenticator = MediaRequestAuthenticator() - let request = mediaAuthenticator.authenticatedRequestForPrivateSite(for: scaledURL) - - downloadImage(from: request) + return scaledURL } - /// Loads the image from the Photon API with the given size. + /// Gets the photon URL with the specified size, or returns the passed `URL` /// - private func loadPhotonUrl(with url: URL, preferredSize size: CGSize) { + private func photonUrl(with url: URL, preferredSize size: CGSize) -> URL { guard let photonURL = getPhotonUrl(for: url, size: size) else { - downloadImage(from: url) - return + return url } - downloadImage(from: photonURL) + return photonURL } /// Download the animated image from the given URL Request. @@ -340,7 +339,12 @@ extension ImageLoader { } if url.isGif { - loadGif(with: url, from: media.blog, preferredSize: size) + let host = MediaHost(with: media.blog) { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + } + + loadGif(with: url, from: host, preferredSize: size) } else if imageView.image == nil { imageView.clean() loadImage(from: media, preferredSize: size) diff --git a/WordPress/Classes/ViewRelated/Post/PostCardCell.swift b/WordPress/Classes/ViewRelated/Post/PostCardCell.swift index 42c5ebd9ffa5..80840121ccd3 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardCell.swift @@ -1,3 +1,4 @@ +import AutomatticTracks import UIKit import Gridicons @@ -201,9 +202,14 @@ class PostCardCell: UITableViewCell, ConfigurablePostView { return } + let host = MediaHost(with: post) { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + } + if currentLoadedFeaturedImage != url.absoluteString { currentLoadedFeaturedImage = url.absoluteString - imageLoader.loadImage(with: url, from: post, preferredSize: preferredSize) + imageLoader.loadImage(with: url, from: host, preferredSize: preferredSize) } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderCommentCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderCommentCell.swift index 8087dda6d1e3..f6088d52da86 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderCommentCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderCommentCell.swift @@ -1,3 +1,4 @@ +import AutomatticTracks import UIKit import WordPressShared import Gridicons @@ -205,7 +206,11 @@ class ReaderCommentCell: UITableViewCell { return } - textView.isPrivate = comment.isPrivateContent() + textView.mediaHost = MediaHost(with: comment.blog, failure: { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) + }) + // Use `content` vs `contentForDisplay`. Hierarchcial comments are already // correctly formatted during the sync process. textView.attributedText = attributedString diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index d7720d7abc78..e18d4790e506 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2036,6 +2036,8 @@ F17A2A1E23BFBD72001E96AC /* UIView+ExistingConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17A2A1D23BFBD72001E96AC /* UIView+ExistingConstraints.swift */; }; F17A2A2023BFBD84001E96AC /* UIView+ExistingConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17A2A1F23BFBD84001E96AC /* UIView+ExistingConstraints.swift */; }; F18B43781F849F580089B817 /* PostAttachmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18B43771F849F580089B817 /* PostAttachmentTests.swift */; }; + F18D27032436A8D700D7A13E /* AbstractPost+Autosave.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18D27022436A8D700D7A13E /* AbstractPost+Autosave.swift */; }; + F18D27052436A96E00D7A13E /* AbstractPost+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18D27042436A96E00D7A13E /* AbstractPost+Local.swift */; }; F1ADCAF7241FEF0C00F150D2 /* AtomicAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1ADCAF6241FEF0C00F150D2 /* AtomicAuthenticationService.swift */; }; F1B1E7A324098FA100549E2A /* BlogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B1E7A224098FA100549E2A /* BlogTests.swift */; }; F1D690161F82913F00200E30 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D690151F828FF000200E30 /* FeatureFlag.swift */; }; @@ -4627,6 +4629,8 @@ F17A2A1D23BFBD72001E96AC /* UIView+ExistingConstraints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+ExistingConstraints.swift"; sourceTree = ""; }; F17A2A1F23BFBD84001E96AC /* UIView+ExistingConstraints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+ExistingConstraints.swift"; sourceTree = ""; }; F18B43771F849F580089B817 /* PostAttachmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PostAttachmentTests.swift; path = Posts/PostAttachmentTests.swift; sourceTree = ""; }; + F18D27022436A8D700D7A13E /* AbstractPost+Autosave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+Autosave.swift"; sourceTree = ""; }; + F18D27042436A96E00D7A13E /* AbstractPost+Local.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+Local.swift"; sourceTree = ""; }; F1ADCAF6241FEF0C00F150D2 /* AtomicAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicAuthenticationService.swift; sourceTree = ""; }; F1B1E7A224098FA100549E2A /* BlogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogTests.swift; sourceTree = ""; }; F1D690141F828FF000200E30 /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; @@ -5582,8 +5586,10 @@ B5176CBF1CDCE14F0083CF2D /* People Management */, 5D42A3D6175E7452005CFF05 /* AbstractPost.h */, 5D42A3D7175E7452005CFF05 /* AbstractPost.m */, + F18D27022436A8D700D7A13E /* AbstractPost+Autosave.swift */, 40232A9C230A6A740036B0B6 /* AbstractPost+HashHelpers.h */, 40232A9D230A6A740036B0B6 /* AbstractPost+HashHelpers.m */, + F18D27042436A96E00D7A13E /* AbstractPost+Local.swift */, E19B17B11E5C8F36007517C6 /* AbstractPost.swift */, 74729CAD205722E300D1394D /* AbstractPost+Searchable.swift */, 5D42A3D8175E7452005CFF05 /* BasePost.h */, @@ -12480,6 +12486,7 @@ FF1B11E5238FDFE70038B93E /* GutenbergGalleryUploadProcessor.swift in Sources */, 31EC15081A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m in Sources */, 9865257D2194D77F0078B916 /* SiteStatsInsightsViewModel.swift in Sources */, + F18D27052436A96E00D7A13E /* AbstractPost+Local.swift in Sources */, 9F74696B209EFD0C0074D52B /* CheckmarkTableViewCell.swift in Sources */, FF5371631FDFF64F00619A3F /* MediaService.swift in Sources */, 40C403F52215D66A00E8C894 /* TopViewedPostStatsRecordValue+CoreDataClass.swift in Sources */, @@ -12750,6 +12757,7 @@ E6431DE71C4E892900FD8D90 /* SharingViewController.m in Sources */, 7E4123C820F417EF00DF8486 /* FormattableActivity.swift in Sources */, 9A162F2B21C2A21A00FDC035 /* RevisionPreviewTextViewManager.swift in Sources */, + F18D27032436A8D700D7A13E /* AbstractPost+Autosave.swift in Sources */, 73D86969223AF4040064920F /* StatsChartLegendView.swift in Sources */, FF286C761DE70A4500383A62 /* NSURL+Exporters.swift in Sources */, 988AC37522F10DD900BC1433 /* FileDownloadsStatsRecordValue+CoreDataProperties.swift in Sources */, From 93290b63848cc4546caf9db4460ac2a7fe0ae8ff Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 12:40:08 -0300 Subject: [PATCH 13/80] Adds a case for MediaHost. Improving Atomic Private support. --- WordPress/Classes/Networking/MediaHost.swift | 7 ++++++- .../MediaRequestAuthenticator.swift | 1 + .../Classes/Utility/Media/ImageLoader.swift | 20 ++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/Networking/MediaHost.swift b/WordPress/Classes/Networking/MediaHost.swift index 5c02bdb0af8d..1f39c48d2052 100644 --- a/WordPress/Classes/Networking/MediaHost.swift +++ b/WordPress/Classes/Networking/MediaHost.swift @@ -12,6 +12,7 @@ import Foundation /// enum MediaHost { case publicSite + case publicWPComSite case privateSelfHostedSite case privateWPComSite case privateAtomicWPComSite(siteID: Int) @@ -28,7 +29,11 @@ enum MediaHost { failure: (Error) -> ()) { guard isPrivate else { - self = .publicSite + if isAccessibleThroughWPCom { + self = .publicWPComSite + } else { + self = .publicSite + } return } diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index fd4ed3b4032a..0d20c1a66574 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -60,6 +60,7 @@ class MediaRequestAuthenticator { switch host { case .publicSite: fallthrough + case .publicWPComSite: fallthrough case .privateSelfHostedSite: // The authentication for these is handled elsewhere let request = URLRequest(url: url) diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index a31117944a2e..730756907d3b 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -179,19 +179,15 @@ import AutomatticTracks private func loadStaticImage(with url: URL, from host: MediaHost, preferredSize size: CGSize = .zero) { let finalURL: URL - // WORKINGPOINT: - if url.isFileURL { - finalURL = url - } else if let source = source { - if source.isPrivateOnWPCom && url.isHostedAtWPCom() { - finalURL = privateImageURL(with: url, from: source, preferredSize: size) - } else if source.isSelfHostedWithCredentials { - finalURL = url - } else { - finalURL = photonUrl(with: url, preferredSize: size) - } - } else { + switch host { + case .publicSite: fallthrough + case .privateSelfHostedSite: finalURL = url + case .publicWPComSite: fallthrough + case .privateAtomicWPComSite(siteID: _): + finalURL = photonUrl(with: url, preferredSize: size) + case .privateWPComSite: + finalURL = privateImageURL(with: url, from: host, preferredSize: size) } let mediaRequestAuthenticator = MediaRequestAuthenticator() From 86c4acf90b8ea898b09c0bd3836166cab341f5d3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 16:53:10 -0300 Subject: [PATCH 14/80] Adds some code to make the App build again. --- WordPress/Classes/Utility/Media/ImageLoader.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 730756907d3b..d66d1c7348b4 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -118,7 +118,16 @@ import AutomatticTracks } } - //@objc(loadImageWithURL:fromPost:preferredSize:placeholder:success:error:) + @objc(loadImageWithURL:fromPost:preferredSize:placeholder:success:error:) + func loadImage(with url: URL, from post: AbstractPost, preferredSize size: CGSize = .zero, placeholder: UIImage?, success: ImageLoaderSuccessBlock?, error: ImageLoaderFailureBlock?) { + + let host = MediaHost(with: post, failure: { error in + CrashLogging.logError(error) + }) + + loadImage(with: url, from: host, preferredSize: size, placeholder: placeholder, success: success, error: error) + } + /// Load an image from a specific post, using the given URL. Supports animated images (gifs) as well. /// /// - Parameters: From 9d53da04cfa0d702f680d855283a0e2c0b9ab372 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 16:56:06 -0300 Subject: [PATCH 15/80] Removes some old code. --- .../AbstractPost+PostInformation.swift | 36 ------------------- WordPress/WordPress.xcodeproj/project.pbxproj | 4 --- 2 files changed, 40 deletions(-) delete mode 100644 WordPress/Classes/Extensions/AbstractPost+PostInformation.swift diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift deleted file mode 100644 index 918832b57e6f..000000000000 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ /dev/null @@ -1,36 +0,0 @@ -/* -extension AbstractPost: ImageSourceInformation { - - var isAtomicOnWPCom: Bool { - return blog.isAtomic() - } - - var isPrivateOnWPCom: Bool { - return isPrivate() && blog.isHostedAtWPcom - } - - var isSelfHostedWithCredentials: Bool { - return blog.isSelfHostedWithCredentials - } - - var isLocalRevision: Bool { - return self.originalIsDraft() && self.isRevision() && self.remoteStatus == .local - } - - /// Returns true if the post is a draft and has never been uploaded to the server. - var isLocalDraft: Bool { - return self.isDraft() && !self.hasRemote() - } - - /// An autosave revision may include post title, content and/or excerpt. - var hasAutosaveRevision: Bool { - guard let autosaveRevisionIdentifier = autosaveIdentifier?.intValue else { - return false - } - return autosaveRevisionIdentifier > 0 - } - - var siteID: NSNumber? { - return blog.dotComID - } -}*/ diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e18d4790e506..ddecc5e54acb 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -964,7 +964,6 @@ 7E58879A20FE8D9300DB6F80 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58879920FE8D9300DB6F80 /* Environment.swift */; }; 7E5887A020FE956100DB6F80 /* AppRatingUtilityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58879F20FE956100DB6F80 /* AppRatingUtilityType.swift */; }; 7E729C28209A087300F76599 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E729C27209A087200F76599 /* ImageLoader.swift */; }; - 7E729C2A209A241100F76599 /* AbstractPost+PostInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E729C29209A241100F76599 /* AbstractPost+PostInformation.swift */; }; 7E7947A9210BAC1D005BB851 /* NotificationContentRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947A8210BAC1D005BB851 /* NotificationContentRange.swift */; }; 7E7947AB210BAC5E005BB851 /* NotificationCommentRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947AA210BAC5E005BB851 /* NotificationCommentRange.swift */; }; 7E7947AD210BAC7B005BB851 /* FormattableNoticonRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947AC210BAC7B005BB851 /* FormattableNoticonRange.swift */; }; @@ -3323,7 +3322,6 @@ 7E58879920FE8D9300DB6F80 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; 7E58879F20FE956100DB6F80 /* AppRatingUtilityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRatingUtilityType.swift; sourceTree = ""; }; 7E729C27209A087200F76599 /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; - 7E729C29209A241100F76599 /* AbstractPost+PostInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+PostInformation.swift"; sourceTree = ""; }; 7E7947A8210BAC1D005BB851 /* NotificationContentRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentRange.swift; sourceTree = ""; }; 7E7947AA210BAC5E005BB851 /* NotificationCommentRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCommentRange.swift; sourceTree = ""; }; 7E7947AC210BAC7B005BB851 /* FormattableNoticonRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattableNoticonRange.swift; sourceTree = ""; }; @@ -8695,7 +8693,6 @@ 4326191322FCB8BE003C7642 /* Colors and Styles */, 086C4D0C1E81F7920011D960 /* Media */, B587798319B799EB00E57C5A /* Notifications */, - 7E729C29209A241100F76599 /* AbstractPost+PostInformation.swift */, F580C3CA23D8F9B40038E243 /* AbstractPost+Dates.swift */, 8BFE36FC230F16580061EBA8 /* AbstractPost+fixLocalMediaURLs.swift */, E1AB5A061E0BF17500574B4E /* Array.swift */, @@ -12859,7 +12856,6 @@ 3F09CCAE24292EFD00D00A8C /* ReaderSection.swift in Sources */, 5D839AAB187F0D8000811F4A /* PostGeolocationCell.m in Sources */, 4054F4422213635000D261AB /* TopCommentsAuthorStatsRecordValue+CoreDataClass.swift in Sources */, - 7E729C2A209A241100F76599 /* AbstractPost+PostInformation.swift in Sources */, 8236EB502024ED8C007C7CF9 /* NotificationsViewController+JetpackPrompt.swift in Sources */, 5D732F971AE84E3C00CD89E7 /* PostListFooterView.m in Sources */, 1767494E1D3633A000B8D1D1 /* ThemeBrowserSearchHeaderView.swift in Sources */, From 8cb6a0cdefe14a32dc485b08d43cf6317a50f468 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:02:31 -0300 Subject: [PATCH 16/80] Adds error logging to a failure closure. --- .../Extensions/UIImageView+SiteIcon.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 9a1b2ba1b836..e087037ce937 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -62,12 +62,18 @@ extension UIImageView { @objc func downloadSiteIcon(for blog: Blog, placeholderImage: UIImage? = .siteIconPlaceholder) { func downloadImage(with request: URLRequest) { - self.downloadImage(usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in - self?.image = image - self?.removePlaceholderBorder() - }) { error in - // No-op (for now!) - } + self.downloadImage( + usingRequest: request, + placeholderImage: placeholderImage, + success: { [weak self] (image) in + self?.image = image + self?.removePlaceholderBorder() + }, + failure: { error -> () in + if let error = error { + CrashLogging.logError(error) + } + }) } guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { From d4adfc67ce9dd0316fa3200d84673432ce9f2cdb Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:13:35 -0300 Subject: [PATCH 17/80] Adds some error logging. Fixes unit tests. --- WordPress/Classes/Extensions/UIImageView+SiteIcon.swift | 7 +------ WordPress/WordPressTest/ReaderPostCardCellTests.swift | 8 ++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index e087037ce937..38e1ddd9328e 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -81,10 +81,6 @@ extension UIImageView { return } - if blog.dotComID == 172825667 { - print("Blog id \(blog.dotComID ?? 0), is atomic \(blog.isAtomic())") - } - let host = MediaHost(with: blog) { error in // We'll log the error, so we know it's there, but we won't halt execution. CrashLogging.logError(error) @@ -98,8 +94,7 @@ extension UIImageView { downloadImage(with: request) }) { error in - - // No-op for now + CrashLogging.logError(error) } } } diff --git a/WordPress/WordPressTest/ReaderPostCardCellTests.swift b/WordPress/WordPressTest/ReaderPostCardCellTests.swift index 63c83e36d29f..481a5b2297d7 100644 --- a/WordPress/WordPressTest/ReaderPostCardCellTests.swift +++ b/WordPress/WordPressTest/ReaderPostCardCellTests.swift @@ -2,6 +2,10 @@ import XCTest class MockContentProvider: NSObject, ReaderPostContentProvider { + func siteID() -> NSNumber! { + return NSNumber(value: 15567) + } + func titleForDisplay() -> String! { return "A title" } @@ -90,6 +94,10 @@ class MockContentProvider: NSObject, ReaderPostContentProvider { func isLikesEnabled() -> Bool { return true } + + func isAtomic() -> Bool { + return false + } func isPrivate() -> Bool { return false From 96d0b64946e593128026309f18d8439e4735d641 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:14:00 -0300 Subject: [PATCH 18/80] rake lint:autocorrect --- WordPress/Classes/Utility/Media/ImageLoader.swift | 6 +++--- WordPress/WordPressTest/ReaderPostCardCellTests.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index d66d1c7348b4..d2dc1556ef99 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -120,14 +120,14 @@ import AutomatticTracks @objc(loadImageWithURL:fromPost:preferredSize:placeholder:success:error:) func loadImage(with url: URL, from post: AbstractPost, preferredSize size: CGSize = .zero, placeholder: UIImage?, success: ImageLoaderSuccessBlock?, error: ImageLoaderFailureBlock?) { - + let host = MediaHost(with: post, failure: { error in CrashLogging.logError(error) }) - + loadImage(with: url, from: host, preferredSize: size, placeholder: placeholder, success: success, error: error) } - + /// Load an image from a specific post, using the given URL. Supports animated images (gifs) as well. /// /// - Parameters: diff --git a/WordPress/WordPressTest/ReaderPostCardCellTests.swift b/WordPress/WordPressTest/ReaderPostCardCellTests.swift index 481a5b2297d7..25fee12fafd5 100644 --- a/WordPress/WordPressTest/ReaderPostCardCellTests.swift +++ b/WordPress/WordPressTest/ReaderPostCardCellTests.swift @@ -5,7 +5,7 @@ class MockContentProvider: NSObject, ReaderPostContentProvider { func siteID() -> NSNumber! { return NSNumber(value: 15567) } - + func titleForDisplay() -> String! { return "A title" } @@ -94,7 +94,7 @@ class MockContentProvider: NSObject, ReaderPostContentProvider { func isLikesEnabled() -> Bool { return true } - + func isAtomic() -> Bool { return false } From b40446da7198afa4ec2d4f59d85b6ea79feec5b3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:16:33 -0300 Subject: [PATCH 19/80] Removed some lines of code that were committed by mistake. --- WordPress/Classes/Services/AtomicAuthenticationService.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/Classes/Services/AtomicAuthenticationService.swift b/WordPress/Classes/Services/AtomicAuthenticationService.swift index 7936421976b8..0a6747dc69bd 100644 --- a/WordPress/Classes/Services/AtomicAuthenticationService.swift +++ b/WordPress/Classes/Services/AtomicAuthenticationService.swift @@ -20,7 +20,6 @@ class AtomicAuthenticationService { func getAuthCookie( siteID: Int, - using session: URLSession = URLSession.shared, success: @escaping (_ cookie: HTTPCookie) -> Void, failure: @escaping (Error) -> Void) { @@ -31,7 +30,6 @@ class AtomicAuthenticationService { into cookieJar: CookieJar, username: String, siteID: Int, - using session: URLSession = URLSession.shared, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { From f1195049a1f43fd1d8c49f23f0c498e776615c8c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:19:14 -0300 Subject: [PATCH 20/80] Removed some commented-out code. --- .../Classes/Utility/Media/ImageLoader.swift | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index d2dc1556ef99..b2001520790e 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -1,30 +1,5 @@ import MobileCoreServices import AutomatticTracks -/* -/// Protocol used to abstract the information needed to load post related images. -/// -@objc protocol ImageSourceInformation { - - @objc - var isAtomic: Bool { get } - - /// The post is private and hosted on WPcom. - /// Redundant name due to naming conflict. - /// - @objc - var isPrivate: Bool { get } - - /// Whether the post is accessible through WPCom. - /// - @objc - var isAccessibleThroughWPCom: Bool { get } - - /// The blog is self-hosted and there is already a basic auth credential stored. - /// - var isSelfHostedWithCredentials: Bool { get } - - var siteID: NSNumber? { get } -}*/ /// Class used together with `CachedAnimatedImageView` to facilitate the loading of both /// still images and animated gifs. @@ -80,7 +55,6 @@ import AutomatticTracks imageView.prepForReuse() } - //@objc(loadImageWithURL:fromPost:andPreferredSize:) /// Load an image from a specific post, using the given URL. Supports animated images (gifs) as well. /// /// - Parameters: @@ -97,7 +71,6 @@ import AutomatticTracks } } - //@objc(loadImageWithURL:success:error:) /// Load an image from a specific URL. As no source is provided, we can assume /// that this is from a public site. Supports animated images (gifs) as well. /// @@ -162,25 +135,6 @@ import AutomatticTracks CrashLogging.logError(error) self.callErrorHandler(with: error) }) - - /* - let request: URLRequest - if url.isFileURL { - request = URLRequest(url: url) - } else if let source = source, - source.isAccessibleThroughWPCom && source.isPrivate { - - let mediaAuthenticator = MediaRequestAuthenticator() - request = mediaAuthenticator.authenticatedRequestForPrivateSite(for: url) - } else { - if let photonUrl = getPhotonUrl(for: url, size: size), - source != nil { - request = URLRequest(url: photonUrl) - } else { - request = URLRequest(url: url) - } - } - downloadGif(from: request)*/ } /// Load a static image from the given URL. From 605a60d0c5ced231c294e82c1916d52c42d637b3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:20:31 -0300 Subject: [PATCH 21/80] Removed some commented-out code. --- .../Gutenberg/EditorMediaUtility.swift | 46 +------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 19c8220b2310..3f15821da1c8 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -131,7 +131,7 @@ class EditorMediaUtility { size requestSize: CGSize, scale: CGFloat, post: AbstractPost, success: @escaping (UIImage) -> Void, - onFailure failure: @escaping (Error) -> Void) -> CancellableTask { //}-> ImageDownloader.Task { + onFailure failure: @escaping (Error) -> Void) -> CancellableTask { let imageMaxDimension = max(requestSize.width, requestSize.height) //use height zero to maintain the aspect ratio when fetching @@ -161,50 +161,6 @@ class EditorMediaUtility { imageDownload.start() return imageDownload - - - /* - let operationQueue = OperationQueue() - - operationQueue.addOperation(<#T##op: Operation##Operation#>) - - // NUEVO CODIGO - - let mediaRequestAuthenticator = MediaRequestAuthenticator() - - mediaRequestAuthenticator.authenticatedRequest( - for: requestURL, - blog: post.blog, - onComplete: { request in - // aca deberia llamarse el request... - }, - onFailure: { error in - failure(error) - }) - - // VIEJO CODIGO - let task = ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in - guard let _ = self else { - return - } - - DispatchQueue.main.async { - guard let image = image else { - DDLogError("Unable to download image for attachment with url = \(url). Details: \(String(describing: error?.localizedDescription))") - if let error = error { - failure(error) - } else { - failure(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)) - } - return - } - - success(image) - } - } - - return task - */ } static func fetchRemoteVideoURL(for media: Media, in post: AbstractPost, completion: @escaping ( Result<(videoURL: URL, posterURL: URL?), Error> ) -> Void) { From 4ae2d7380d859eb3500ee3c318c6ed373f9dace3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:22:40 -0300 Subject: [PATCH 22/80] Removing some commented-out code. --- WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h index 35cc0999bff7..83d18e55c7ff 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h @@ -20,8 +20,4 @@ + (void)registerPrivateSiteURLProtocol; + (void)unregisterPrivateSiteURLProtocol; -//+ (nonnull NSURLRequest *)requestForPrivateSiteFromURL:(nonnull NSURL *)url; - -//+ (BOOL)urlGoesToWPComSite:(nonnull NSURL *)url; - @end From f26b98c68cf4910dff328b73c77877062ed249eb Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:23:23 -0300 Subject: [PATCH 23/80] Removing some commented-out code. --- .../ViewRelated/Post/PrivateSiteURLProtocol.m | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m index 44585fff9032..ad8cd857c1b7 100644 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m +++ b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m @@ -109,21 +109,6 @@ + (BOOL)urlGoesToWPComSite:(NSURL *)url return NO; } -/* -+ (NSURLRequest *)requestForPrivateSiteFromURL:(NSURL *)url -{ - if (![self urlGoesToWPComSite:url]) { - return [NSURLRequest requestWithURL:url]; - } - NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; - //make sure the scheme used is https - [urlComponents setScheme:@"https"]; - NSURL *httpsURL = [urlComponents URL]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:httpsURL]; - NSString *bearerToken = [NSString stringWithFormat:@"Bearer %@", [self bearerToken]]; - [request addValue:bearerToken forHTTPHeaderField:@"Authorization"]; - return request; -}*/ - (void)startLoading { From c72c2d37f2c15c0969a4e7941d66f0621c2ec6b5 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 3 Apr 2020 17:24:57 -0300 Subject: [PATCH 24/80] Removed a file that was no longer necessary. --- .../Post/PrivateSiteURLProtocol.swift | 137 ------------------ WordPress/WordPress.xcodeproj/project.pbxproj | 4 - 2 files changed, 141 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift deleted file mode 100644 index 9ecde59458c8..000000000000 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.swift +++ /dev/null @@ -1,137 +0,0 @@ -import Foundation -import AutomatticTracks - -extension PrivateSiteURLProtocol { -/* - enum Error: Swift.Error { - case cannotBreakDownURLIntoComponents(url: URL) - case cannotFindWPContentInPhotonPath(components: URLComponents) - case cannotCreateAtomicURL(components: URLComponents) - case cannotCreateAtomicProxyURL(components: URLComponents) - case cannotFindAuthenticationToken(url: URL) - } - - static let photonHost = "i0.wp.com" - - private static func isPhoton(url: URL) -> Bool { - return url.host == photonHost - } - - static func request( - for url: URL, - siteID: Int, - isAtomic: Bool, - onComplete provide: @escaping (URLRequest) -> ()) { - - if urlGoes(toWPComSite: url) { - if isAtomic { - requestForPrivateAtomicSite(for: url, siteID: siteID, onComplete: provide) - } else { - let request = requestForPrivateSite(from: url) - provide(request) - } - } else if isAtomic && isPhoton(url: url) { - requestForPrivateAtomicSiteThroughPhoton(for: url, siteID: siteID, onComplete: provide) - } else { - let request = URLRequest(url: url) - provide(request) - } - }*/ -/* - /// Photon URLs are currently not working for private atomic sites, so this is a workaround - /// to replace those URLs with working URLs. - /// - /// By recommendation of @zieladam we'll be using the Atomic Proxy endpoint for these until - /// Photon starts working with Atomic Private Sites: - /// - /// https://public-api.wordpress.com/wpcom/v2/sites/$siteID/atomic-auth-proxy/file/$wpContentPath - /// - /// To know whether you can remove this method, try requesting the photon URL from an - /// atomic private site. If it works then you can remove this workaround logic. - /// - static func requestForPrivateAtomicSiteThroughPhoton( - for url: URL, - siteID: Int, - onComplete provide: @escaping (URLRequest) -> ()) { - - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - CrashLogging.logError(Error.cannotBreakDownURLIntoComponents(url: url)) - provide(URLRequest(url: url)) - return - } - - guard let wpContentRange = components.path.range(of: "/wp-content") else { - CrashLogging.logError(Error.cannotFindWPContentInPhotonPath(components: components)) - provide(URLRequest(url: url)) - return - } - - let contentPath = components.path[wpContentRange.lowerBound ..< components.path.endIndex] - - components.scheme = "https" - components.host = "public-api.wordpress.com" - components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file\(contentPath)" - - guard let finalURL = components.url else { - CrashLogging.logError(Error.cannotCreateAtomicProxyURL(components: components)) - provide(URLRequest(url: url)) - return - } - - guard let request = tokenAuthenticatedWPComRequest(for: finalURL) else { - CrashLogging.logError(Error.cannotFindAuthenticationToken(url: finalURL)) - provide(URLRequest(url: url)) - return - } - - provide(request) - } - - static func requestForPrivateAtomicSite( - for url: URL, - siteID: Int, - onComplete provide: @escaping (URLRequest) -> ()) { - - guard !PrivateSiteURLProtocol.urlGoes(toWPComSite: url), - var components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), - let authToken = account.authToken else { - return provide(URLRequest(url: url)) - } - - let authenticationService = AtomicAuthenticationService(account: account) - let cookieJar = HTTPCookieStorage.shared - - // Just in case, enforce HTTPs - components.scheme = "https" - - guard let finalURL = components.url else { - CrashLogging.logError(Error.cannotCreateAtomicURL(components: components)) - provide(URLRequest(url: url)) - return - } - - let request = tokenAuthenticatedWPComRequest(for: finalURL, authToken: authToken) - - authenticationService.loadAuthCookies(into: cookieJar, username: account.username, siteID: siteID, success: { - return provide(request) - }) { error in - return provide(request) - } - } - - private static func tokenAuthenticatedWPComRequest(for url: URL) -> URLRequest? { - guard let account = AccountService(managedObjectContext: ContextManager.sharedInstance().mainContext).defaultWordPressComAccount(), - let authToken = account.authToken else { - return nil - } - - return tokenAuthenticatedWPComRequest(for: url, authToken: authToken) - } - - private static func tokenAuthenticatedWPComRequest(for url: URL, authToken: String) -> URLRequest { - var request = URLRequest(url: url) - request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") - return request - }*/ -} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ddecc5e54acb..bff44f1975f9 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2044,7 +2044,6 @@ F1DB8D292288C14400906E2F /* Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DB8D282288C14400906E2F /* Uploader.swift */; }; F1DB8D2B2288C24500906E2F /* UploadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DB8D2A2288C24500906E2F /* UploadsManager.swift */; }; F1E746E7242E459400C74D8A /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E746E6242E459400C74D8A /* RequestAuthenticator.swift */; }; - F1E746E924322BF800C74D8A /* PrivateSiteURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */; }; F1F083F6241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */; }; F511F8A42356A4F400895E73 /* PublishSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F511F8A32356A4F400895E73 /* PublishSettingsViewController.swift */; }; F53FF3A123E2377E001AD596 /* NewBlogDetailHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A023E2377E001AD596 /* NewBlogDetailHeaderView.swift */; }; @@ -4636,7 +4635,6 @@ F1DB8D282288C14400906E2F /* Uploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uploader.swift; sourceTree = ""; }; F1DB8D2A2288C24500906E2F /* UploadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadsManager.swift; sourceTree = ""; }; F1E746E6242E459400C74D8A /* RequestAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = ""; }; - F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateSiteURLProtocol.swift; sourceTree = ""; }; F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicAuthenticationServiceTests.swift; sourceTree = ""; }; F262DFCA1F94418CE76D1D78 /* Pods-WordPressNotificationContentExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationContentExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationContentExtension/Pods-WordPressNotificationContentExtension.release-internal.xcconfig"; sourceTree = ""; }; F373612EEEEF10E500093FF3 /* Pods-WordPress.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPress.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPress/Pods-WordPress.release-alpha.xcconfig"; sourceTree = ""; }; @@ -8356,7 +8354,6 @@ E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */, 5D17F0BC1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.h */, 5D17F0BD1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m */, - F1E746E824322BF800C74D8A /* PrivateSiteURLProtocol.swift */, 5903AE1C19B60AB9009D5354 /* WPButtonForNavigationBar.h */, 5903AE1A19B60A98009D5354 /* WPButtonForNavigationBar.m */, FF0AAE0B1A16550D0089841D /* WPMediaProgressTableViewController.h */, @@ -12025,7 +12022,6 @@ F16C35DA23F3F76C00C81331 /* PostAutoUploadMessageProvider.swift in Sources */, 91D8364121946EFB008340B2 /* GutenbergMediaPickerHelper.swift in Sources */, F1D690161F82913F00200E30 /* FeatureFlag.swift in Sources */, - F1E746E924322BF800C74D8A /* PrivateSiteURLProtocol.swift in Sources */, 4313437B217956DB00DA2176 /* QuickStartSkipAllCell.swift in Sources */, 74989B8C2088E3650054290B /* BlogDetailsViewController+Activity.swift in Sources */, B532D4EB199D4357006E4DF6 /* NoteBlockTableViewCell.swift in Sources */, From a32f7c5cd39a8b3abbb941d76f2b35db8a283563 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 06:09:01 -0300 Subject: [PATCH 25/80] Fixed the project file. --- WordPress/WordPress.xcodeproj/project.pbxproj | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 0dc1ac97765b..db313c44ef16 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -874,7 +874,6 @@ 748437F11F1D4ECC00E8DDAF /* notifications-last-seen.json in Resources */ = {isa = PBXBuildFile; fileRef = 748BD8881F1923D500813F9A /* notifications-last-seen.json */; }; 7484D94D20320DFE006E94B4 /* WordPressShare.js in Resources */ = {isa = PBXBuildFile; fileRef = E1AFA8C21E8E34230004A323 /* WordPressShare.js */; }; 748BD8851F19234300813F9A /* notifications-mark-as-read.json in Resources */ = {isa = PBXBuildFile; fileRef = 748BD8841F19234300813F9A /* notifications-mark-as-read.json */; }; - 749197EE209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 749197ED209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift */; }; 7492F78E1F9BD94500B5A04A /* ShareMediaFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7430C4481F97F23600E2673E /* ShareMediaFileManager.swift */; }; 74989B8C2088E3650054290B /* BlogDetailsViewController+Activity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74989B8B2088E3650054290B /* BlogDetailsViewController+Activity.swift */; }; 74AC1DA1200D0CC300973CAD /* UINavigationController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AC1DA0200D0CC300973CAD /* UINavigationController+Extensions.swift */; }; @@ -965,7 +964,6 @@ 7E58879A20FE8D9300DB6F80 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58879920FE8D9300DB6F80 /* Environment.swift */; }; 7E5887A020FE956100DB6F80 /* AppRatingUtilityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58879F20FE956100DB6F80 /* AppRatingUtilityType.swift */; }; 7E729C28209A087300F76599 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E729C27209A087200F76599 /* ImageLoader.swift */; }; - 7E729C2A209A241100F76599 /* AbstractPost+PostInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E729C29209A241100F76599 /* AbstractPost+PostInformation.swift */; }; 7E7947A9210BAC1D005BB851 /* NotificationContentRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947A8210BAC1D005BB851 /* NotificationContentRange.swift */; }; 7E7947AB210BAC5E005BB851 /* NotificationCommentRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947AA210BAC5E005BB851 /* NotificationCommentRange.swift */; }; 7E7947AD210BAC7B005BB851 /* FormattableNoticonRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947AC210BAC7B005BB851 /* FormattableNoticonRange.swift */; }; @@ -991,7 +989,6 @@ 7EBB4126206C388100012D98 /* StockPhotosService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EBB4125206C388100012D98 /* StockPhotosService.swift */; }; 7EC9FE0B22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC9FE0A22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift */; }; 7ECD5B8120C4D823001AEBC5 /* MediaPreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECD5B8020C4D823001AEBC5 /* MediaPreviewHelper.swift */; }; - 7ED3695520A9F091007B0D56 /* Blog+ImageSourceInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED3695420A9F091007B0D56 /* Blog+ImageSourceInformation.swift */; }; 7EDAB3F420B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EDAB3F320B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift */; }; 7EF2EEA0210A67B60007A76B /* notifications-unapproved-comment.json in Resources */ = {isa = PBXBuildFile; fileRef = 7EF2EE9F210A67B60007A76B /* notifications-unapproved-comment.json */; }; 7EF9F65722F03C9200F79BBF /* SiteSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF9F65622F03C9200F79BBF /* SiteSettingsScreen.swift */; }; @@ -1884,7 +1881,6 @@ E1B9128B1BB0129C003C25B9 /* WPStyleGuide+People.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B9128A1BB0129C003C25B9 /* WPStyleGuide+People.swift */; }; E1B9128F1BB05B1D003C25B9 /* PeopleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B912841BB01266003C25B9 /* PeopleCell.swift */; }; E1B921BC1C0ED5A3003EA3CB /* MediaSizeSliderCellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B921BB1C0ED5A3003EA3CB /* MediaSizeSliderCellTest.swift */; }; - E1BB85981F82459800797050 /* WebViewAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BB85971F82459800797050 /* WebViewAuthenticator.swift */; }; E1BB92321FDAAFFA00F2D817 /* TextWithAccessoryButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BB92311FDAAFFA00F2D817 /* TextWithAccessoryButtonCell.swift */; }; E1BEEC631C4E35A8000B4FA0 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BEEC621C4E35A8000B4FA0 /* Animator.swift */; }; E1BEEC651C4E3978000B4FA0 /* PaddedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BEEC641C4E3978000B4FA0 /* PaddedLabel.swift */; }; @@ -2031,6 +2027,9 @@ F1450CF52437E1A600A28BFE /* MediaHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1450CF42437E1A600A28BFE /* MediaHost.swift */; }; F1450CF72437E8F800A28BFE /* MediaHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1450CF62437E8F800A28BFE /* MediaHostTests.swift */; }; F1450CF92437EEBB00A28BFE /* MediaRequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1450CF82437EEBB00A28BFE /* MediaRequestAuthenticatorTests.swift */; }; + F15272FD243B27BC00C8DC7A /* AbstractPost+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15272FC243B27BC00C8DC7A /* AbstractPost+Local.swift */; }; + F15272FF243B28B700C8DC7A /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15272FE243B28B600C8DC7A /* RequestAuthenticator.swift */; }; + F1527301243B290E00C8DC7A /* AbstractPost+Autosave.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1527300243B290D00C8DC7A /* AbstractPost+Autosave.swift */; }; F15A230420A3EBE300625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; F15A230520A3ECC500625EA2 /* ImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFE20A33BDB0010EB6E /* ImgUploadProcessor.swift */; }; F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */; }; @@ -4627,6 +4626,9 @@ F14B5F74208E64F900439554 /* Version.public.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.public.xcconfig; sourceTree = ""; }; F14B5F75208E64F900439554 /* Version.internal.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.internal.xcconfig; sourceTree = ""; }; F14E844C2317252200D0C63E /* WordPress 90.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 90.xcdatamodel"; sourceTree = ""; }; + F15272FC243B27BC00C8DC7A /* AbstractPost+Local.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AbstractPost+Local.swift"; sourceTree = ""; }; + F15272FE243B28B600C8DC7A /* RequestAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = ""; }; + F1527300243B290D00C8DC7A /* AbstractPost+Autosave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AbstractPost+Autosave.swift"; sourceTree = ""; }; F1655B4722A6C2FA00227BFB /* Routes+Mbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Mbar.swift"; sourceTree = ""; }; F16601C323E9E783007950AE /* SharingAuthorizationWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingAuthorizationWebViewController.swift; sourceTree = ""; }; F16C35D523F33DE400C81331 /* PageAutoUploadMessageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PageAutoUploadMessageProvider.swift; path = ../Post/PageAutoUploadMessageProvider.swift; sourceTree = ""; }; @@ -5426,6 +5428,7 @@ 1A433B1B2254CBC300AE7910 /* Networking */ = { isa = PBXGroup; children = ( + F15272FE243B28B600C8DC7A /* RequestAuthenticator.swift */, 1A433B1C2254CBEE00AE7910 /* WordPressComRestApi+Defaults.swift */, ); path = Networking; @@ -5587,8 +5590,10 @@ B5176CBF1CDCE14F0083CF2D /* People Management */, 5D42A3D6175E7452005CFF05 /* AbstractPost.h */, 5D42A3D7175E7452005CFF05 /* AbstractPost.m */, + F1527300243B290D00C8DC7A /* AbstractPost+Autosave.swift */, 40232A9C230A6A740036B0B6 /* AbstractPost+HashHelpers.h */, 40232A9D230A6A740036B0B6 /* AbstractPost+HashHelpers.m */, + F15272FC243B27BC00C8DC7A /* AbstractPost+Local.swift */, E19B17B11E5C8F36007517C6 /* AbstractPost.swift */, 74729CAD205722E300D1394D /* AbstractPost+Searchable.swift */, 5D42A3D8175E7452005CFF05 /* BasePost.h */, @@ -11726,7 +11731,6 @@ 1E9D544D23C4C56300F6A9E0 /* GutenbergRollout.swift in Sources */, 17E24F5420FCF1D900BD70A3 /* Routes+MySites.swift in Sources */, 0828D7FA1E6E09AE00C7C7D4 /* WPAppAnalytics+Media.swift in Sources */, - 7ED3695520A9F091007B0D56 /* Blog+ImageSourceInformation.swift in Sources */, 591AA5011CEF9BF20074934F /* Post.swift in Sources */, D82253EA219A8A720014D0E2 /* SiteInformationStep.swift in Sources */, C545E0A21811B9880020844C /* ContextManager.m in Sources */, @@ -11753,6 +11757,7 @@ 5D7DEA2919D488DD0032EE77 /* WPStyleGuide+ReaderComments.swift in Sources */, 40C403F62215D66A00E8C894 /* TopViewedPostStatsRecordValue+CoreDataProperties.swift in Sources */, 82FC61251FA8ADAD00A1757E /* ActivityListViewController.swift in Sources */, + F15272FF243B28B700C8DC7A /* RequestAuthenticator.swift in Sources */, E66E2A651FE4311300788F22 /* SettingsTitleSubtitleController.swift in Sources */, E1222B671F878E4700D23173 /* WebViewControllerFactory.swift in Sources */, E1D86E691B2B414300DD2192 /* WordPress-32-33.xcmappingmodel in Sources */, @@ -11855,6 +11860,7 @@ 7E53AB0020FE55A9005796FE /* ActivityContentRouter.swift in Sources */, 738B9A5C21B85EB00005062B /* UIView+ContentLayout.swift in Sources */, E1B9128F1BB05B1D003C25B9 /* PeopleCell.swift in Sources */, + F15272FD243B27BC00C8DC7A /* AbstractPost+Local.swift in Sources */, 3F43603123F31E09001DEE70 /* MeScenePresenter.swift in Sources */, 313AE4A019E3F20400AAFABE /* CommentViewController.m in Sources */, 7E3E7A6620E44F200075D159 /* HeaderContentGroup.swift in Sources */, @@ -12276,7 +12282,6 @@ 85DA8C4418F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m in Sources */, B50C0C5E1EF42A4A00372C65 /* AztecAttachmentViewController.swift in Sources */, F53FF3A123E2377E001AD596 /* NewBlogDetailHeaderView.swift in Sources */, - E1BB85981F82459800797050 /* WebViewAuthenticator.swift in Sources */, 43FB3F411EBD215C00FC8A62 /* LoginEpilogueBlogCell.swift in Sources */, 57D66B9A234BB206005A2D74 /* PostServiceRemoteFactory.swift in Sources */, 98EB126A20D2DC2500D2D5B5 /* NoResultsViewController+Model.swift in Sources */, @@ -12286,7 +12291,6 @@ 7E7947AB210BAC5E005BB851 /* NotificationCommentRange.swift in Sources */, D816C1E920E0880400C4D82F /* NotificationAction.swift in Sources */, E19B17B01E5C69A5007517C6 /* NSManagedObject.swift in Sources */, - 749197EE209B9A2E006F5E66 /* ReaderCardContent+PostInformation.swift in Sources */, FF355D981FB492DD00244E6D /* ExportableAsset.swift in Sources */, E15644EF1CE0E53B00D96E64 /* PlanListViewModel.swift in Sources */, 983002A822FA05D600F03DBB /* AddInsightTableViewController.swift in Sources */, @@ -12343,6 +12347,7 @@ E6E57CD51D0F08B200C22E3E /* ReaderMenuViewController.swift in Sources */, 08D978591CD2AF7D0054F19A /* MenuItemSourceTextBar.m in Sources */, 40E469952017FB1F0030DB5F /* PluginDirectoryViewModel.swift in Sources */, + F1527301243B290E00C8DC7A /* AbstractPost+Autosave.swift in Sources */, 745A41B02065405600299D75 /* ReaderPost+Searchable.swift in Sources */, E1A8CACB1C22FF7C0038689E /* MeViewController.swift in Sources */, 9A341E5321997A1F0036662E /* BlogService+BlogAuthors.swift in Sources */, @@ -12861,7 +12866,6 @@ 3F09CCAE24292EFD00D00A8C /* ReaderSection.swift in Sources */, 5D839AAB187F0D8000811F4A /* PostGeolocationCell.m in Sources */, 4054F4422213635000D261AB /* TopCommentsAuthorStatsRecordValue+CoreDataClass.swift in Sources */, - 7E729C2A209A241100F76599 /* AbstractPost+PostInformation.swift in Sources */, 8236EB502024ED8C007C7CF9 /* NotificationsViewController+JetpackPrompt.swift in Sources */, 5D732F971AE84E3C00CD89E7 /* PostListFooterView.m in Sources */, 1767494E1D3633A000B8D1D1 /* ThemeBrowserSearchHeaderView.swift in Sources */, From 25b90c3f8667ddf94be3f0abd208d11d96af38c6 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 06:14:14 -0300 Subject: [PATCH 26/80] Fixes a final issue in an image setter. --- WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.h | 4 ++-- WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.h b/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.h index 4da86ab3382a..eae85221395c 100644 --- a/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.h +++ b/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.h @@ -1,6 +1,6 @@ #import -@protocol ImageSourceInformation; +@class AbstractPost; @class PostFeaturedImageCell; @protocol PostFeaturedImageCellDelegate @@ -16,6 +16,6 @@ extern CGFloat const PostFeaturedImageCellMargin; @property (weak, nonatomic, nullable) id delegate; @property (strong, nonatomic, readonly, nullable) UIImage *image; -- (void)setImageWithURL:(nonnull NSURL *)url inPost:(nonnull id)postInformation withSize:(CGSize)size; +- (void)setImageWithURL:(nonnull NSURL *)url inPost:(nonnull AbstractPost *)post withSize:(CGSize)size; @end diff --git a/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.m b/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.m index 248af25858b6..c0f4103c382b 100644 --- a/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.m +++ b/WordPress/Classes/ViewRelated/Cells/PostFeaturedImageCell.m @@ -27,10 +27,10 @@ - (void)setup _imageLoader = [[ImageLoader alloc] initWithImageView:self.featuredImageView gifStrategy:GIFStrategyLargeGIFs]; } -- (void)setImageWithURL:(NSURL *)url inPost:(id)postInformation withSize:(CGSize)size +- (void)setImageWithURL:(NSURL *)url inPost:(AbstractPost *)post withSize:(CGSize)size { __weak PostFeaturedImageCell *weakSelf = self; - [self.imageLoader loadImageWithURL:url fromPost:postInformation preferredSize:size placeholder:nil success:^{ + [self.imageLoader loadImageWithURL:url fromPost:post preferredSize:size placeholder:nil success:^{ [weakSelf informDelegateImageLoaded]; } error:^(NSError * _Nullable error) { if (weakSelf && weakSelf.delegate) { From 022f4acb29145f6eb45b8fbdaf8deca6f1c89009 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 06:40:29 -0300 Subject: [PATCH 27/80] Adds atomic private support to site icons. --- .../Extensions/UIImageView+SiteIcon.swift | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 9241fd9d5f03..38e1ddd9328e 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -1,6 +1,6 @@ +import AutomatticTracks import Foundation - /// UIImageView Helper Methods that allow us to download a SiteIcon, given a website's "Icon Path" /// extension UIImageView { @@ -61,22 +61,41 @@ extension UIImageView { /// @objc func downloadSiteIcon(for blog: Blog, placeholderImage: UIImage? = .siteIconPlaceholder) { + func downloadImage(with request: URLRequest) { + self.downloadImage( + usingRequest: request, + placeholderImage: placeholderImage, + success: { [weak self] (image) in + self?.image = image + self?.removePlaceholderBorder() + }, + failure: { error -> () in + if let error = error { + CrashLogging.logError(error) + } + }) + } + guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { image = placeholderImage return } - let request: URLRequest - if blog.isPrivate(), PrivateSiteURLProtocol.urlGoes(toWPComSite: siteIconURL) { - request = PrivateSiteURLProtocol.requestForPrivateSite(from: siteIconURL) - } else { - request = URLRequest(url: siteIconURL) + let host = MediaHost(with: blog) { error in + // We'll log the error, so we know it's there, but we won't halt execution. + CrashLogging.logError(error) } - downloadImage(usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in - self?.image = image - self?.removePlaceholderBorder() - }, failure: nil) + let mediaRequestAuthenticator = MediaRequestAuthenticator() + mediaRequestAuthenticator.authenticatedRequest( + for: siteIconURL, + from: host, + onComplete: { request in + + downloadImage(with: request) + }) { error in + CrashLogging.logError(error) + } } } From 862e81617c3008009fd8eb88f1fc24405cda38cc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 07:25:32 -0300 Subject: [PATCH 28/80] Fixes atomic support for site icons. --- Podfile | 3 ++- Podfile.lock | 15 ++++++++++----- WordPress/Classes/Models/AbstractPost.m | 2 +- WordPress/Classes/Models/Blog.h | 1 + WordPress/Classes/Models/Blog.m | 9 +++++++-- WordPress/Classes/Models/Comment.m | 4 ++-- .../Classes/Services/MediaThumbnailService.swift | 4 ++-- .../Gutenberg/EditorMediaUtility.swift | 2 +- .../People/InvitePersonViewController.swift | 2 +- .../ViewRelated/Post/PostPreviewGenerator.swift | 2 +- 10 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Podfile b/Podfile index 0c3224dda881..26bccb5ba837 100644 --- a/Podfile +++ b/Podfile @@ -34,12 +34,13 @@ end def wordpress_ui ## for production: - pod 'WordPressUI', '~> 1.5.2' + #pod 'WordPressUI', '~> 1.5.2' ## for development: #pod 'WordPressUI', :path => '../WordPressUI-iOS' ## while PR is in review: #pod 'WordPressUI', :git => 'https://github.com/wordpress-mobile/WordPressUI-iOS', :branch => '' + pod 'WordPressUI', :git => 'https://github.com/wordpress-mobile/WordPressUI-iOS', :commit => '71f32a3300b4c630b41ba7ae8101896f9d297606' end def wordpress_kit diff --git a/Podfile.lock b/Podfile.lock index 99749129438f..d9f894d8b46d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -397,7 +397,7 @@ PODS: - WordPressShared (1.8.16): - CocoaLumberjack (~> 3.4) - FormatterKit/TimeIntervalFormatter (= 1.8.2) - - WordPressUI (1.5.2) + - WordPressUI (1.5.3-beta.1) - WPMediaPicker (1.6.1) - wpxmlrpc (0.8.5) - Yoga (1.14.0) @@ -480,7 +480,7 @@ DEPENDENCIES: - WordPressKit (~> 4.7.0-beta.1) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - - WordPressUI (~> 1.5.2) + - WordPressUI (from `https://github.com/wordpress-mobile/WordPressUI-iOS`, commit `71f32a3300b4c630b41ba7ae8101896f9d297606`) - WPMediaPicker (~> 1.6.1) - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.0.0) @@ -527,7 +527,6 @@ SPEC REPOS: - WordPressKit - WordPressMocks - WordPressShared - - WordPressUI - WPMediaPicker - wpxmlrpc - ZendeskCommonUISDK @@ -609,6 +608,9 @@ EXTERNAL SOURCES: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 + WordPressUI: + :commit: 71f32a3300b4c630b41ba7ae8101896f9d297606 + :git: https://github.com/wordpress-mobile/WordPressUI-iOS Yoga: :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json @@ -622,6 +624,9 @@ CHECKOUT OPTIONS: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 + WordPressUI: + :commit: 71f32a3300b4c630b41ba7ae8101896f9d297606 + :git: https://github.com/wordpress-mobile/WordPressUI-iOS SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 @@ -696,7 +701,7 @@ SPEC CHECKSUMS: WordPressKit: bd4cd5b4e7616c388082db83e7cd2031b6b3404e WordPressMocks: b4064b99a073117bbc304abe82df78f2fbe60992 WordPressShared: 1bc316ed162f42af4e0fa2869437e9e28b532b01 - WordPressUI: 70cc58a253c352330b23cd8fa6dd6a2021570e18 + WordPressUI: 91b91e51b8cd7db83a2bd492c12998db711a6a57 WPMediaPicker: 59559813ec8a7929a91aa5a1db74998d8485fb9f wpxmlrpc: 6a9bdd6ab9d1b159b384b0df0f3f39de9af4fecf Yoga: c920bf12bf8146aa5cd118063378c2cf5682d16c @@ -709,6 +714,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: cef51484392988d89101dda34bf165be054aa6e7 +PODFILE CHECKSUM: f62751f0796910e3ea9a31732bcf3e638c006f57 COCOAPODS: 1.8.4 diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index b3d094ed8a3a..6af6d7942cc1 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -477,7 +477,7 @@ - (BOOL)supportsStats - (BOOL)isPrivate { - return self.blog.isPrivate; + return self.blog.isPrivateAtWPCom; } - (BOOL)isMultiAuthorBlog diff --git a/WordPress/Classes/Models/Blog.h b/WordPress/Classes/Models/Blog.h index fdc5584fdfb9..0b5fc47471d8 100644 --- a/WordPress/Classes/Models/Blog.h +++ b/WordPress/Classes/Models/Blog.h @@ -192,6 +192,7 @@ typedef NS_ENUM(NSInteger, SiteVisibility) { - (BOOL)isAtomic; - (BOOL)isAutomatedTransfer; - (BOOL)isPrivate; +- (BOOL)isPrivateAtWPCom; - (nullable NSArray *)sortedCategories; - (nullable id)getOptionValue:(NSString *) name; - (NSString *)loginUrl; diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index a51bfdd5c3b8..a5e084072f96 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -331,10 +331,15 @@ - (NSString *)postFormatTextFromSlug:(NSString *)postFormatSlug return formatText; } -// WP.COM private blog. - (BOOL)isPrivate { - return (self.isHostedAtWPcom && [self.settings.privacy isEqualToNumber:@(SiteVisibilityPrivate)]); + return [self.settings.privacy isEqualToNumber:@(SiteVisibilityPrivate)]; +} + +// WP.COM private blog. +- (BOOL)isPrivateAtWPCom +{ + return (self.isHostedAtWPcom && [self isPrivate]); } - (SiteVisibility)siteVisibility diff --git a/WordPress/Classes/Models/Comment.m b/WordPress/Classes/Models/Comment.m index 446a9d3a0856..9c0c0c5dc3fb 100644 --- a/WordPress/Classes/Models/Comment.m +++ b/WordPress/Classes/Models/Comment.m @@ -102,8 +102,8 @@ - (NSDate *)dateCreated - (BOOL)isPrivateContent { - if ([self.post respondsToSelector:@selector(isPrivate)]) { - return (BOOL)[self.post performSelector:@selector(isPrivate)]; + if ([self.post respondsToSelector:@selector(isPrivateAtWPCom)]) { + return (BOOL)[self.post performSelector:@selector(isPrivateAtWPCom)]; } return NO; } diff --git a/WordPress/Classes/Services/MediaThumbnailService.swift b/WordPress/Classes/Services/MediaThumbnailService.swift index 768f65a87260..1148c232d226 100644 --- a/WordPress/Classes/Services/MediaThumbnailService.swift +++ b/WordPress/Classes/Services/MediaThumbnailService.swift @@ -162,7 +162,7 @@ class MediaThumbnailService: LocalCoreDataService { return } // Get an expected WP URL, for sizing. - if media.blog.isPrivate() || (!media.blog.isHostedAtWPcom && media.blog.isBasicAuthCredentialStored()) { + if media.blog.isPrivateAtWPCom() || (!media.blog.isHostedAtWPcom && media.blog.isBasicAuthCredentialStored()) { remoteURL = WPImageURLHelper.imageURLWithSize(preferredSize, forImageURL: remoteAssetURL) } else { remoteURL = PhotonImageURLHelper.photonURL(with: preferredSize, forImageURL: remoteAssetURL) @@ -187,7 +187,7 @@ class MediaThumbnailService: LocalCoreDataService { onError(error) } } - if media.blog.isPrivate() { + if media.blog.isPrivateAtWPCom() { let accountService = AccountService(managedObjectContext: self.managedObjectContext) guard let authToken = accountService.defaultWordPressComAccount()?.authToken else { // Don't have an auth token for some reason, return nothing. diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index af77a0ea964a..6b3066c2c538 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -62,7 +62,7 @@ class EditorMediaUtility { if url.isFileURL { request = URLRequest(url: url) - } else if post.blog.isPrivate() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + } else if post.blog.isPrivateAtWPCom() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { // private wpcom image needs special handling. // the size that WPImageHelper expects is pixel size size.width = size.width * scale diff --git a/WordPress/Classes/ViewRelated/People/InvitePersonViewController.swift b/WordPress/Classes/ViewRelated/People/InvitePersonViewController.swift index a9aecfb5ac62..4617e0b5c986 100644 --- a/WordPress/Classes/ViewRelated/People/InvitePersonViewController.swift +++ b/WordPress/Classes/ViewRelated/People/InvitePersonViewController.swift @@ -56,7 +56,7 @@ class InvitePersonViewController: UITableViewController { let blogRoles = blog?.sortedRoles ?? [] var roles = [RemoteRole]() let inviteRole: RemoteRole - if blog.isPrivate() { + if blog.isPrivateAtWPCom() { inviteRole = RemoteRole.viewer } else { inviteRole = RemoteRole.follower diff --git a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift index c1d1241b00ec..9b02d9576738 100644 --- a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift +++ b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift @@ -87,7 +87,7 @@ private extension PostPreviewGenerator { case .draft, .publishPrivate, .pending, .scheduled, .publish: return true default: - return post.blog.isPrivate() + return post.blog.isPrivateAtWPCom() } } From 553b7a6e93d9fc2205fb833e30d805ebe0eb9a39 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 08:07:37 -0300 Subject: [PATCH 29/80] Fixes a bug in usernameForSite. --- WordPress/Classes/Models/Blog.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index a5e084072f96..cadf2885fa10 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -443,7 +443,7 @@ - (NSString *)usernameForSite { if (self.username) { return self.username; - } else if (self.account && self.isHostedAtWPcom) { + } else if (self.account && self.isAccessibleThroughWPCom) { return self.account.username; } else { // FIXME: Figure out how to get the self hosted username when using Jetpack REST (@koke 2015-06-15) From f9adeaa9b221d1d8456eed40974ff45174f89c0d Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 08:45:39 -0300 Subject: [PATCH 30/80] Documents some code. --- .../Extensions/UIImageView+SiteIcon.swift | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 38e1ddd9328e..ef3b347bc15c 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -66,8 +66,27 @@ extension UIImageView { usingRequest: request, placeholderImage: placeholderImage, success: { [weak self] (image) in - self?.image = image - self?.removePlaceholderBorder() + guard let self = self else { + return + } + + // In `MediaRequesAuthenticator.authenticatedRequestForPrivateAtomicSiteThroughPhoton` we're + // having to replace photon URLs for Atomic Private Sites, with a call to the Atomic Media Proxy + // endpoint. The downside of calling that endpoint is that it doesn't always return images of + // the requested size. + // + // The following lines of code ensure that we resize the image to the default Site Icon size, to + // ensure there is no UI breakage due to having larger images set here. + // + let expectedSize = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize) + + if image.size != expectedSize { + self.image = image.resizedImage(with: .scaleAspectFill, bounds: expectedSize, interpolationQuality: .default) + } else { + self.image = image + } + + self.removePlaceholderBorder() }, failure: { error -> () in if let error = error { From 1778e6c75e6ee5912a16a89b6ea3d0c925e6e5b8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 08:54:08 -0300 Subject: [PATCH 31/80] Fixes a merge conflict. --- .../Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index ca24291979c8..685e31109fbe 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -140,7 +140,7 @@ class EditorMediaUtility { if url.isFileURL { requestURL = url - } else if post.blog.isPrivateAtWPCom() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + } else if post.blog.isPrivateAtWPCom() && url.isHostedAtWPCom() { // private wpcom image needs special handling. // the size that WPImageHelper expects is pixel size size.width = size.width * scale From 017a6224d5a91098c9924f27b845476dc02ac819 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 09:09:53 -0300 Subject: [PATCH 32/80] Updates WordPressUI to include a bug fix. --- Podfile | 4 ++-- Podfile.lock | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Podfile b/Podfile index 26bccb5ba837..c50418815eb8 100644 --- a/Podfile +++ b/Podfile @@ -34,13 +34,13 @@ end def wordpress_ui ## for production: - #pod 'WordPressUI', '~> 1.5.2' + pod 'WordPressUI', '~> 1.5.3-beta.1' ## for development: #pod 'WordPressUI', :path => '../WordPressUI-iOS' ## while PR is in review: #pod 'WordPressUI', :git => 'https://github.com/wordpress-mobile/WordPressUI-iOS', :branch => '' - pod 'WordPressUI', :git => 'https://github.com/wordpress-mobile/WordPressUI-iOS', :commit => '71f32a3300b4c630b41ba7ae8101896f9d297606' + #pod 'WordPressUI', :git => 'https://github.com/wordpress-mobile/WordPressUI-iOS', :commit => '71f32a3300b4c630b41ba7ae8101896f9d297606' end def wordpress_kit diff --git a/Podfile.lock b/Podfile.lock index d9f894d8b46d..36750371f9d2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -480,7 +480,7 @@ DEPENDENCIES: - WordPressKit (~> 4.7.0-beta.1) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - - WordPressUI (from `https://github.com/wordpress-mobile/WordPressUI-iOS`, commit `71f32a3300b4c630b41ba7ae8101896f9d297606`) + - WordPressUI (~> 1.5.3-beta.1) - WPMediaPicker (~> 1.6.1) - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.0.0) @@ -527,6 +527,7 @@ SPEC REPOS: - WordPressKit - WordPressMocks - WordPressShared + - WordPressUI - WPMediaPicker - wpxmlrpc - ZendeskCommonUISDK @@ -608,9 +609,6 @@ EXTERNAL SOURCES: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 - WordPressUI: - :commit: 71f32a3300b4c630b41ba7ae8101896f9d297606 - :git: https://github.com/wordpress-mobile/WordPressUI-iOS Yoga: :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json @@ -624,9 +622,6 @@ CHECKOUT OPTIONS: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 - WordPressUI: - :commit: 71f32a3300b4c630b41ba7ae8101896f9d297606 - :git: https://github.com/wordpress-mobile/WordPressUI-iOS SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 @@ -714,6 +709,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: f62751f0796910e3ea9a31732bcf3e638c006f57 +PODFILE CHECKSUM: 58f1eb624a61162ede2961b9e8e804969bbcb43d COCOAPODS: 1.8.4 From ed3b31037e8aaefabafae9a4e168897d3fce65f6 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 20:33:45 -0300 Subject: [PATCH 33/80] Wires site_is_atomic from the reader endpoint. --- Podfile | 4 +- Podfile.lock | 15 +- WordPress/Classes/Models/ReaderPost.h | 1 + WordPress/Classes/Models/ReaderPost.m | 1 + .../Classes/Services/ReaderPostService.m | 1 + .../WordPress.xcdatamodeld/.xccurrentversion | 2 +- .../WordPress 95.xcdatamodel/contents | 844 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 4 +- 8 files changed, 863 insertions(+), 9 deletions(-) create mode 100644 WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents diff --git a/Podfile b/Podfile index 4ed8d4d60af9..a77339986a3c 100644 --- a/Podfile +++ b/Podfile @@ -43,11 +43,11 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 4.7.0' + #pod 'WordPressKit', '~> 4.7.0' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '4.6.0-beta.3' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => '' - #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '' + pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde' #pod 'WordPressKit', :path => '../WordPressKit-iOS' end diff --git a/Podfile.lock b/Podfile.lock index 46b08b7b5c13..cb8891832657 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -386,7 +386,7 @@ PODS: - WordPressKit (~> 4.7.0) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.5.2) - - WordPressKit (4.7.0): + - WordPressKit (4.7.1-beta.1): - Alamofire (~> 4.8.0) - CocoaLumberjack (~> 3.4) - NSObject-SafeExpectations (= 0.0.4) @@ -477,7 +477,7 @@ DEPENDENCIES: - SVProgressHUD (= 2.2.5) - WordPress-Editor-iOS (~> 1.17.1) - WordPressAuthenticator (~> 1.12.0) - - WordPressKit (~> 4.7.0) + - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde`) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.5.2) @@ -524,7 +524,6 @@ SPEC REPOS: - WordPress-Aztec-iOS - WordPress-Editor-iOS - WordPressAuthenticator - - WordPressKit - WordPressMocks - WordPressShared - WordPressUI @@ -609,6 +608,9 @@ EXTERNAL SOURCES: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 + WordPressKit: + :commit: 5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git Yoga: :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json @@ -622,6 +624,9 @@ CHECKOUT OPTIONS: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 + WordPressKit: + :commit: 5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 @@ -693,7 +698,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 319620514af963ca519bd83b96a2c0ebdf3a0f03 WordPress-Editor-iOS: 497b55838ef0030cc6ca82eb92da84e661423521 WordPressAuthenticator: 593bbdad097e4752cc464ac1c6ef460e1a9801a8 - WordPressKit: 0602e8188245b3267269570d3d78c138e64a4eba + WordPressKit: dde0a214279fb70d7150b69ae90c7224c18fe2d0 WordPressMocks: b4064b99a073117bbc304abe82df78f2fbe60992 WordPressShared: 1bc316ed162f42af4e0fa2869437e9e28b532b01 WordPressUI: 70cc58a253c352330b23cd8fa6dd6a2021570e18 @@ -709,6 +714,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: 0686708ae420e1e60b9eb397c6ca36e04241a8c6 +PODFILE CHECKSUM: 496ae937cce078bdb658397d8f82e03fd0345721 COCOAPODS: 1.8.4 diff --git a/WordPress/Classes/Models/ReaderPost.h b/WordPress/Classes/Models/ReaderPost.h index 9ed8f8b7d1b3..130c20ebd7a1 100644 --- a/WordPress/Classes/Models/ReaderPost.h +++ b/WordPress/Classes/Models/ReaderPost.h @@ -26,6 +26,7 @@ extern NSString * const ReaderPostStoredCommentTextKey; @property (nonatomic, strong) NSNumber *feedID; @property (nonatomic, strong) NSNumber *feedItemID; @property (nonatomic, strong) NSString *globalID; +@property (nonatomic) BOOL isBlogAtomic; @property (nonatomic) BOOL isBlogPrivate; @property (nonatomic) BOOL isFollowing; @property (nonatomic) BOOL isLiked; diff --git a/WordPress/Classes/Models/ReaderPost.m b/WordPress/Classes/Models/ReaderPost.m index fb67c389fc34..f250c7d117b0 100644 --- a/WordPress/Classes/Models/ReaderPost.m +++ b/WordPress/Classes/Models/ReaderPost.m @@ -26,6 +26,7 @@ @implementation ReaderPost @dynamic featuredImage; @dynamic feedID; @dynamic feedItemID; +@dynamic isBlogAtomic; @dynamic isBlogPrivate; @dynamic isFollowing; @dynamic isLiked; diff --git a/WordPress/Classes/Services/ReaderPostService.m b/WordPress/Classes/Services/ReaderPostService.m index a40f3c826f26..8de9c22db52a 100644 --- a/WordPress/Classes/Services/ReaderPostService.m +++ b/WordPress/Classes/Services/ReaderPostService.m @@ -1159,6 +1159,7 @@ - (ReaderPost *)createOrReplaceFromRemotePost:(RemoteReaderPost *)remotePost for post.feedID = remotePost.feedID; post.feedItemID = remotePost.feedItemID; post.globalID = remotePost.globalID; + post.isBlogAtomic = remotePost.isBlogAtomic; post.isBlogPrivate = remotePost.isBlogPrivate; post.isFollowing = remotePost.isFollowing; post.isLiked = remotePost.isLiked; diff --git a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion index 8f55dabda40d..b2610f0a8915 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion +++ b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - WordPress 94.xcdatamodel + WordPress 95.xcdatamodel diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents new file mode 100644 index 000000000000..fbf79e66e09f --- /dev/null +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents @@ -0,0 +1,844 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index c2aa85b6117e..cf2c127fa362 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -4620,6 +4620,7 @@ F18B43771F849F580089B817 /* PostAttachmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PostAttachmentTests.swift; path = Posts/PostAttachmentTests.swift; sourceTree = ""; }; F1ADCAF6241FEF0C00F150D2 /* AtomicAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicAuthenticationService.swift; sourceTree = ""; }; F1B1E7A224098FA100549E2A /* BlogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogTests.swift; sourceTree = ""; }; + F1BBA95E243BEFC500E9E5E6 /* WordPress 95.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 95.xcdatamodel"; sourceTree = ""; }; F1D690141F828FF000200E30 /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; F1D690151F828FF000200E30 /* FeatureFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = ""; }; F1DB8D282288C14400906E2F /* Uploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uploader.swift; sourceTree = ""; }; @@ -16448,6 +16449,7 @@ E125443B12BF5A7200D87A0A /* WordPress.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + F1BBA95E243BEFC500E9E5E6 /* WordPress 95.xcdatamodel */, 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */, 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */, 57CCB37E2358E5DC003ECD0C /* WordPress 92.xcdatamodel */, @@ -16543,7 +16545,7 @@ 8350E15911D28B4A00A7B073 /* WordPress.xcdatamodel */, E125443D12BF5A7200D87A0A /* WordPress 2.xcdatamodel */, ); - currentVersion = 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */; + currentVersion = F1BBA95E243BEFC500E9E5E6 /* WordPress 95.xcdatamodel */; name = WordPress.xcdatamodeld; path = Classes/WordPress.xcdatamodeld; sourceTree = ""; From dba7675939b73f5b104e80a62f86974301f94bd2 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 20:44:19 -0300 Subject: [PATCH 34/80] Adds some documentation. --- WordPress/Classes/Models/Blog.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index cadf2885fa10..bccc3132f70a 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -331,12 +331,15 @@ - (NSString *)postFormatTextFromSlug:(NSString *)postFormatSlug return formatText; } +/// Call this method to know whether the blog is private. +/// - (BOOL)isPrivate { return [self.settings.privacy isEqualToNumber:@(SiteVisibilityPrivate)]; } -// WP.COM private blog. +/// Call this method to know whether the blog is private AND hosted at WP.com. +/// - (BOOL)isPrivateAtWPCom { return (self.isHostedAtWPcom && [self isPrivate]); From 5c540a3bdfd62cc884b855b933fef6df0e8e1a4f Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 6 Apr 2020 20:47:41 -0300 Subject: [PATCH 35/80] Fixes an issue introduced by Xcode's renaming tool. --- WordPress/Classes/Models/Comment.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Models/Comment.m b/WordPress/Classes/Models/Comment.m index 9c0c0c5dc3fb..446a9d3a0856 100644 --- a/WordPress/Classes/Models/Comment.m +++ b/WordPress/Classes/Models/Comment.m @@ -102,8 +102,8 @@ - (NSDate *)dateCreated - (BOOL)isPrivateContent { - if ([self.post respondsToSelector:@selector(isPrivateAtWPCom)]) { - return (BOOL)[self.post performSelector:@selector(isPrivateAtWPCom)]; + if ([self.post respondsToSelector:@selector(isPrivate)]) { + return (BOOL)[self.post performSelector:@selector(isPrivate)]; } return NO; } From 07aa44656a4ea16ee52e91e0530cd41d5d62eb54 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 12:00:30 -0300 Subject: [PATCH 36/80] Renames a method for clarity. --- .../Classes/Extensions/AbstractPost+PostInformation.swift | 2 +- WordPress/Classes/Models/AbstractPost.h | 2 +- WordPress/Classes/Models/AbstractPost.m | 2 +- WordPress/Classes/Models/Comment.m | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 39bc42b56466..4538659694b6 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -1,7 +1,7 @@ extension AbstractPost: ImageSourceInformation { var isPrivateOnWPCom: Bool { - return isPrivate() && blog.isHostedAtWPcom + return isPrivateAtWPCom() } var isSelfHostedWithCredentials: Bool { diff --git a/WordPress/Classes/Models/AbstractPost.h b/WordPress/Classes/Models/AbstractPost.h index 3175e05fbbc0..d11802c97f0e 100644 --- a/WordPress/Classes/Models/AbstractPost.h +++ b/WordPress/Classes/Models/AbstractPost.h @@ -96,7 +96,7 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { - (NSString *)blavatarForDisplay; - (NSString *)dateStringForDisplay; - (BOOL)isMultiAuthorBlog; -- (BOOL)isPrivate; +- (BOOL)isPrivateAtWPCom; - (BOOL)supportsStats; diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index 6af6d7942cc1..07cd7b99679b 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -475,7 +475,7 @@ - (BOOL)supportsStats return [self.blog supports:BlogFeatureStats] && [self hasRemote]; } -- (BOOL)isPrivate +- (BOOL)isPrivateAtWPCom { return self.blog.isPrivateAtWPCom; } diff --git a/WordPress/Classes/Models/Comment.m b/WordPress/Classes/Models/Comment.m index 446a9d3a0856..9c0c0c5dc3fb 100644 --- a/WordPress/Classes/Models/Comment.m +++ b/WordPress/Classes/Models/Comment.m @@ -102,8 +102,8 @@ - (NSDate *)dateCreated - (BOOL)isPrivateContent { - if ([self.post respondsToSelector:@selector(isPrivate)]) { - return (BOOL)[self.post performSelector:@selector(isPrivate)]; + if ([self.post respondsToSelector:@selector(isPrivateAtWPCom)]) { + return (BOOL)[self.post performSelector:@selector(isPrivateAtWPCom)]; } return NO; } From 53a8c20e84b3b0c37e0d66d4c7e2b7712eb2dae6 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 12:15:16 -0300 Subject: [PATCH 37/80] Moves some code. --- .../Extensions/UIImageView+SiteIcon.swift | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index ef3b347bc15c..75e3435eb268 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -51,6 +51,49 @@ extension UIImageView { downloadImage(from: siteIconURL, placeholderImage: placeholderImage) } + /// Downloads a SiteIcon image, using a specified request. + /// + /// - Parameters: + /// - request: the request for the SiteIcon. + /// - placeholderImage: Yes. It's the "place holder image". + /// + private func downloadSiteIcon( + with request: URLRequest, + placeholderImage: UIImage?) { + + downloadImage( + usingRequest: request, + placeholderImage: placeholderImage, + success: { [weak self] (image) in + guard let self = self else { + return + } + + // In `MediaRequesAuthenticator.authenticatedRequestForPrivateAtomicSiteThroughPhoton` we're + // having to replace photon URLs for Atomic Private Sites, with a call to the Atomic Media Proxy + // endpoint. The downside of calling that endpoint is that it doesn't always return images of + // the requested size. + // + // The following lines of code ensure that we resize the image to the default Site Icon size, to + // ensure there is no UI breakage due to having larger images set here. + // + let expectedSize = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize) + + if image.size != expectedSize { + self.image = image.resizedImage(with: .scaleAspectFill, bounds: expectedSize, interpolationQuality: .default) + } else { + self.image = image + } + + self.removePlaceholderBorder() + }, + failure: { error -> () in + if let error = error { + CrashLogging.logError(error) + } + }) + } + /// Downloads the SiteIcon Image, associated to a given Blog. This method will attempt to optimize the URL, so that /// the download Image Size matches `SiteIconDefaults.imageSize`. @@ -61,40 +104,6 @@ extension UIImageView { /// @objc func downloadSiteIcon(for blog: Blog, placeholderImage: UIImage? = .siteIconPlaceholder) { - func downloadImage(with request: URLRequest) { - self.downloadImage( - usingRequest: request, - placeholderImage: placeholderImage, - success: { [weak self] (image) in - guard let self = self else { - return - } - - // In `MediaRequesAuthenticator.authenticatedRequestForPrivateAtomicSiteThroughPhoton` we're - // having to replace photon URLs for Atomic Private Sites, with a call to the Atomic Media Proxy - // endpoint. The downside of calling that endpoint is that it doesn't always return images of - // the requested size. - // - // The following lines of code ensure that we resize the image to the default Site Icon size, to - // ensure there is no UI breakage due to having larger images set here. - // - let expectedSize = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize) - - if image.size != expectedSize { - self.image = image.resizedImage(with: .scaleAspectFill, bounds: expectedSize, interpolationQuality: .default) - } else { - self.image = image - } - - self.removePlaceholderBorder() - }, - failure: { error -> () in - if let error = error { - CrashLogging.logError(error) - } - }) - } - guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else { image = placeholderImage return @@ -109,9 +118,8 @@ extension UIImageView { mediaRequestAuthenticator.authenticatedRequest( for: siteIconURL, from: host, - onComplete: { request in - - downloadImage(with: request) + onComplete: { [weak self] request in + self?.downloadSiteIcon(with: request, placeholderImage: placeholderImage) }) { error in CrashLogging.logError(error) } From f7564cff073ac4437e598a653db17f921021711c Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Mon, 30 Mar 2020 15:17:52 -0700 Subject: [PATCH 38/80] Add a new bottom sheet presentation controller --- .../DrawerPresentationController.swift | 342 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 4 + 2 files changed, 346 insertions(+) create mode 100644 WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift new file mode 100644 index 000000000000..0135c1b5ee6f --- /dev/null +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -0,0 +1,342 @@ +import UIKit + +public enum DrawerPosition { + case top + case bottom + case closed +} + +public protocol DrawerPresentable: AnyObject { + var initialHeight: CGFloat { get } + var scrollableView: UIScrollView? { get } + var allowsUserTransition: Bool { get } + var allowsDragToDismiss: Bool { get } +} + +typealias UIDrawerPresentable = DrawerPresentable & UIViewController + +public extension DrawerPresentable where Self: UIViewController { + //Default values + var allowsUserTransition: Bool { + return true + } + + var initialHeight: CGFloat { + return 0 + } + + var scrollableView: UIScrollView? { + return nil + } + + var allowsDragToDismiss: Bool { + return true + } + + // Helpers + var presentedVC: DrawerPresentationController? { + guard let navController = self.navigationController else { + return presentationController as? DrawerPresentationController + } + + return navController.presentationController as? DrawerPresentationController + } +} + +public class DrawerPresentationController: FancyAlertPresentationController { + private enum Constants { + static let transitionDuration: TimeInterval = 0.2 + static let defaultTopMargin: CGFloat = 20 + static let flickVelocity: CGFloat = 300 + static let bounceAmount: CGFloat = 0.01 + } + + override public var frameOfPresentedViewInContainerView: CGRect { + guard let containerView = self.containerView else { + return .zero + } + + var frame = containerView.frame + let y = bottomYPosition + + frame.origin.y = y + + return frame + } + + public var position: DrawerPosition = .bottom + + public func transition(to position: DrawerPosition) { + self.position = position + + if position == .closed { + dismiss() + return + } + + var margin: CGFloat = 0 + + switch position { + case .top: + margin = topYPosition + + case .bottom: + margin = bottomYPosition + + default: + margin = 0 + } + + setTopMargin(margin) + } + + @objc func dismiss() { + self.presentedViewController.dismiss(animated: true, completion: nil) + } + + public override func presentationTransitionWillBegin() { + super.presentationTransitionWillBegin() + + configureScrollViewInsets() + } + + public override func presentationTransitionDidEnd(_ completed: Bool) { + super.presentationTransitionDidEnd(completed) + + configureScrollViewInsets() + } + + + //MARK: - Internal Positions + private var closedPosition: CGFloat { + guard let presentedView = self.presentedView else { + return 0 + } + + return presentedView.bounds.height + } + + private var bottomYPosition: CGFloat { + return calculatedTopMargin(for: presentableInitialHeight) + } + + private var topYPosition: CGFloat { + return Constants.defaultTopMargin + safeAreaInsets.top + } + + + //MARK: - Panning + private lazy var tapGestureRecognizer: UITapGestureRecognizer = { + return UITapGestureRecognizer(target: self, action: #selector(self.dismiss)) + }() + + private lazy var panGestureRecognizer: UIPanGestureRecognizer = { + return UIPanGestureRecognizer(target: self, action: #selector(self.pan(_:))) + }() + + var interactionController: UIPercentDrivenInteractiveTransition? + + override public func containerViewWillLayoutSubviews() { + super.containerViewWillLayoutSubviews() + + addGestures() + } + + private var startPoint: CGPoint? + +} + +//MARK: - Dragging +private extension DrawerPresentationController { + private func addGestures() { + guard let presentedView = self.presentedView else { return } + + presentedView.addGestureRecognizer(self.panGestureRecognizer) + } + + @objc func pan(_ gesture: UIPanGestureRecognizer) { + guard let presentedView = self.presentedView else { return } + + let translation = gesture.translation(in: presentedView) + let allowsUserTransition = presentableViewController?.allowsUserTransition ?? false + let allowDragToDismiss = presentableViewController?.allowsDragToDismiss ?? true + + switch gesture.state { + case .began: + startPoint = presentedView.frame.origin + + case .changed: + let startY = startPoint?.y ?? 0 + var yTranslation = translation.y + + if (!allowsUserTransition || !allowDragToDismiss) { + let maxBounce: CGFloat = (startY * Constants.bounceAmount) + + if yTranslation < 0 { + yTranslation = max(yTranslation, maxBounce * -1) + } else { + if !allowDragToDismiss { + yTranslation = min(yTranslation, maxBounce) + } + } + } + + self.setTopMargin((startY + yTranslation), animated: false) + + case .ended: + /// Helper closure to prevent user transitions + let transition:(DrawerPosition) -> () = { pos in + + if allowsUserTransition { + self.transition(to: pos) + return + } + + if pos == .closed && allowDragToDismiss { + self.transition(to: pos) + } else { + self.transition(to: self.position) + } + } + + let velocity = gesture.velocity(in: presentedView).y + let startY = startPoint?.y ?? 0 + + let currentPosition = (startY + translation.y) + let position = closestPosition(for: currentPosition) + + // Determine how to handle flicking of the view + if ((abs(velocity) - Constants.flickVelocity) > 0) { + //Flick up + if velocity < 0 { + transition(.top) + } + else { + if(position == .top){ + transition(.bottom) + } else { + transition(.closed) + } + } + + return + } + + transition(position) + + startPoint = nil + + default: + return + } + } + +} + +private extension UIScrollView { + /** + A flag to determine if a scroll view is scrolling + */ + var isScrolling: Bool { + return isDragging && !isDecelerating || isTracking + } +} + +//MARK: - Private: Helpers +private extension DrawerPresentationController { + + private func configureScrollViewInsets() { + guard + let scrollView = presentableViewController?.scrollableView, + !scrollView.isScrolling, + let presentedView = self.presentedView + else { return } + + + let bottom = presentingViewController.bottomLayoutGuide.length + let margin = presentedView.frame.origin.y + bottom + + /** + Disable vertical scroll indicator until we start to scroll + to avoid visual bugs + */ +// scrollView.showsVerticalScrollIndicator = false +// scrollView.scrollIndicatorInsets = .zero +// + scrollView.contentInset.bottom = margin + } + + private var presentableViewController: DrawerPresentable? { + return presentedViewController as? DrawerPresentable + } + + private var presentableInitialHeight: CGFloat { + guard let presentableVC = presentableViewController else { + return 0 + } + + return presentableVC.initialHeight + } + + private func calculatedTopMargin(for height: CGFloat) -> CGFloat { + guard let containerView = self.containerView else { + return 0 + } + + let bounds = containerView.bounds + let margin = bounds.maxY - (safeAreaInsets.bottom + ((height > 0) ? height : (bounds.height * 0.5))) + + //Limit the max height + return max(margin, safeAreaInsets.top) + } + + private func setTopMargin(_ margin: CGFloat, animated: Bool = true) { + guard let presentedView = self.presentedView else { + return + } + + var frame = presentedView.frame + frame.origin.y = margin + + let animations = { + presentedView.frame = frame + + self.configureScrollViewInsets() + } + + + if animated { + UIView.animate(withDuration: Constants.transitionDuration, animations: animations) + } else { + animations() + } + } + + private var safeAreaInsets: UIEdgeInsets { + guard let rootViewController = self.rootViewController else { + return .zero + } + + return rootViewController.view.safeAreaInsets + } + + func closestPosition(for yPosition: CGFloat) -> DrawerPosition { + let positions = [closedPosition, bottomYPosition, topYPosition] + let closestVal = positions.min(by: { abs(yPosition - $0) < abs(yPosition - $1) }) ?? yPosition + + var returnPosition: DrawerPosition = .closed + + if closestVal == topYPosition { + returnPosition = .top + } else if(closestVal == bottomYPosition) { + returnPosition = .bottom + } + + return returnPosition + } + + + private var rootViewController: UIViewController? { + return UIApplication.shared.keyWindow?.rootViewController + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index c2aa85b6117e..6c69a193d9f3 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -336,6 +336,7 @@ 319D6E8519E44F7F0013871C /* SuggestionsTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 319D6E8419E44F7F0013871C /* SuggestionsTableViewCell.m */; }; 31C9F82E1A2368A2008BB945 /* BlogDetailHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 31C9F82D1A2368A2008BB945 /* BlogDetailHeaderView.m */; }; 31EC15081A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m in Sources */ = {isa = PBXBuildFile; fileRef = 31EC15071A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m */; }; + 320B40DE2432533600DAB738 /* DrawerPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320B40DD2432533600DAB738 /* DrawerPresentationController.swift */; }; 321E292623A5F10900588610 /* FullScreenCommentReplyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 328CEC5D23A532BA00A6899E /* FullScreenCommentReplyViewController.swift */; }; 323F8F3023A22C4C000BA49C /* SiteCreationRotatingMessageViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C6CDDA23A1FF0D002556FF /* SiteCreationRotatingMessageViewTests.swift */; }; 323F8F3123A22C8F000BA49C /* SiteCreationRotatingMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3221278523A0BD27002CA183 /* SiteCreationRotatingMessageView.swift */; }; @@ -2661,6 +2662,7 @@ 31EC15061A5B6675009FC8B3 /* WPStyleGuide+Suggestions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "WPStyleGuide+Suggestions.h"; sourceTree = ""; }; 31EC15071A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "WPStyleGuide+Suggestions.m"; sourceTree = ""; }; 31FA16CC1A49B3C0003E1887 /* WordPress 25.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 25.xcdatamodel"; sourceTree = ""; }; + 320B40DD2432533600DAB738 /* DrawerPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerPresentationController.swift; sourceTree = ""; }; 3221278523A0BD27002CA183 /* SiteCreationRotatingMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteCreationRotatingMessageView.swift; sourceTree = ""; }; 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 94.xcdatamodel"; sourceTree = ""; }; 3249615023F20111004C7733 /* PostSignUpInterstitialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSignUpInterstitialViewController.swift; sourceTree = ""; }; @@ -10088,6 +10090,7 @@ F5E032E22408D537003AF350 /* Action Sheet */ = { isa = PBXGroup; children = ( + 320B40DD2432533600DAB738 /* DrawerPresentationController.swift */, F5E032E32408D537003AF350 /* ActionSheetViewController.swift */, F5E032E42408D537003AF350 /* GripView.swift */, F5E032E52408D537003AF350 /* BottomSheetPresentationController.swift */, @@ -12263,6 +12266,7 @@ 7E3E7A6020E44E490075D159 /* FooterContentGroup.swift in Sources */, 59A3CADD1CD2FF0C009BFA1B /* BasePageListCell.m in Sources */, 436D56202117312700CEAA33 /* RegisterDomainSuggestionsTableViewController.swift in Sources */, + 320B40DE2432533600DAB738 /* DrawerPresentationController.swift in Sources */, E114D79A153D85A800984182 /* WPError.m in Sources */, 7E53AB0220FE5EAE005796FE /* ContentRouter.swift in Sources */, E16273E11B2ACEB600088AF7 /* BlogToBlog32to33.swift in Sources */, From e8cbf45d7a9ff5588449b9ef33918ab6e9d7115d Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 09:45:26 -0700 Subject: [PATCH 39/80] Use a spring animation when transitioning between Drawer positions --- .../Action Sheet/DrawerPresentationController.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 0135c1b5ee6f..a1ad0c3358d3 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -304,9 +304,8 @@ private extension DrawerPresentationController { self.configureScrollViewInsets() } - if animated { - UIView.animate(withDuration: Constants.transitionDuration, animations: animations) + animate(animations) } else { animations() } @@ -335,6 +334,14 @@ private extension DrawerPresentationController { return returnPosition } + private func animate(_ animations: @escaping () -> Void) { + UIView.animate(withDuration: Constants.transitionDuration, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0, + options: .curveEaseInOut, + animations: animations) + } private var rootViewController: UIViewController? { return UIApplication.shared.keyWindow?.rootViewController From eaa0ba8fe2689a55ea663acdeb06ea74b4882b23 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 09:49:30 -0700 Subject: [PATCH 40/80] Add more customization for the bottom/top drawer heights --- .../DrawerPresentationController.swift | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index a1ad0c3358d3..babbde680d4a 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -6,11 +6,32 @@ public enum DrawerPosition { case closed } +public enum DrawerHeight { + // The maximum height for the screen + case maxHeight + + //Height is based on the specified margin from the top of the screen + case topMargin(CGFloat) + + // Height will be equal to the the content height value + case contentHeight(CGFloat) +} + public protocol DrawerPresentable: AnyObject { - var initialHeight: CGFloat { get } - var scrollableView: UIScrollView? { get } + /// The height of the drawer when it's in the expanded position + var topHeight: DrawerHeight { get } + + /// The height of the drawer when it's in the collapsed position + var bottomHeight: DrawerHeight { get } + + /// Whether or not the user is allowed to swipe to switch between the expanded and collapsed position var allowsUserTransition: Bool { get } + + /// Whether or not the user is allowed to drag to dismiss the drawer var allowsDragToDismiss: Bool { get } + + /// A scroll view that should have its insets adjusted when the drawer is expanded/collapsed + var scrollableView: UIScrollView? { get } } typealias UIDrawerPresentable = DrawerPresentable & UIViewController @@ -21,8 +42,12 @@ public extension DrawerPresentable where Self: UIViewController { return true } - var initialHeight: CGFloat { - return 0 + var bottomHeight: DrawerHeight { + return .contentHeight(0) + } + + var topHeight: DrawerHeight { + return .topMargin(20) } var scrollableView: UIScrollView? { @@ -117,13 +142,37 @@ public class DrawerPresentationController: FancyAlertPresentationController { } private var bottomYPosition: CGFloat { - return calculatedTopMargin(for: presentableInitialHeight) + guard let presentableVC = presentableViewController else { + return calculatedTopMargin(for: 0) + } + + return topMargin(with: presentableVC.bottomHeight) } private var topYPosition: CGFloat { - return Constants.defaultTopMargin + safeAreaInsets.top + guard let presentableVC = presentableViewController else { + return calculatedTopMargin(for: Constants.defaultTopMargin) + } + + return topMargin(with: presentableVC.topHeight) } + private func topMargin(with drawerHeight: DrawerHeight) -> CGFloat { + var topMargin: CGFloat + + switch drawerHeight { + case .contentHeight(let height): + topMargin = calculatedTopMargin(for: height) + + case .topMargin(let margin): + topMargin = safeAreaInsets.top + margin + + case .maxHeight: + topMargin = safeAreaInsets.top + } + + return topMargin + } //MARK: - Panning private lazy var tapGestureRecognizer: UITapGestureRecognizer = { @@ -143,7 +192,6 @@ public class DrawerPresentationController: FancyAlertPresentationController { } private var startPoint: CGPoint? - } //MARK: - Dragging @@ -181,7 +229,9 @@ private extension DrawerPresentationController { } } - self.setTopMargin((startY + yTranslation), animated: false) + let maxY = topMargin(with: .maxHeight) + + self.setTopMargin(max((startY + yTranslation), maxY), animated: false) case .ended: /// Helper closure to prevent user transitions From 7d123139bb6d7f793147cc59b5d2c0461caba451 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 10:02:38 -0700 Subject: [PATCH 41/80] Change drawer positions from top/bottom to expanded/collapsed --- .../DrawerPresentationController.swift | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index babbde680d4a..4516230d9447 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -1,8 +1,8 @@ import UIKit public enum DrawerPosition { - case top - case bottom + case expanded + case collapsed case closed } @@ -19,10 +19,10 @@ public enum DrawerHeight { public protocol DrawerPresentable: AnyObject { /// The height of the drawer when it's in the expanded position - var topHeight: DrawerHeight { get } + var expandedHeight: DrawerHeight { get } /// The height of the drawer when it's in the collapsed position - var bottomHeight: DrawerHeight { get } + var collapsedHeight: DrawerHeight { get } /// Whether or not the user is allowed to swipe to switch between the expanded and collapsed position var allowsUserTransition: Bool { get } @@ -42,12 +42,12 @@ public extension DrawerPresentable where Self: UIViewController { return true } - var bottomHeight: DrawerHeight { - return .contentHeight(0) + var expandedHeight: DrawerHeight { + return .topMargin(20) } - var topHeight: DrawerHeight { - return .topMargin(20) + var collapsedHeight: DrawerHeight { + return .contentHeight(0) } var scrollableView: UIScrollView? { @@ -70,7 +70,7 @@ public extension DrawerPresentable where Self: UIViewController { public class DrawerPresentationController: FancyAlertPresentationController { private enum Constants { - static let transitionDuration: TimeInterval = 0.2 + static let transitionDuration: TimeInterval = 0.5 static let defaultTopMargin: CGFloat = 20 static let flickVelocity: CGFloat = 300 static let bounceAmount: CGFloat = 0.01 @@ -82,14 +82,14 @@ public class DrawerPresentationController: FancyAlertPresentationController { } var frame = containerView.frame - let y = bottomYPosition + let y = collapsedYPosition frame.origin.y = y return frame } - public var position: DrawerPosition = .bottom + public var position: DrawerPosition = .collapsed public func transition(to position: DrawerPosition) { self.position = position @@ -102,11 +102,11 @@ public class DrawerPresentationController: FancyAlertPresentationController { var margin: CGFloat = 0 switch position { - case .top: - margin = topYPosition + case .expanded: + margin = expandedYPosition - case .bottom: - margin = bottomYPosition + case .collapsed: + margin = collapsedYPosition default: margin = 0 @@ -141,20 +141,20 @@ public class DrawerPresentationController: FancyAlertPresentationController { return presentedView.bounds.height } - private var bottomYPosition: CGFloat { + private var collapsedYPosition: CGFloat { guard let presentableVC = presentableViewController else { return calculatedTopMargin(for: 0) } - return topMargin(with: presentableVC.bottomHeight) + return topMargin(with: presentableVC.collapsedHeight) } - private var topYPosition: CGFloat { + private var expandedYPosition: CGFloat { guard let presentableVC = presentableViewController else { return calculatedTopMargin(for: Constants.defaultTopMargin) } - return topMargin(with: presentableVC.topHeight) + return topMargin(with: presentableVC.expandedHeight) } private func topMargin(with drawerHeight: DrawerHeight) -> CGFloat { @@ -259,11 +259,11 @@ private extension DrawerPresentationController { if ((abs(velocity) - Constants.flickVelocity) > 0) { //Flick up if velocity < 0 { - transition(.top) + transition(.expanded) } else { - if(position == .top){ - transition(.bottom) + if(position == .expanded){ + transition(.collapsed) } else { transition(.closed) } @@ -320,14 +320,6 @@ private extension DrawerPresentationController { return presentedViewController as? DrawerPresentable } - private var presentableInitialHeight: CGFloat { - guard let presentableVC = presentableViewController else { - return 0 - } - - return presentableVC.initialHeight - } - private func calculatedTopMargin(for height: CGFloat) -> CGFloat { guard let containerView = self.containerView else { return 0 @@ -370,15 +362,15 @@ private extension DrawerPresentationController { } func closestPosition(for yPosition: CGFloat) -> DrawerPosition { - let positions = [closedPosition, bottomYPosition, topYPosition] + let positions = [closedPosition, collapsedYPosition, expandedYPosition] let closestVal = positions.min(by: { abs(yPosition - $0) < abs(yPosition - $1) }) ?? yPosition var returnPosition: DrawerPosition = .closed - if closestVal == topYPosition { - returnPosition = .top - } else if(closestVal == bottomYPosition) { - returnPosition = .bottom + if closestVal == expandedYPosition { + returnPosition = .expanded + } else if(closestVal == collapsedYPosition) { + returnPosition = .collapsed } return returnPosition From 5655e86f9d54bedc89d4823ac1d10cd95db46642 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 10:34:16 -0700 Subject: [PATCH 42/80] Add tap to dismiss to the drawer presentation controller --- .../DrawerPresentationController.swift | 55 +++++++++++++++++-- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 4516230d9447..4cb3bfe209e8 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -30,6 +30,9 @@ public protocol DrawerPresentable: AnyObject { /// Whether or not the user is allowed to drag to dismiss the drawer var allowsDragToDismiss: Bool { get } + /// Whether or not the user is allowed to tap outside the view to dismiss the drawer + var allowsTapToDismiss: Bool { get } + /// A scroll view that should have its insets adjusted when the drawer is expanded/collapsed var scrollableView: UIScrollView? { get } } @@ -58,6 +61,10 @@ public extension DrawerPresentable where Self: UIViewController { return true } + var allowsTapToDismiss: Bool { + return true + } + // Helpers var presentedVC: DrawerPresentationController? { guard let navController = self.navigationController else { @@ -174,17 +181,17 @@ public class DrawerPresentationController: FancyAlertPresentationController { return topMargin } - //MARK: - Panning + //MARK: - Gestures private lazy var tapGestureRecognizer: UITapGestureRecognizer = { - return UITapGestureRecognizer(target: self, action: #selector(self.dismiss)) + let gesture = UITapGestureRecognizer(target: self, action: #selector(self.dismiss(_:))) + gesture.delegate = self + return gesture }() private lazy var panGestureRecognizer: UIPanGestureRecognizer = { return UIPanGestureRecognizer(target: self, action: #selector(self.pan(_:))) }() - var interactionController: UIPercentDrivenInteractiveTransition? - override public func containerViewWillLayoutSubviews() { super.containerViewWillLayoutSubviews() @@ -197,9 +204,27 @@ public class DrawerPresentationController: FancyAlertPresentationController { //MARK: - Dragging private extension DrawerPresentationController { private func addGestures() { - guard let presentedView = self.presentedView else { return } + guard + let presentedView = self.presentedView, + let containerView = self.containerView + else { return } - presentedView.addGestureRecognizer(self.panGestureRecognizer) + presentedView.addGestureRecognizer(panGestureRecognizer) + containerView.addGestureRecognizer(tapGestureRecognizer) + } + + @objc func dismiss(_ gesture: UIPanGestureRecognizer) { + var canDismiss = true + + if let presentableVC = presentableViewController { + canDismiss = presentableVC.allowsTapToDismiss + } + + guard canDismiss else { + return + } + + dismiss() } @objc func pan(_ gesture: UIPanGestureRecognizer) { @@ -292,6 +317,24 @@ private extension UIScrollView { } } +extension DrawerPresentationController: UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + /// Shouldn't happen; should always have container & presented view when tapped + guard + let containerView = containerView, + let presentedView = presentedView + else { + return false + } + + let touchPoint = touch.location(in: containerView) + let isInPresentedView = presentedView.frame.contains(touchPoint) + + /// Do not accept the touch if inside of the presented view + return (gestureRecognizer == tapGestureRecognizer) && isInPresentedView == false + } +} + //MARK: - Private: Helpers private extension DrawerPresentationController { From 4bcecae3475714ced27ca8d5cf681615972b770f Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 10:39:31 -0700 Subject: [PATCH 43/80] Rename startPoint to dragStartPoint --- .../Action Sheet/DrawerPresentationController.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 4cb3bfe209e8..be62ce2864b7 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -198,7 +198,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { addGestures() } - private var startPoint: CGPoint? + private var dragStartPoint: CGPoint? } //MARK: - Dragging @@ -236,10 +236,10 @@ private extension DrawerPresentationController { switch gesture.state { case .began: - startPoint = presentedView.frame.origin + dragStartPoint = presentedView.frame.origin case .changed: - let startY = startPoint?.y ?? 0 + let startY = dragStartPoint?.y ?? 0 var yTranslation = translation.y if (!allowsUserTransition || !allowDragToDismiss) { @@ -275,7 +275,7 @@ private extension DrawerPresentationController { } let velocity = gesture.velocity(in: presentedView).y - let startY = startPoint?.y ?? 0 + let startY = dragStartPoint?.y ?? 0 let currentPosition = (startY + translation.y) let position = closestPosition(for: currentPosition) @@ -299,7 +299,7 @@ private extension DrawerPresentationController { transition(position) - startPoint = nil + dragStartPoint = nil default: return From ee1a292bbad8ed8c7e7d35f77198281fea88dcb0 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 10:52:02 -0700 Subject: [PATCH 44/80] Fix lint issues --- .../DrawerPresentationController.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index be62ce2864b7..5fbab77f28a2 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -139,7 +139,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { } - //MARK: - Internal Positions + // MARK: - Internal Positions private var closedPosition: CGFloat { guard let presentedView = self.presentedView else { return 0 @@ -181,7 +181,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { return topMargin } - //MARK: - Gestures + // MARK: - Gestures private lazy var tapGestureRecognizer: UITapGestureRecognizer = { let gesture = UITapGestureRecognizer(target: self, action: #selector(self.dismiss(_:))) gesture.delegate = self @@ -201,7 +201,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { private var dragStartPoint: CGPoint? } -//MARK: - Dragging +// MARK: - Dragging private extension DrawerPresentationController { private func addGestures() { guard @@ -242,7 +242,7 @@ private extension DrawerPresentationController { let startY = dragStartPoint?.y ?? 0 var yTranslation = translation.y - if (!allowsUserTransition || !allowDragToDismiss) { + if !allowsUserTransition || !allowDragToDismiss { let maxBounce: CGFloat = (startY * Constants.bounceAmount) if yTranslation < 0 { @@ -260,8 +260,7 @@ private extension DrawerPresentationController { case .ended: /// Helper closure to prevent user transitions - let transition:(DrawerPosition) -> () = { pos in - + let transition: (DrawerPosition) -> () = { pos in if allowsUserTransition { self.transition(to: pos) return @@ -281,13 +280,13 @@ private extension DrawerPresentationController { let position = closestPosition(for: currentPosition) // Determine how to handle flicking of the view - if ((abs(velocity) - Constants.flickVelocity) > 0) { + if (abs(velocity) - Constants.flickVelocity) > 0 { //Flick up if velocity < 0 { transition(.expanded) } else { - if(position == .expanded){ + if position == .expanded { transition(.collapsed) } else { transition(.closed) @@ -335,7 +334,7 @@ extension DrawerPresentationController: UIGestureRecognizerDelegate { } } -//MARK: - Private: Helpers +// MARK: - Private: Helpers private extension DrawerPresentationController { private func configureScrollViewInsets() { @@ -412,7 +411,7 @@ private extension DrawerPresentationController { if closestVal == expandedYPosition { returnPosition = .expanded - } else if(closestVal == collapsedYPosition) { + } else if closestVal == collapsedYPosition { returnPosition = .collapsed } From 1f98402135a5d660cf9944d3d29da006c56b6c41 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 11:12:18 -0700 Subject: [PATCH 45/80] Rename Drawer presentation controller position property to currentPosition --- .../DrawerPresentationController.swift | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 5fbab77f28a2..3512954c7447 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -96,10 +96,10 @@ public class DrawerPresentationController: FancyAlertPresentationController { return frame } - public var position: DrawerPosition = .collapsed + public var currentPosition: DrawerPosition = .collapsed public func transition(to position: DrawerPosition) { - self.position = position + currentPosition = position if position == .closed { dismiss() @@ -259,17 +259,13 @@ private extension DrawerPresentationController { self.setTopMargin(max((startY + yTranslation), maxY), animated: false) case .ended: - /// Helper closure to prevent user transitions + /// Helper closure to prevent user transition/dismiss let transition: (DrawerPosition) -> () = { pos in - if allowsUserTransition { - self.transition(to: pos) - return - } - - if pos == .closed && allowDragToDismiss { + if allowsUserTransition || pos == .closed && allowDragToDismiss { self.transition(to: pos) } else { - self.transition(to: self.position) + //Reset to the original position + self.transition(to: self.currentPosition) } } From 1eb6cb7b1faa69ec893512fbab578ec303217a74 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 12:09:52 -0700 Subject: [PATCH 46/80] Move drawer defaults to an enum --- .../DrawerPresentationController.swift | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 3512954c7447..6f1321a3f16d 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -39,18 +39,34 @@ public protocol DrawerPresentable: AnyObject { typealias UIDrawerPresentable = DrawerPresentable & UIViewController +private enum Constants { + static let transitionDuration: TimeInterval = 0.5 + + static let flickVelocity: CGFloat = 300 + static let bounceAmount: CGFloat = 0.01 + + enum Defaults { + static let expandedHeight: DrawerHeight = .topMargin(20) + static let collapsedHeight: DrawerHeight = .contentHeight(0) + + static let allowsUserTransition: Bool = true + static let allowsTapToDismiss: Bool = true + static let allowsDragToDismiss: Bool = true + } +} + public extension DrawerPresentable where Self: UIViewController { //Default values var allowsUserTransition: Bool { - return true + return Constants.Defaults.allowsUserTransition } var expandedHeight: DrawerHeight { - return .topMargin(20) + return Constants.Defaults.expandedHeight } var collapsedHeight: DrawerHeight { - return .contentHeight(0) + return Constants.Defaults.collapsedHeight } var scrollableView: UIScrollView? { @@ -58,11 +74,11 @@ public extension DrawerPresentable where Self: UIViewController { } var allowsDragToDismiss: Bool { - return true + return Constants.Defaults.allowsDragToDismiss } var allowsTapToDismiss: Bool { - return true + return Constants.Defaults.allowsTapToDismiss } // Helpers @@ -76,13 +92,6 @@ public extension DrawerPresentable where Self: UIViewController { } public class DrawerPresentationController: FancyAlertPresentationController { - private enum Constants { - static let transitionDuration: TimeInterval = 0.5 - static let defaultTopMargin: CGFloat = 20 - static let flickVelocity: CGFloat = 300 - static let bounceAmount: CGFloat = 0.01 - } - override public var frameOfPresentedViewInContainerView: CGRect { guard let containerView = self.containerView else { return .zero @@ -96,8 +105,12 @@ public class DrawerPresentationController: FancyAlertPresentationController { return frame } + /// Returns the current position of the drawer public var currentPosition: DrawerPosition = .collapsed + + /// Animates between the drawer positions + /// - Parameter position: The position to animate to public func transition(to position: DrawerPosition) { currentPosition = position @@ -140,6 +153,8 @@ public class DrawerPresentationController: FancyAlertPresentationController { // MARK: - Internal Positions + // Helpers to calculate the Y positions for the drawer positions + private var closedPosition: CGFloat { guard let presentedView = self.presentedView else { return 0 @@ -149,21 +164,19 @@ public class DrawerPresentationController: FancyAlertPresentationController { } private var collapsedYPosition: CGFloat { - guard let presentableVC = presentableViewController else { - return calculatedTopMargin(for: 0) - } + let height = presentableViewController?.collapsedHeight ?? Constants.Defaults.collapsedHeight - return topMargin(with: presentableVC.collapsedHeight) + return topMargin(with: height) } private var expandedYPosition: CGFloat { - guard let presentableVC = presentableViewController else { - return calculatedTopMargin(for: Constants.defaultTopMargin) - } + let height = presentableViewController?.expandedHeight ?? Constants.Defaults.expandedHeight - return topMargin(with: presentableVC.expandedHeight) + return topMargin(with: height) } + /// Calculates the Y position for the view based on a DrawerHeight enum + /// - Parameter drawerHeight: The drawer height to calculate private func topMargin(with drawerHeight: DrawerHeight) -> CGFloat { var topMargin: CGFloat @@ -213,12 +226,11 @@ private extension DrawerPresentationController { containerView.addGestureRecognizer(tapGestureRecognizer) } + /// Dismiss action for the tap gesture + /// Will prevent dismissal if the `allowsTapToDismiss` is false + /// - Parameter gesture: The tap gesture @objc func dismiss(_ gesture: UIPanGestureRecognizer) { - var canDismiss = true - - if let presentableVC = presentableViewController { - canDismiss = presentableVC.allowsTapToDismiss - } + let canDismiss = presentableViewController?.allowsTapToDismiss ?? Constants.Defaults.allowsTapToDismiss guard canDismiss else { return @@ -231,8 +243,8 @@ private extension DrawerPresentationController { guard let presentedView = self.presentedView else { return } let translation = gesture.translation(in: presentedView) - let allowsUserTransition = presentableViewController?.allowsUserTransition ?? false - let allowDragToDismiss = presentableViewController?.allowsDragToDismiss ?? true + let allowsUserTransition = presentableViewController?.allowsUserTransition ?? Constants.Defaults.allowsUserTransition + let allowDragToDismiss = presentableViewController?.allowsDragToDismiss ?? Constants.Defaults.allowsDragToDismiss switch gesture.state { case .began: @@ -300,7 +312,6 @@ private extension DrawerPresentationController { return } } - } private extension UIScrollView { From 9611e78101175e9db8bff479304579909a980de0 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Wed, 1 Apr 2020 12:45:15 -0700 Subject: [PATCH 47/80] Fix deprecation warnings --- .../BottomSheetViewController.swift | 167 ++++++++++++++++++ .../DrawerPresentationController.swift | 6 +- 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift new file mode 100644 index 000000000000..74d113773b9f --- /dev/null +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -0,0 +1,167 @@ +import UIKit + +class BottomSheetViewController: UIViewController, DrawerPresentable { + // Drawer + var expandedHeight: DrawerHeight { + return self.childViewController?.expandedHeight ?? .maxHeight + } + + var collapsedHeight: DrawerHeight { + return self.childViewController?.collapsedHeight ?? .contentHeight(200) + } + + var scrollableView: UIScrollView? { + return self.childViewController?.scrollableView + } + + enum Constants { + static let gripHeight: CGFloat = 5 + static let cornerRadius: CGFloat = 8 + static let buttonSpacing: CGFloat = 8 + static let additionalSafeAreaInsetsRegular: UIEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0) + static let minimumWidth: CGFloat = 300 + + enum Header { + static let spacing: CGFloat = 16 + static let insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18) + } + + enum Button { + static let height: CGFloat = 54 + static let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 35) + static let titleInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) + static let imageTintColor: UIColor = .neutral(.shade30) + static let font: UIFont = .preferredFont(forTextStyle: .callout) + static let textColor: UIColor = .text + } + + enum Stack { + static let insets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0) + } + } + + private weak var childViewController: UIDrawerPresentable? + + init(childViewController: UIDrawerPresentable) { + self.childViewController = childViewController + super.init(nibName: nil, bundle: nil) + } + + func show(from presenting: UIViewController, sourceView: UIView? = nil) { + if UIDevice.isPad() { + modalPresentationStyle = .popover + popoverPresentationController?.sourceView = sourceView ?? UIView() + popoverPresentationController?.sourceRect = sourceView?.bounds ?? .zero + } else { + transitioningDelegate = self + modalPresentationStyle = .custom + } + + presenting.present(self, animated: true) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var gripButton: UIButton = { + let button = GripButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) + return button + }() + + @objc func buttonPressed() { + dismiss(animated: true, completion: nil) + } + + override func viewDidLoad() { + super.viewDidLoad() + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + + view.clipsToBounds = true + view.layer.cornerRadius = Constants.cornerRadius + view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + view.backgroundColor = .basicBackground + + NSLayoutConstraint.activate([ + gripButton.heightAnchor.constraint(equalToConstant: Constants.gripHeight) + ]) + + guard let childViewController = childViewController else { + return + } + + addChild(childViewController) + + let stackView = UIStackView(arrangedSubviews: [ + gripButton, + childViewController.view + ]) + + stackView.setCustomSpacing(Constants.Header.spacing, after: gripButton) + + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + + refreshForTraits() + + view.addSubview(stackView) + view.pinSubviewToSafeArea(stackView, insets: Constants.Stack.insets) + + childViewController.didMove(toParent: self) + } + + open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + refreshForTraits() + } + + private func refreshForTraits() { + if presentingViewController?.traitCollection.horizontalSizeClass == .regular && presentingViewController?.traitCollection.verticalSizeClass != .compact { + gripButton.isHidden = true + additionalSafeAreaInsets = Constants.additionalSafeAreaInsetsRegular + } else { + gripButton.isHidden = false + additionalSafeAreaInsets = .zero + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + return preferredContentSize = CGSize(width: Constants.minimumWidth, height: view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height) + } + + var presentedVC: DrawerPresentationController? { + guard let navController = self.navigationController else { + return presentationController as? DrawerPresentationController + } + + return navController.presentationController as? DrawerPresentationController + } + + @objc func keyboardWillShow(_ notification: NSNotification) { + self.presentedVC?.transition(to: .expanded) + } + + @objc func keyboardWillHide(_ notification: NSNotification) { + self.presentedVC?.transition(to: .collapsed) + } +} + +extension BottomSheetViewController: UIViewControllerTransitioningDelegate { + public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return BottomSheetAnimationController(transitionType: .presenting) + } + + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return BottomSheetAnimationController(transitionType: .dismissing) + } + + public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return DrawerPresentationController(presentedViewController: presented, presenting: presenting) + } +} diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 6f1321a3f16d..de36f445bed7 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -352,7 +352,7 @@ private extension DrawerPresentationController { else { return } - let bottom = presentingViewController.bottomLayoutGuide.length + let bottom = presentingViewController.view.safeAreaLayoutGuide.layoutFrame.origin.y let margin = presentedView.frame.origin.y + bottom /** @@ -435,6 +435,8 @@ private extension DrawerPresentationController { } private var rootViewController: UIViewController? { - return UIApplication.shared.keyWindow?.rootViewController + let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first + + return keyWindow?.rootViewController } } From 7d4763d008e51de0df13b8e911e913b52a16e266 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Thu, 2 Apr 2020 08:38:42 -0700 Subject: [PATCH 48/80] Fix an issue when determining which presentationController to return --- .../BottomSheetViewController.swift | 8 ------- .../DrawerPresentationController.swift | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index 74d113773b9f..9e3b10f149b6 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -135,14 +135,6 @@ class BottomSheetViewController: UIViewController, DrawerPresentable { return preferredContentSize = CGSize(width: Constants.minimumWidth, height: view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height) } - var presentedVC: DrawerPresentationController? { - guard let navController = self.navigationController else { - return presentationController as? DrawerPresentationController - } - - return navController.presentationController as? DrawerPresentationController - } - @objc func keyboardWillShow(_ notification: NSNotification) { self.presentedVC?.transition(to: .expanded) } diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index de36f445bed7..3f9a08c544dd 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -56,7 +56,7 @@ private enum Constants { } public extension DrawerPresentable where Self: UIViewController { - //Default values + // Default values var allowsUserTransition: Bool { return Constants.Defaults.allowsUserTransition } @@ -82,12 +82,22 @@ public extension DrawerPresentable where Self: UIViewController { } // Helpers + + /// Try to determine the correct DrawerPresentationController to use + + /// Returns the `DrawerPresentationController` for a view controller if there is one + /// This tries to determine the correct one to use in the following order: + /// - The view controller + /// - The navController + /// - The navController parentViewController + /// - The views parentViewController var presentedVC: DrawerPresentationController? { - guard let navController = self.navigationController else { - return presentationController as? DrawerPresentationController - } + let presentationController = self.presentationController as? DrawerPresentationController + let navigationPresentationController = navigationController?.presentationController as? DrawerPresentationController + let navParentPresetationController = navigationController?.parent?.presentationController as? DrawerPresentationController + let parentPresentationController = parent?.presentationController as? DrawerPresentationController - return navController.presentationController as? DrawerPresentationController + return presentationController ?? navigationPresentationController ?? navParentPresetationController ?? parentPresentationController } } @@ -315,9 +325,7 @@ private extension DrawerPresentationController { } private extension UIScrollView { - /** - A flag to determine if a scroll view is scrolling - */ + /// A flag to determine if a scroll view is scrolling var isScrolling: Bool { return isDragging && !isDecelerating || isTracking } From fae87c1ae3f6828c19f7cd7ee2b051356782fafe Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Thu, 2 Apr 2020 09:47:32 -0700 Subject: [PATCH 49/80] Move DrawerPresentable options to extensions --- .../BottomSheetViewController.swift | 30 ++--- .../PrepublishingNavigationController.swift | 118 ++++++++++++++++++ 2 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index 9e3b10f149b6..2dc74a6c61c1 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -1,19 +1,6 @@ import UIKit -class BottomSheetViewController: UIViewController, DrawerPresentable { - // Drawer - var expandedHeight: DrawerHeight { - return self.childViewController?.expandedHeight ?? .maxHeight - } - - var collapsedHeight: DrawerHeight { - return self.childViewController?.collapsedHeight ?? .contentHeight(200) - } - - var scrollableView: UIScrollView? { - return self.childViewController?.scrollableView - } - +class BottomSheetViewController: UIViewController { enum Constants { static let gripHeight: CGFloat = 5 static let cornerRadius: CGFloat = 8 @@ -157,3 +144,18 @@ extension BottomSheetViewController: UIViewControllerTransitioningDelegate { return DrawerPresentationController(presentedViewController: presented, presenting: presenting) } } + +// MARK: - DrawerDelegate +extension BottomSheetViewController: DrawerPresentable { + var expandedHeight: DrawerHeight { + return childViewController?.expandedHeight ?? .maxHeight + } + + var collapsedHeight: DrawerHeight { + return childViewController?.collapsedHeight ?? .contentHeight(200) + } + + var scrollableView: UIScrollView? { + return childViewController?.scrollableView + } +} diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift new file mode 100644 index 000000000000..362f789f0b76 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift @@ -0,0 +1,118 @@ +import UIKit + + +class PrepublishingNavigationController: UINavigationController { + + lazy var header: PrepublishingHeaderView = { + let header = PrepublishingHeaderView.loadFromNib() + header.translatesAutoresizingMaskIntoConstraints = false + header.delegate = self + return header + }() + + lazy var blog: Blog? = { + return (viewControllers.first { $0 is PrepublishingViewController } as? PrepublishingViewController)?.post.blog + }() + + // In iOS 13+ we need to take into account the navigationBar frame height + // iOS 12 or 11 that's not needed + lazy var insets: UIEdgeInsets = { + var top: CGFloat = 0 + if #available(iOS 13, *) { + top = Constants.navigationHeaderHeight - navigationBar.frame.height + } else { + top = Constants.navigationHeaderHeight + } + return UIEdgeInsets(top: top, left: 0, bottom: 0, right: 0) + }() + + override func viewDidLoad() { + super.viewDidLoad() + + delegate = self + + configureNavigationHeader() + configureNavigationBar() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + guard let blog = blog else { + return + } + + // Configure the header + header.configure(blog) + } + + private func configureNavigationBar() { + if #available(iOS 13.0, *) { + let appearance = UINavigationBarAppearance() + appearance.configureWithTransparentBackground() + navigationBar.standardAppearance = appearance + } else { + let clearImage = UIImage(color: .clear, havingSize: CGSize(width: 1, height: 1)) + navigationBar.shadowImage = clearImage + } + } + + private func configureNavigationHeader() { + view.addSubview(header) + + // Put our custom navigation in front of the current navigation + NSLayoutConstraint.activate([ + header.topAnchor.constraint(equalTo: view.topAnchor), + header.leftAnchor.constraint(equalTo: navigationBar.leftAnchor), + header.rightAnchor.constraint(equalTo: navigationBar.rightAnchor), + header.heightAnchor.constraint(equalToConstant: Constants.navigationHeaderHeight) + ]) + additionalSafeAreaInsets = insets + } + + private enum Constants { + static let navigationHeaderHeight: CGFloat = 80 + } +} + +// MARK: - UINavigationControllerDelegate + +extension PrepublishingNavigationController: UINavigationControllerDelegate { + + /// Animated the back button based on what View Controller will be shown + func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { + transitionCoordinator?.animate(alongsideTransition: { context in + viewController is PrepublishingViewController ? self.header.hideBackButton() : self.header.showBackButton() + + self.header.setTitle(viewController.title, transitionDuration: context.transitionDuration) + }, completion: nil) + } +} + +// MARK: - PrepublishingHeaderViewDelegate + +extension PrepublishingNavigationController: PrepublishingHeaderViewDelegate { + + /// Pop the current view controller when Back button is pressed + func backButtonTapped() { + popViewController(animated: true) + } +} + +// MARK: - DrawerPresentable + +extension PrepublishingNavigationController: DrawerPresentable { + var expandedHeight: DrawerHeight { + return .maxHeight + } + + var collapsedHeight: DrawerHeight { + return .contentHeight(250) + } + + var scrollableView: UIScrollView? { + let scroll = visibleViewController?.view as? UIScrollView + + return scroll + } +} From e51d7d80aebcb40bb8e118badfc5c70ab314cfb0 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Thu, 2 Apr 2020 09:48:06 -0700 Subject: [PATCH 50/80] Clean up self references --- .../System/Action Sheet/DrawerPresentationController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 3f9a08c544dd..3f899773f2ae 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -146,7 +146,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { } @objc func dismiss() { - self.presentedViewController.dismiss(animated: true, completion: nil) + presentedViewController.dismiss(animated: true, completion: nil) } public override func presentationTransitionWillBegin() { @@ -278,7 +278,7 @@ private extension DrawerPresentationController { let maxY = topMargin(with: .maxHeight) - self.setTopMargin(max((startY + yTranslation), maxY), animated: false) + setTopMargin(max((startY + yTranslation), maxY), animated: false) case .ended: /// Helper closure to prevent user transition/dismiss From 1cb0e8a2fb1224e806fa2d5d1a770a54bf9fb1a6 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Thu, 2 Apr 2020 09:55:47 -0700 Subject: [PATCH 51/80] Rename the UIDrawerPresentable type to DrawerPresentableViewController --- .../Utility/Bottom Sheet/BottomSheetViewController.swift | 4 ++-- .../System/Action Sheet/DrawerPresentationController.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index 2dc74a6c61c1..d758723234c9 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -27,9 +27,9 @@ class BottomSheetViewController: UIViewController { } } - private weak var childViewController: UIDrawerPresentable? + private weak var childViewController: DrawerPresentableViewController? - init(childViewController: UIDrawerPresentable) { + init(childViewController: DrawerPresentableViewController) { self.childViewController = childViewController super.init(nibName: nil, bundle: nil) } diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 3f899773f2ae..6e3a7ec739d2 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -37,8 +37,6 @@ public protocol DrawerPresentable: AnyObject { var scrollableView: UIScrollView? { get } } -typealias UIDrawerPresentable = DrawerPresentable & UIViewController - private enum Constants { static let transitionDuration: TimeInterval = 0.5 @@ -55,6 +53,8 @@ private enum Constants { } } +typealias DrawerPresentableViewController = DrawerPresentable & UIViewController + public extension DrawerPresentable where Self: UIViewController { // Default values var allowsUserTransition: Bool { From b13670aacb845f05b5c5f51ac7f04c54ccdd4fe4 Mon Sep 17 00:00:00 2001 From: Emily Laguna Date: Thu, 2 Apr 2020 09:57:28 -0700 Subject: [PATCH 52/80] Update DrawerPresentationController comments --- .../Action Sheet/DrawerPresentationController.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 6e3a7ec739d2..940b1ff97364 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -10,7 +10,7 @@ public enum DrawerHeight { // The maximum height for the screen case maxHeight - //Height is based on the specified margin from the top of the screen + // Height is based on the specified margin from the top of the screen case topMargin(CGFloat) // Height will be equal to the the content height value @@ -363,13 +363,6 @@ private extension DrawerPresentationController { let bottom = presentingViewController.view.safeAreaLayoutGuide.layoutFrame.origin.y let margin = presentedView.frame.origin.y + bottom - /** - Disable vertical scroll indicator until we start to scroll - to avoid visual bugs - */ -// scrollView.showsVerticalScrollIndicator = false -// scrollView.scrollIndicatorInsets = .zero -// scrollView.contentInset.bottom = margin } @@ -385,7 +378,7 @@ private extension DrawerPresentationController { let bounds = containerView.bounds let margin = bounds.maxY - (safeAreaInsets.bottom + ((height > 0) ? height : (bounds.height * 0.5))) - //Limit the max height + // Limit the max height return max(margin, safeAreaInsets.top) } From 6dec4f0bc2b037edcae973dbaa7edefbdf081ea3 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Mon, 6 Apr 2020 09:54:59 -0300 Subject: [PATCH 53/80] Adapt the Bottom Sheet for landscape mode --- .../DrawerPresentationController.swift | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 940b1ff97364..a910eae7c132 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -43,6 +43,8 @@ private enum Constants { static let flickVelocity: CGFloat = 300 static let bounceAmount: CGFloat = 0.01 + static let maxWidthPercentage: CGFloat = 0.66 /// Used to constrain the width to a smaller size (instead of full width) when sheet is too wide + enum Defaults { static let expandedHeight: DrawerHeight = .topMargin(20) static let collapsedHeight: DrawerHeight = .contentHeight(0) @@ -109,10 +111,26 @@ public class DrawerPresentationController: FancyAlertPresentationController { var frame = containerView.frame let y = collapsedYPosition + var width: CGFloat = containerView.bounds.width - (containerView.safeAreaInsets.left + containerView.safeAreaInsets.right) frame.origin.y = y - return frame + /// If we're in a compact vertical size class, constrain the width a bit more so it doesn't get overly wide. + if traitCollection.verticalSizeClass == .compact { + width = width * Constants.maxWidthPercentage + } + + /// If we constrain the width, this centers the view by applying the appropriate insets based on width + let leftInset: CGFloat = ((containerView.bounds.width - width) / 2) + + return CGRect(x: leftInset, y: frame.origin.y, width: width, height: frame.height) + } + + override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + coordinator.animate(alongsideTransition: { _ in + self.presentedView?.frame = self.frameOfPresentedViewInContainerView + }, completion: nil) + super.viewWillTransition(to: size, with: coordinator) } /// Returns the current position of the drawer @@ -155,6 +173,11 @@ public class DrawerPresentationController: FancyAlertPresentationController { configureScrollViewInsets() } + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + transition(to: currentPosition) + } + public override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) From ff9ba098f443ba3984aa2fbcb76400abc8a50ff9 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Mon, 6 Apr 2020 14:13:02 -0300 Subject: [PATCH 54/80] When going portrait/landscape keep the current position --- .../System/Action Sheet/DrawerPresentationController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index a910eae7c132..4b53e3e7c61e 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -129,6 +129,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { coordinator.animate(alongsideTransition: { _ in self.presentedView?.frame = self.frameOfPresentedViewInContainerView + self.transition(to: self.currentPosition) }, completion: nil) super.viewWillTransition(to: size, with: coordinator) } From 5d4c5deafd0fce832c43bc3a00cfee9750d9eac2 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Mon, 6 Apr 2020 16:46:00 -0300 Subject: [PATCH 55/80] Make the width configurable --- .../BottomSheetViewController.swift | 4 +++ .../DrawerPresentationController.swift | 34 ++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index d758723234c9..0b8fe7c7618e 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -147,6 +147,10 @@ extension BottomSheetViewController: UIViewControllerTransitioningDelegate { // MARK: - DrawerDelegate extension BottomSheetViewController: DrawerPresentable { + var width: DrawerWidth { + childViewController?.width ?? .percentage(0.66) + } + var expandedHeight: DrawerHeight { return childViewController?.expandedHeight ?? .maxHeight } diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 4b53e3e7c61e..4d7894f8b4eb 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -17,6 +17,17 @@ public enum DrawerHeight { case contentHeight(CGFloat) } +public enum DrawerWidth { + // Fills the whole screen width + case maxWidth + + // When in compact mode, fills a percentage of the screen + case percentage(CGFloat) + + // Width will be equal to the the content height value + case contentWidth(CGFloat) +} + public protocol DrawerPresentable: AnyObject { /// The height of the drawer when it's in the expanded position var expandedHeight: DrawerHeight { get } @@ -24,6 +35,9 @@ public protocol DrawerPresentable: AnyObject { /// The height of the drawer when it's in the collapsed position var collapsedHeight: DrawerHeight { get } + /// The width of the Drawer in compact screen + var width: DrawerWidth { get } + /// Whether or not the user is allowed to swipe to switch between the expanded and collapsed position var allowsUserTransition: Bool { get } @@ -43,11 +57,10 @@ private enum Constants { static let flickVelocity: CGFloat = 300 static let bounceAmount: CGFloat = 0.01 - static let maxWidthPercentage: CGFloat = 0.66 /// Used to constrain the width to a smaller size (instead of full width) when sheet is too wide - enum Defaults { static let expandedHeight: DrawerHeight = .topMargin(20) static let collapsedHeight: DrawerHeight = .contentHeight(0) + static let compactWidth: DrawerWidth = .percentage(0.66) static let allowsUserTransition: Bool = true static let allowsTapToDismiss: Bool = true @@ -71,6 +84,10 @@ public extension DrawerPresentable where Self: UIViewController { return Constants.Defaults.collapsedHeight } + var width: DrawerWidth { + return Constants.Defaults.compactWidth + } + var scrollableView: UIScrollView? { return nil } @@ -116,8 +133,17 @@ public class DrawerPresentationController: FancyAlertPresentationController { frame.origin.y = y /// If we're in a compact vertical size class, constrain the width a bit more so it doesn't get overly wide. - if traitCollection.verticalSizeClass == .compact { - width = width * Constants.maxWidthPercentage + if let widthForCompactSizeClass = presentableViewController?.width, + traitCollection.verticalSizeClass == .compact { + + switch widthForCompactSizeClass { + case .percentage(let percentage): + width = width * percentage + case .contentWidth(let givenWidth): + width = givenWidth + case .maxWidth: + break + } } /// If we constrain the width, this centers the view by applying the appropriate insets based on width From 95a2431d4226f2d2b18002a3c62d931139257093 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Mon, 6 Apr 2020 18:09:31 -0300 Subject: [PATCH 56/80] Rename width to compactWidth --- .../Utility/Bottom Sheet/BottomSheetViewController.swift | 4 ++-- .../System/Action Sheet/DrawerPresentationController.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index 0b8fe7c7618e..d33b3c20d4ab 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -147,8 +147,8 @@ extension BottomSheetViewController: UIViewControllerTransitioningDelegate { // MARK: - DrawerDelegate extension BottomSheetViewController: DrawerPresentable { - var width: DrawerWidth { - childViewController?.width ?? .percentage(0.66) + var compactWidth: DrawerWidth { + childViewController?.compactWidth ?? .percentage(0.66) } var expandedHeight: DrawerHeight { diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 4d7894f8b4eb..fb6649af0f22 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -36,7 +36,7 @@ public protocol DrawerPresentable: AnyObject { var collapsedHeight: DrawerHeight { get } /// The width of the Drawer in compact screen - var width: DrawerWidth { get } + var compactWidth: DrawerWidth { get } /// Whether or not the user is allowed to swipe to switch between the expanded and collapsed position var allowsUserTransition: Bool { get } @@ -84,7 +84,7 @@ public extension DrawerPresentable where Self: UIViewController { return Constants.Defaults.collapsedHeight } - var width: DrawerWidth { + var compactWidth: DrawerWidth { return Constants.Defaults.compactWidth } @@ -133,7 +133,7 @@ public class DrawerPresentationController: FancyAlertPresentationController { frame.origin.y = y /// If we're in a compact vertical size class, constrain the width a bit more so it doesn't get overly wide. - if let widthForCompactSizeClass = presentableViewController?.width, + if let widthForCompactSizeClass = presentableViewController?.compactWidth, traitCollection.verticalSizeClass == .compact { switch widthForCompactSizeClass { From 78d2cde3d5290495ab821c0451e450ee6ad3a130 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Tue, 7 Apr 2020 10:23:17 -0300 Subject: [PATCH 57/80] Just change frame properties instead of creating a new one --- .../System/Action Sheet/DrawerPresentationController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index fb6649af0f22..256dbe75274b 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -145,11 +145,12 @@ public class DrawerPresentationController: FancyAlertPresentationController { break } } + frame.size.width = width /// If we constrain the width, this centers the view by applying the appropriate insets based on width - let leftInset: CGFloat = ((containerView.bounds.width - width) / 2) + frame.origin.x = ((containerView.bounds.width - width) / 2) - return CGRect(x: leftInset, y: frame.origin.y, width: width, height: frame.height) + return frame } override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { From 6d51500db67ad43827b4c35559a9676bd361135e Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Tue, 7 Apr 2020 14:03:54 -0300 Subject: [PATCH 58/80] Remove files that aren't needed for now --- .../BottomSheetViewController.swift | 165 ------------------ .../PrepublishingNavigationController.swift | 118 ------------- 2 files changed, 283 deletions(-) delete mode 100644 WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift delete mode 100644 WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift deleted file mode 100644 index d33b3c20d4ab..000000000000 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ /dev/null @@ -1,165 +0,0 @@ -import UIKit - -class BottomSheetViewController: UIViewController { - enum Constants { - static let gripHeight: CGFloat = 5 - static let cornerRadius: CGFloat = 8 - static let buttonSpacing: CGFloat = 8 - static let additionalSafeAreaInsetsRegular: UIEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0) - static let minimumWidth: CGFloat = 300 - - enum Header { - static let spacing: CGFloat = 16 - static let insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18) - } - - enum Button { - static let height: CGFloat = 54 - static let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 35) - static let titleInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) - static let imageTintColor: UIColor = .neutral(.shade30) - static let font: UIFont = .preferredFont(forTextStyle: .callout) - static let textColor: UIColor = .text - } - - enum Stack { - static let insets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0) - } - } - - private weak var childViewController: DrawerPresentableViewController? - - init(childViewController: DrawerPresentableViewController) { - self.childViewController = childViewController - super.init(nibName: nil, bundle: nil) - } - - func show(from presenting: UIViewController, sourceView: UIView? = nil) { - if UIDevice.isPad() { - modalPresentationStyle = .popover - popoverPresentationController?.sourceView = sourceView ?? UIView() - popoverPresentationController?.sourceRect = sourceView?.bounds ?? .zero - } else { - transitioningDelegate = self - modalPresentationStyle = .custom - } - - presenting.present(self, animated: true) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private var gripButton: UIButton = { - let button = GripButton() - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) - return button - }() - - @objc func buttonPressed() { - dismiss(animated: true, completion: nil) - } - - override func viewDidLoad() { - super.viewDidLoad() - - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) - - view.clipsToBounds = true - view.layer.cornerRadius = Constants.cornerRadius - view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] - view.backgroundColor = .basicBackground - - NSLayoutConstraint.activate([ - gripButton.heightAnchor.constraint(equalToConstant: Constants.gripHeight) - ]) - - guard let childViewController = childViewController else { - return - } - - addChild(childViewController) - - let stackView = UIStackView(arrangedSubviews: [ - gripButton, - childViewController.view - ]) - - stackView.setCustomSpacing(Constants.Header.spacing, after: gripButton) - - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .vertical - - refreshForTraits() - - view.addSubview(stackView) - view.pinSubviewToSafeArea(stackView, insets: Constants.Stack.insets) - - childViewController.didMove(toParent: self) - } - - open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - refreshForTraits() - } - - private func refreshForTraits() { - if presentingViewController?.traitCollection.horizontalSizeClass == .regular && presentingViewController?.traitCollection.verticalSizeClass != .compact { - gripButton.isHidden = true - additionalSafeAreaInsets = Constants.additionalSafeAreaInsetsRegular - } else { - gripButton.isHidden = false - additionalSafeAreaInsets = .zero - } - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - return preferredContentSize = CGSize(width: Constants.minimumWidth, height: view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height) - } - - @objc func keyboardWillShow(_ notification: NSNotification) { - self.presentedVC?.transition(to: .expanded) - } - - @objc func keyboardWillHide(_ notification: NSNotification) { - self.presentedVC?.transition(to: .collapsed) - } -} - -extension BottomSheetViewController: UIViewControllerTransitioningDelegate { - public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return BottomSheetAnimationController(transitionType: .presenting) - } - - public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return BottomSheetAnimationController(transitionType: .dismissing) - } - - public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return DrawerPresentationController(presentedViewController: presented, presenting: presenting) - } -} - -// MARK: - DrawerDelegate -extension BottomSheetViewController: DrawerPresentable { - var compactWidth: DrawerWidth { - childViewController?.compactWidth ?? .percentage(0.66) - } - - var expandedHeight: DrawerHeight { - return childViewController?.expandedHeight ?? .maxHeight - } - - var collapsedHeight: DrawerHeight { - return childViewController?.collapsedHeight ?? .contentHeight(200) - } - - var scrollableView: UIScrollView? { - return childViewController?.scrollableView - } -} diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift deleted file mode 100644 index 362f789f0b76..000000000000 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift +++ /dev/null @@ -1,118 +0,0 @@ -import UIKit - - -class PrepublishingNavigationController: UINavigationController { - - lazy var header: PrepublishingHeaderView = { - let header = PrepublishingHeaderView.loadFromNib() - header.translatesAutoresizingMaskIntoConstraints = false - header.delegate = self - return header - }() - - lazy var blog: Blog? = { - return (viewControllers.first { $0 is PrepublishingViewController } as? PrepublishingViewController)?.post.blog - }() - - // In iOS 13+ we need to take into account the navigationBar frame height - // iOS 12 or 11 that's not needed - lazy var insets: UIEdgeInsets = { - var top: CGFloat = 0 - if #available(iOS 13, *) { - top = Constants.navigationHeaderHeight - navigationBar.frame.height - } else { - top = Constants.navigationHeaderHeight - } - return UIEdgeInsets(top: top, left: 0, bottom: 0, right: 0) - }() - - override func viewDidLoad() { - super.viewDidLoad() - - delegate = self - - configureNavigationHeader() - configureNavigationBar() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - guard let blog = blog else { - return - } - - // Configure the header - header.configure(blog) - } - - private func configureNavigationBar() { - if #available(iOS 13.0, *) { - let appearance = UINavigationBarAppearance() - appearance.configureWithTransparentBackground() - navigationBar.standardAppearance = appearance - } else { - let clearImage = UIImage(color: .clear, havingSize: CGSize(width: 1, height: 1)) - navigationBar.shadowImage = clearImage - } - } - - private func configureNavigationHeader() { - view.addSubview(header) - - // Put our custom navigation in front of the current navigation - NSLayoutConstraint.activate([ - header.topAnchor.constraint(equalTo: view.topAnchor), - header.leftAnchor.constraint(equalTo: navigationBar.leftAnchor), - header.rightAnchor.constraint(equalTo: navigationBar.rightAnchor), - header.heightAnchor.constraint(equalToConstant: Constants.navigationHeaderHeight) - ]) - additionalSafeAreaInsets = insets - } - - private enum Constants { - static let navigationHeaderHeight: CGFloat = 80 - } -} - -// MARK: - UINavigationControllerDelegate - -extension PrepublishingNavigationController: UINavigationControllerDelegate { - - /// Animated the back button based on what View Controller will be shown - func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { - transitionCoordinator?.animate(alongsideTransition: { context in - viewController is PrepublishingViewController ? self.header.hideBackButton() : self.header.showBackButton() - - self.header.setTitle(viewController.title, transitionDuration: context.transitionDuration) - }, completion: nil) - } -} - -// MARK: - PrepublishingHeaderViewDelegate - -extension PrepublishingNavigationController: PrepublishingHeaderViewDelegate { - - /// Pop the current view controller when Back button is pressed - func backButtonTapped() { - popViewController(animated: true) - } -} - -// MARK: - DrawerPresentable - -extension PrepublishingNavigationController: DrawerPresentable { - var expandedHeight: DrawerHeight { - return .maxHeight - } - - var collapsedHeight: DrawerHeight { - return .contentHeight(250) - } - - var scrollableView: UIScrollView? { - let scroll = visibleViewController?.view as? UIScrollView - - return scroll - } -} From 5bf409efd368ee4638fc9057eabf607a3a2711b7 Mon Sep 17 00:00:00 2001 From: Leandro Alonso Date: Tue, 7 Apr 2020 14:32:02 -0300 Subject: [PATCH 59/80] Adds BottomSheetViewController --- .../BottomSheetViewController.swift | 165 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++ 2 files changed, 177 insertions(+) create mode 100644 WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift new file mode 100644 index 000000000000..d33b3c20d4ab --- /dev/null +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -0,0 +1,165 @@ +import UIKit + +class BottomSheetViewController: UIViewController { + enum Constants { + static let gripHeight: CGFloat = 5 + static let cornerRadius: CGFloat = 8 + static let buttonSpacing: CGFloat = 8 + static let additionalSafeAreaInsetsRegular: UIEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0) + static let minimumWidth: CGFloat = 300 + + enum Header { + static let spacing: CGFloat = 16 + static let insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18) + } + + enum Button { + static let height: CGFloat = 54 + static let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 35) + static let titleInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) + static let imageTintColor: UIColor = .neutral(.shade30) + static let font: UIFont = .preferredFont(forTextStyle: .callout) + static let textColor: UIColor = .text + } + + enum Stack { + static let insets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0) + } + } + + private weak var childViewController: DrawerPresentableViewController? + + init(childViewController: DrawerPresentableViewController) { + self.childViewController = childViewController + super.init(nibName: nil, bundle: nil) + } + + func show(from presenting: UIViewController, sourceView: UIView? = nil) { + if UIDevice.isPad() { + modalPresentationStyle = .popover + popoverPresentationController?.sourceView = sourceView ?? UIView() + popoverPresentationController?.sourceRect = sourceView?.bounds ?? .zero + } else { + transitioningDelegate = self + modalPresentationStyle = .custom + } + + presenting.present(self, animated: true) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var gripButton: UIButton = { + let button = GripButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) + return button + }() + + @objc func buttonPressed() { + dismiss(animated: true, completion: nil) + } + + override func viewDidLoad() { + super.viewDidLoad() + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + + view.clipsToBounds = true + view.layer.cornerRadius = Constants.cornerRadius + view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + view.backgroundColor = .basicBackground + + NSLayoutConstraint.activate([ + gripButton.heightAnchor.constraint(equalToConstant: Constants.gripHeight) + ]) + + guard let childViewController = childViewController else { + return + } + + addChild(childViewController) + + let stackView = UIStackView(arrangedSubviews: [ + gripButton, + childViewController.view + ]) + + stackView.setCustomSpacing(Constants.Header.spacing, after: gripButton) + + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + + refreshForTraits() + + view.addSubview(stackView) + view.pinSubviewToSafeArea(stackView, insets: Constants.Stack.insets) + + childViewController.didMove(toParent: self) + } + + open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + refreshForTraits() + } + + private func refreshForTraits() { + if presentingViewController?.traitCollection.horizontalSizeClass == .regular && presentingViewController?.traitCollection.verticalSizeClass != .compact { + gripButton.isHidden = true + additionalSafeAreaInsets = Constants.additionalSafeAreaInsetsRegular + } else { + gripButton.isHidden = false + additionalSafeAreaInsets = .zero + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + return preferredContentSize = CGSize(width: Constants.minimumWidth, height: view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height) + } + + @objc func keyboardWillShow(_ notification: NSNotification) { + self.presentedVC?.transition(to: .expanded) + } + + @objc func keyboardWillHide(_ notification: NSNotification) { + self.presentedVC?.transition(to: .collapsed) + } +} + +extension BottomSheetViewController: UIViewControllerTransitioningDelegate { + public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return BottomSheetAnimationController(transitionType: .presenting) + } + + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return BottomSheetAnimationController(transitionType: .dismissing) + } + + public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return DrawerPresentationController(presentedViewController: presented, presenting: presenting) + } +} + +// MARK: - DrawerDelegate +extension BottomSheetViewController: DrawerPresentable { + var compactWidth: DrawerWidth { + childViewController?.compactWidth ?? .percentage(0.66) + } + + var expandedHeight: DrawerHeight { + return childViewController?.expandedHeight ?? .maxHeight + } + + var collapsedHeight: DrawerHeight { + return childViewController?.collapsedHeight ?? .contentHeight(200) + } + + var scrollableView: UIScrollView? { + return childViewController?.scrollableView + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 6c69a193d9f3..ea7ee959b2bf 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1049,6 +1049,7 @@ 85F8E19D1B018698000859BB /* PushAuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F8E19C1B018698000859BB /* PushAuthenticationServiceTests.swift */; }; 8B05D29123A9417E0063B9AA /* WPMediaEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B05D29023A9417E0063B9AA /* WPMediaEditor.swift */; }; 8B05D29323AA572A0063B9AA /* GutenbergMediaEditorImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B05D29223AA572A0063B9AA /* GutenbergMediaEditorImage.swift */; }; + 8B158B2A243CF07F00C66823 /* BottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B158B29243CF07F00C66823 /* BottomSheetViewController.swift */; }; 8B3DECAB2388506400A459C2 /* SentryStartupEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */; }; 8B6EA62323FDE50B004BA312 /* PostServiceUploadingList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6EA62223FDE50B004BA312 /* PostServiceUploadingList.swift */; }; 8B7623382384373E00AB3EE7 /* PageListViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7623372384373E00AB3EE7 /* PageListViewControllerTests.swift */; }; @@ -3435,6 +3436,7 @@ 85F8E19C1B018698000859BB /* PushAuthenticationServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushAuthenticationServiceTests.swift; sourceTree = ""; }; 8B05D29023A9417E0063B9AA /* WPMediaEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPMediaEditor.swift; sourceTree = ""; }; 8B05D29223AA572A0063B9AA /* GutenbergMediaEditorImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergMediaEditorImage.swift; sourceTree = ""; }; + 8B158B29243CF07F00C66823 /* BottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetViewController.swift; sourceTree = ""; }; 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStartupEvent.swift; sourceTree = ""; }; 8B6EA62223FDE50B004BA312 /* PostServiceUploadingList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostServiceUploadingList.swift; sourceTree = ""; }; 8B7623372384373E00AB3EE7 /* PageListViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListViewControllerTests.swift; sourceTree = ""; }; @@ -7400,6 +7402,7 @@ 8584FDB4192437160019C02E /* Utility */ = { isa = PBXGroup; children = ( + 8B158B28243CF05900C66823 /* Bottom Sheet */, 1A433B1B2254CBC300AE7910 /* Networking */, 40247E002120FE2300AE1C3C /* Automated Transfer */, F198533821ADAA4E00DCDAE7 /* Editor */, @@ -7538,6 +7541,14 @@ name = Networking; sourceTree = ""; }; + 8B158B28243CF05900C66823 /* Bottom Sheet */ = { + isa = PBXGroup; + children = ( + 8B158B29243CF07F00C66823 /* BottomSheetViewController.swift */, + ); + path = "Bottom Sheet"; + sourceTree = ""; + }; 8B7623352384372200AB3EE7 /* Pages */ = { isa = PBXGroup; children = ( @@ -11995,6 +12006,7 @@ 4054F4562214E3C800D261AB /* TagsCategoriesStatsRecordValue+CoreDataClass.swift in Sources */, E11000991CDB5F1E00E33887 /* KeychainTools.swift in Sources */, D800D87020998A7300E7C7E5 /* SavedForLaterMenuItemCreator.swift in Sources */, + 8B158B2A243CF07F00C66823 /* BottomSheetViewController.swift in Sources */, E6D2E1671B8AAD8C0000ED14 /* ReaderTagStreamHeader.swift in Sources */, 321E292623A5F10900588610 /* FullScreenCommentReplyViewController.swift in Sources */, E6D3B1431D1C702600008D4B /* ReaderFollowedSitesViewController.swift in Sources */, From 6d2ecc69ff4fba7b1d422807047397646524f945 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 14:57:36 -0300 Subject: [PATCH 60/80] Simplifies two calls. --- .../Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift | 2 +- WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 6b3066c2c538..719623373898 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -62,7 +62,7 @@ class EditorMediaUtility { if url.isFileURL { request = URLRequest(url: url) - } else if post.blog.isPrivateAtWPCom() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { + } else if post.isPrivateAtWPCom() && PrivateSiteURLProtocol.urlGoes(toWPComSite: url) { // private wpcom image needs special handling. // the size that WPImageHelper expects is pixel size size.width = size.width * scale diff --git a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift index 9b02d9576738..47385b2a2247 100644 --- a/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift +++ b/WordPress/Classes/ViewRelated/Post/PostPreviewGenerator.swift @@ -87,7 +87,7 @@ private extension PostPreviewGenerator { case .draft, .publishPrivate, .pending, .scheduled, .publish: return true default: - return post.blog.isPrivateAtWPCom() + return post.isPrivateAtWPCom() } } From d5551dc112996ef5d2b5594e26485632b62a5ee0 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 15:20:46 -0300 Subject: [PATCH 61/80] Removes an unused error. --- WordPress/Classes/ViewRelated/Post/PostCompactCell.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift index 0d2224518b44..0855595a3886 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift @@ -15,10 +15,6 @@ class PostCompactCell: UITableViewCell, ConfigurablePostView { @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var separator: UIView! - enum Error: Swift.Error { - case cannotCreateMediaHostFromPost(post: AbstractPost) - } - private weak var actionSheetDelegate: PostActionSheetDelegate? lazy var imageLoader: ImageLoader = { From cb2f31db16def16302aad9b774d2fe31ee9841c4 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 15:22:13 -0300 Subject: [PATCH 62/80] Rolls back some unnecessary changes. --- WordPress/WordPressTest/ReaderPostCardCellTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/WordPressTest/ReaderPostCardCellTests.swift b/WordPress/WordPressTest/ReaderPostCardCellTests.swift index 25fee12fafd5..836ee8991be2 100644 --- a/WordPress/WordPressTest/ReaderPostCardCellTests.swift +++ b/WordPress/WordPressTest/ReaderPostCardCellTests.swift @@ -2,8 +2,8 @@ import XCTest class MockContentProvider: NSObject, ReaderPostContentProvider { - func siteID() -> NSNumber! { - return NSNumber(value: 15567) + func siteID() -> NSNumber { + return NSNumber(value: 15546) } func titleForDisplay() -> String! { From 7dab71ce44ba78023473ca7c1232f2d47340257a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 7 Apr 2020 15:45:20 -0300 Subject: [PATCH 63/80] Fixes an issue when accessing the WP Media Library in Atomic Private Sites. --- .../Services/MediaThumbnailService.swift | 24 ++++--------------- .../Gutenberg/EditorMediaUtility.swift | 6 +---- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/WordPress/Classes/Services/MediaThumbnailService.swift b/WordPress/Classes/Services/MediaThumbnailService.swift index 1148c232d226..97891b2f465b 100644 --- a/WordPress/Classes/Services/MediaThumbnailService.swift +++ b/WordPress/Classes/Services/MediaThumbnailService.swift @@ -187,26 +187,10 @@ class MediaThumbnailService: LocalCoreDataService { onError(error) } } - if media.blog.isPrivateAtWPCom() { - let accountService = AccountService(managedObjectContext: self.managedObjectContext) - guard let authToken = accountService.defaultWordPressComAccount()?.authToken else { - // Don't have an auth token for some reason, return nothing. - onCompletion(nil) - return - } - DispatchQueue.main.async { - WPImageSource.shared().downloadImage(for: imageURL, - authToken: authToken, - withSuccess: inContextImageHandler, - failure: inContextErrorHandler) - } - } else { - DispatchQueue.main.async { - WPImageSource.shared().downloadImage(for: imageURL, - withSuccess: inContextImageHandler, - failure: inContextErrorHandler) - } - } + + let download = ImageDownload(url: imageURL, blog: media.blog, onSuccess: inContextImageHandler, onFailure: inContextErrorHandler) + + download.start() } // MARK: - Helpers diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 01838a105d24..087dbd52991d 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -28,11 +28,7 @@ final class ImageDownload: AsyncOperation { for: url, from: host, onComplete: { request in - ImageDownloader.shared.downloadImage(for: request) { [weak self] (image, error) in - guard let self = self else { - return - } - + ImageDownloader.shared.downloadImage(for: request) { (image, error) in self.state = .isFinished DispatchQueue.main.async { From adab57af9d449101d28cb29a64f3e4e5b006adeb Mon Sep 17 00:00:00 2001 From: Ngoc T Date: Tue, 7 Apr 2020 14:04:30 +1000 Subject: [PATCH 64/80] Show the more media picking as Alert instead of Popover on iPad so its position won't be misplaced #10881 --- .../Media/StockPhotos/AztecMediaPickingCoordinator.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Media/StockPhotos/AztecMediaPickingCoordinator.swift b/WordPress/Classes/ViewRelated/Media/StockPhotos/AztecMediaPickingCoordinator.swift index a87f9cc7a246..82e2ca5b47bf 100644 --- a/WordPress/Classes/ViewRelated/Media/StockPhotos/AztecMediaPickingCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Media/StockPhotos/AztecMediaPickingCoordinator.swift @@ -16,7 +16,9 @@ final class AztecMediaPickingCoordinator { let blog = context.blog let fromView = context.view - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let alertController = UIAlertController(title: nil, + message: nil, + preferredStyle: UIDevice.isPad() ? .alert : .actionSheet) if blog.supports(.stockPhotos) { alertController.addAction(freePhotoAction(origin: origin, blog: blog)) From 75fc75e4cde35a7e5553cc1b969f982e4ac98be4 Mon Sep 17 00:00:00 2001 From: Ngoc T Date: Wed, 8 Apr 2020 10:17:56 +1000 Subject: [PATCH 65/80] Added release note --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index cec2c3ec3603..27a5da49c631 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,7 @@ +14.7 +---- +* Classic Editor: Fixed action sheet position for additional Media sources picker on iPad + 14.6 ----- * [internal] the login flow with 2-factor authentication enabled has code changes that can cause regressions. See https://git.io/Jvdil for testing details. From ce5c4a584fcbe7f8460409a2165c39dfa7b81fa1 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 05:00:51 -0300 Subject: [PATCH 66/80] Fixes an issue in the project file. --- WordPress/WordPress.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 28d91d9d6f1c..ef228f5920c7 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1841,7 +1841,6 @@ E185474E1DED8D8800D875D7 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E185474D1DED8D8800D875D7 /* UserNotifications.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; E18549D9230EED73003C620E /* BlogService+Deduplicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18549D8230EED73003C620E /* BlogService+Deduplicate.swift */; }; E18549DB230FBFEF003C620E /* BlogServiceDeduplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18549DA230FBFEF003C620E /* BlogServiceDeduplicationTests.swift */; }; - E1928B2E1F8369F100E076C8 /* WebViewAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1928B2D1F8369F100E076C8 /* WebViewAuthenticatorTests.swift */; }; E192E78C22EF453C008D725D /* WordPress-87-88.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = E192E78B22EF453C008D725D /* WordPress-87-88.xcmappingmodel */; }; E19B17AE1E5C6944007517C6 /* BasePost.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19B17AD1E5C6944007517C6 /* BasePost.swift */; }; E19B17B01E5C69A5007517C6 /* NSManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19B17AF1E5C69A5007517C6 /* NSManagedObject.swift */; }; @@ -4343,7 +4342,6 @@ E1874BFE161C5DBC0058BDC4 /* WordPress 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 7.xcdatamodel"; sourceTree = ""; }; E18D8AE21397C51A00000861 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; E18D8AE41397C54E00000861 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - E1928B2D1F8369F100E076C8 /* WebViewAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewAuthenticatorTests.swift; sourceTree = ""; }; E192E78B22EF453C008D725D /* WordPress-87-88.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = "WordPress-87-88.xcmappingmodel"; sourceTree = ""; }; E1939C671B15B4D2001AFEF7 /* WordPress 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 30.xcdatamodel"; sourceTree = ""; }; E19853331755E461001CC6D5 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; @@ -7354,7 +7352,6 @@ 93A379EB19FFBF7900415023 /* KeychainTest.m */, 1759F1711FE017F20003EC81 /* QueueTests.swift */, 1797373620EBAA4100377B4E /* RouteMatcherTests.swift */, - E1928B2D1F8369F100E076C8 /* WebViewAuthenticatorTests.swift */, 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */, E1E4CE0C177439D100430844 /* WPAvatarSourceTest.m */, 5981FE041AB8A89A0009E080 /* WPUserAgentTests.m */, @@ -13390,7 +13387,6 @@ D821C81B21003AE9002ED995 /* FormattableContentGroupTests.swift in Sources */, 93D86B981C691E71003D8E3E /* LocalCoreDataServiceTests.m in Sources */, 400A2C932217B463000A8A59 /* ReferrerStatsRecordValueTests.swift in Sources */, - E1928B2E1F8369F100E076C8 /* WebViewAuthenticatorTests.swift in Sources */, 73178C2921BEE09300E37C9A /* SiteSegmentsStepTests.swift in Sources */, 08F8CD311EBD2A960049D0C0 /* MediaImageExporterTests.swift in Sources */, 400A2C912217B308000A8A59 /* CountryStatsRecordValueTests.swift in Sources */, From 71b6ddebeead7922d486c7be662407849b787354 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 05:01:58 -0300 Subject: [PATCH 67/80] rake lint:autocorrect --- WordPress/Classes/Services/MediaThumbnailService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Services/MediaThumbnailService.swift b/WordPress/Classes/Services/MediaThumbnailService.swift index 97891b2f465b..487d69254518 100644 --- a/WordPress/Classes/Services/MediaThumbnailService.swift +++ b/WordPress/Classes/Services/MediaThumbnailService.swift @@ -187,9 +187,9 @@ class MediaThumbnailService: LocalCoreDataService { onError(error) } } - + let download = ImageDownload(url: imageURL, blog: media.blog, onSuccess: inContextImageHandler, onFailure: inContextErrorHandler) - + download.start() } From aa8d473590c28e47ff142f72faff20f07fc03089 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 05:25:25 -0300 Subject: [PATCH 68/80] Renamed CancellableTask to ImageDownloaderTask. --- .../Services/MediaThumbnailService.swift | 2 +- .../Utility/Media/ImageDownloader.swift | 29 ++++++++++++++----- .../AztecPostViewController.swift | 2 +- .../Gutenberg/AztecAttachmentDelegate.swift | 2 +- .../Gutenberg/EditorMediaUtility.swift | 20 +++---------- .../Utils/GutenbergMediaEditorImage.swift | 2 +- .../RevisionPreviewTextViewManager.swift | 2 +- 7 files changed, 30 insertions(+), 29 deletions(-) diff --git a/WordPress/Classes/Services/MediaThumbnailService.swift b/WordPress/Classes/Services/MediaThumbnailService.swift index 487d69254518..a718ba9db68c 100644 --- a/WordPress/Classes/Services/MediaThumbnailService.swift +++ b/WordPress/Classes/Services/MediaThumbnailService.swift @@ -188,7 +188,7 @@ class MediaThumbnailService: LocalCoreDataService { } } - let download = ImageDownload(url: imageURL, blog: media.blog, onSuccess: inContextImageHandler, onFailure: inContextErrorHandler) + let download = AuthenticatedImageDownload(url: imageURL, blog: media.blog, onSuccess: inContextImageHandler, onFailure: inContextErrorHandler) download.start() } diff --git a/WordPress/Classes/Utility/Media/ImageDownloader.swift b/WordPress/Classes/Utility/Media/ImageDownloader.swift index e88cdd3b39f8..d7b586370629 100644 --- a/WordPress/Classes/Utility/Media/ImageDownloader.swift +++ b/WordPress/Classes/Utility/Media/ImageDownloader.swift @@ -1,19 +1,32 @@ import Foundation -extension URLSessionTask: CancellableTask {} +// MARK: - ImageDownloadTask protocol + +/// This protocol can be implemented to represent an image download task handled by the ImageDownloader. +/// +protocol ImageDownloaderTask { + /// Calling this method should cancel the task's execution. + /// + func cancel() +} + +extension Operation: ImageDownloaderTask {} +extension URLSessionTask: ImageDownloaderTask {} + +extension URLSession: ImageDownloaderTask { + func cancel() { + invalidateAndCancel() + } +} // MARK: - Image Downloading Tool -// + class ImageDownloader { /// Shared Instance! /// static let shared = ImageDownloader() - /// Public Aliases - /// - typealias Task = URLSessionDataTask - /// Internal URLSession Instance /// private let session = URLSession(configuration: .default) @@ -27,7 +40,7 @@ class ImageDownloader { /// Downloads the UIImage resource at the specified URL. On completion the received closure will be executed. /// @discardableResult - func downloadImage(at url: URL, completion: @escaping (UIImage?, Error?) -> Void) -> CancellableTask { + func downloadImage(at url: URL, completion: @escaping (UIImage?, Error?) -> Void) -> ImageDownloaderTask { var request = URLRequest(url: url) request.httpShouldHandleCookies = false request.addValue("image/*", forHTTPHeaderField: "Accept") @@ -38,7 +51,7 @@ class ImageDownloader { /// Downloads the UIImage resource at the specified endpoint. On completion the received closure will be executed. /// @discardableResult - func downloadImage(for request: URLRequest, completion: @escaping (UIImage?, Error?) -> Void) -> CancellableTask { + func downloadImage(for request: URLRequest, completion: @escaping (UIImage?, Error?) -> Void) -> ImageDownloaderTask { let task = session.dataTask(with: request) { (data, _, error) in guard let data = data, let image = UIImage(data: data) else { let error = error ?? ImageDownloaderError.failed diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 61b6acc7cd4a..8326b364d7bb 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -360,7 +360,7 @@ class AztecPostViewController: UIViewController, PostEditor { /// Active Downloads /// - fileprivate var activeMediaRequests = [CancellableTask]() + fileprivate var activeMediaRequests = [ImageDownloaderTask]() /// Media Library Data Source /// diff --git a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift index ef6eb4c974ca..8dfa1929acbb 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/AztecAttachmentDelegate.swift @@ -2,7 +2,7 @@ import Aztec class AztecAttachmentDelegate: TextViewAttachmentDelegate { private let post: AbstractPost - private var activeMediaRequests = [CancellableTask]() + private var activeMediaRequests = [ImageDownloaderTask]() private let mediaUtility = EditorMediaUtility() init(post: AbstractPost) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift index 087dbd52991d..08c4a9627df1 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/EditorMediaUtility.swift @@ -2,9 +2,7 @@ import AutomatticTracks import Aztec import Gridicons -extension Operation: CancellableTask {} - -final class ImageDownload: AsyncOperation { +final class AuthenticatedImageDownload: AsyncOperation { let url: URL let blog: Blog private let onSuccess: (UIImage) -> () @@ -54,16 +52,6 @@ final class ImageDownload: AsyncOperation { } } -protocol CancellableTask { - func cancel() -} - -extension URLSession: CancellableTask { - func cancel() { - invalidateAndCancel() - } -} - class EditorMediaUtility { private struct Constants { @@ -112,7 +100,7 @@ class EditorMediaUtility { from url: URL, post: AbstractPost, success: @escaping (UIImage) -> Void, - onFailure failure: @escaping (Error) -> Void) -> CancellableTask { + onFailure failure: @escaping (Error) -> Void) -> ImageDownloaderTask { let imageMaxDimension = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) //use height zero to maintain the aspect ratio when fetching @@ -127,7 +115,7 @@ class EditorMediaUtility { size requestSize: CGSize, scale: CGFloat, post: AbstractPost, success: @escaping (UIImage) -> Void, - onFailure failure: @escaping (Error) -> Void) -> CancellableTask { + onFailure failure: @escaping (Error) -> Void) -> ImageDownloaderTask { let imageMaxDimension = max(requestSize.width, requestSize.height) //use height zero to maintain the aspect ratio when fetching @@ -149,7 +137,7 @@ class EditorMediaUtility { requestURL = PhotonImageURLHelper.photonURL(with: size, forImageURL: url) } - let imageDownload = ImageDownload( + let imageDownload = AuthenticatedImageDownload( url: requestURL, blog: post.blog, onSuccess: success, diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift index 034e931e91f2..97d75e5cbefa 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergMediaEditorImage.swift @@ -6,7 +6,7 @@ import MediaEditor We need the full high-quality image in the Media Editor. */ class GutenbergMediaEditorImage: AsyncImage { - private var tasks: [CancellableTask] = [] + private var tasks: [ImageDownloaderTask] = [] private var originalURL: URL diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift index e315d318e970..9b53f309e66f 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift @@ -6,7 +6,7 @@ class RevisionPreviewTextViewManager: NSObject { var post: AbstractPost? private let mediaUtility = EditorMediaUtility() - private var activeMediaRequests = [CancellableTask]() + private var activeMediaRequests = [ImageDownloaderTask]() private enum Constants { static let mediaPlaceholderImageSize = CGSize(width: 128, height: 128) From 669e9ef0cb0cf713f33687bbc13b43cec591f188 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 07:49:34 -0300 Subject: [PATCH 69/80] Fixes several issues with image downloads by switching to AlamoFire. --- Podfile | 1 + Podfile.lock | 7 ++++++- .../Extensions/UIImageView+SiteIcon.swift | 18 ++++++++---------- .../Classes/Utility/Media/ImageLoader.swift | 13 ++++++------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Podfile b/Podfile index 7a8aced1e5da..609911970253 100644 --- a/Podfile +++ b/Podfile @@ -162,6 +162,7 @@ target 'WordPress' do pod 'Starscream', '3.0.6' pod 'SVProgressHUD', '2.2.5' pod 'ZendeskSupportSDK', '5.0.0' + pod 'AlamofireImage', '3.5.2' pod 'AlamofireNetworkActivityIndicator', '~> 2.4' pod 'FSInteractiveMap', :git => 'https://github.com/wordpress-mobile/FSInteractiveMap.git', :tag => '0.2.0' pod 'JTAppleCalendar', '~> 8.0.2' diff --git a/Podfile.lock b/Podfile.lock index bee761303478..755e81bd9136 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,8 @@ PODS: - 1PasswordExtension (1.8.6) - Alamofire (4.8.0) + - AlamofireImage (3.5.2): + - Alamofire (~> 4.8) - AlamofireNetworkActivityIndicator (2.4.0): - Alamofire (~> 4.8) - AppCenter (2.5.1): @@ -419,6 +421,7 @@ PODS: DEPENDENCIES: - Alamofire (= 4.8.0) + - AlamofireImage (= 3.5.2) - AlamofireNetworkActivityIndicator (~> 2.4) - AppCenter (= 2.5.1) - AppCenter/Distribute (= 2.5.1) @@ -490,6 +493,7 @@ SPEC REPOS: trunk: - 1PasswordExtension - Alamofire + - AlamofireImage - AlamofireNetworkActivityIndicator - AppCenter - Automattic-Tracks-iOS @@ -626,6 +630,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 + AlamofireImage: 63cfe3baf1370be6c498149687cf6db3e3b00999 AlamofireNetworkActivityIndicator: 9acc3de3ca6645bf0efed462396b0df13dd3e7b8 AppCenter: fddcbac6e4baae3d93a196ceb0bfe0e4ce407dec Automattic-Tracks-iOS: dbe6301bebdc1e444972475bae19299491702cef @@ -709,6 +714,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: 07d9390e327784a39db6ad2b2138fa671d3cce59 +PODFILE CHECKSUM: 6a01cb2728f23526b2998f507ebb3d0afdabdaf9 COCOAPODS: 1.8.4 diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index 75e3435eb268..e84867ad8a14 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -1,3 +1,4 @@ +import AlamofireImage import AutomatticTracks import Foundation @@ -61,10 +62,9 @@ extension UIImageView { with request: URLRequest, placeholderImage: UIImage?) { - downloadImage( - usingRequest: request, - placeholderImage: placeholderImage, - success: { [weak self] (image) in + af_setImage(withURLRequest: request, placeholderImage: placeholderImage) { [weak self] dataResponse in + switch dataResponse.result { + case .success(let image): guard let self = self else { return } @@ -86,12 +86,10 @@ extension UIImageView { } self.removePlaceholderBorder() - }, - failure: { error -> () in - if let error = error { - CrashLogging.logError(error) - } - }) + case .failure(let error): + CrashLogging.logError(error) + } + } } diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index b2001520790e..87eaa1f2e487 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -1,4 +1,5 @@ import MobileCoreServices +import AlamofireImage import AutomatticTracks /// Class used together with `CachedAnimatedImageView` to facilitate the loading of both @@ -198,24 +199,22 @@ import AutomatticTracks /// private func downloadImage(from request: URLRequest) { imageView.startLoadingAnimation() + imageView.af_setImage(withURLRequest: request) +/* imageView.downloadImage(usingRequest: request, placeholderImage: placeholder, success: { [weak self] (image) in // Since a success block is specified, we need to set the image manually. self?.imageView.image = image self?.callSuccessHandler() }) { [weak self] (error) in self?.callErrorHandler(with: error) - } + }*/ } /// Downloads the image from the given URL. /// private func downloadImage(from url: URL) { - imageView.startLoadingAnimation() - imageView.downloadImage(from: url, placeholderImage: placeholder, success: { [weak self] (_) in - self?.callSuccessHandler() - }) { [weak self] (error) in - self?.callErrorHandler(with: error) - } + let request = URLRequest(url: url) + downloadImage(from: request) } private func callSuccessHandler() { From b1d1b14c27c79954eb454b19f71be5a9380cd6e8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 07:58:58 -0300 Subject: [PATCH 70/80] Fixes some issues with a code change in the last commit. --- .../Classes/Utility/Media/ImageLoader.swift | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 87eaa1f2e487..76eacfd61da1 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -199,15 +199,18 @@ import AutomatticTracks /// private func downloadImage(from request: URLRequest) { imageView.startLoadingAnimation() - imageView.af_setImage(withURLRequest: request) -/* - imageView.downloadImage(usingRequest: request, placeholderImage: placeholder, success: { [weak self] (image) in - // Since a success block is specified, we need to set the image manually. - self?.imageView.image = image - self?.callSuccessHandler() - }) { [weak self] (error) in - self?.callErrorHandler(with: error) - }*/ + imageView.af_setImage(withURLRequest: request, completion: { [weak self] dataResponse in + guard let self = self else { + return + } + + switch dataResponse.result { + case .success: + self.callSuccessHandler() + case .failure(let error): + self.callErrorHandler(with: error) + } + }) } /// Downloads the image from the given URL. From e7e025bf4e83aa7dafda0d9cbb6d5590c020b350 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 08:15:36 -0300 Subject: [PATCH 71/80] Removes another call to downloadImage. --- WordPress/Classes/Extensions/UIImageView+SiteIcon.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift index e84867ad8a14..f7e527f9a925 100644 --- a/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift +++ b/WordPress/Classes/Extensions/UIImageView+SiteIcon.swift @@ -49,7 +49,8 @@ extension UIImageView { return } - downloadImage(from: siteIconURL, placeholderImage: placeholderImage) + let request = URLRequest(url: siteIconURL) + downloadSiteIcon(with: request, placeholderImage: placeholderImage) } /// Downloads a SiteIcon image, using a specified request. From c9177c72db3faa90235eaa6980250765ad500211 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 08:31:45 -0300 Subject: [PATCH 72/80] Updates to WPKit 4.7.1-beta.1 --- Podfile | 4 ++-- Podfile.lock | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Podfile b/Podfile index a77339986a3c..acf4fe27ff18 100644 --- a/Podfile +++ b/Podfile @@ -43,11 +43,11 @@ def wordpress_ui end def wordpress_kit - #pod 'WordPressKit', '~> 4.7.0' + pod 'WordPressKit', '~> 4.7.1-beta.1' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '4.6.0-beta.3' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => '' - pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde' + #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '' #pod 'WordPressKit', :path => '../WordPressKit-iOS' end diff --git a/Podfile.lock b/Podfile.lock index cb8891832657..0b1f96fc0cdf 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -477,7 +477,7 @@ DEPENDENCIES: - SVProgressHUD (= 2.2.5) - WordPress-Editor-iOS (~> 1.17.1) - WordPressAuthenticator (~> 1.12.0) - - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde`) + - WordPressKit (~> 4.7.1-beta.1) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.5.2) @@ -524,6 +524,7 @@ SPEC REPOS: - WordPress-Aztec-iOS - WordPress-Editor-iOS - WordPressAuthenticator + - WordPressKit - WordPressMocks - WordPressShared - WordPressUI @@ -608,9 +609,6 @@ EXTERNAL SOURCES: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 - WordPressKit: - :commit: 5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git Yoga: :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.25.0/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json @@ -624,9 +622,6 @@ CHECKOUT OPTIONS: RNTAztecView: :git: http://github.com/wordpress-mobile/gutenberg-mobile/ :tag: v1.25.0 - WordPressKit: - :commit: 5c697bd1aa74bb2aa11ba6f5ef62d1aaf1ab1dde - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 @@ -714,6 +709,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: 496ae937cce078bdb658397d8f82e03fd0345721 +PODFILE CHECKSUM: b123d3845aea68ad2718a1c6636e0a92e881b7f1 COCOAPODS: 1.8.4 From 36a94d02911bc536473088ffd233af9d4cbb309a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 08:35:50 -0300 Subject: [PATCH 73/80] Wires isAtomic in ReaderPost. --- WordPress/Classes/Models/ReaderPost.m | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/WordPress/Classes/Models/ReaderPost.m b/WordPress/Classes/Models/ReaderPost.m index f250c7d117b0..56fb70cea524 100644 --- a/WordPress/Classes/Models/ReaderPost.m +++ b/WordPress/Classes/Models/ReaderPost.m @@ -68,11 +68,7 @@ - (BOOL)isCrossPost - (BOOL)isAtomic { - // TODO: This is temporary until we start parsing a new value from the reader endpoint. - // - // Issue: https://git.io/JvNAN - // - return false; + return self.isBlogAtomic; } - (BOOL)isPrivate From 665162286a4bd8a518934d8a90d511f37fe7b1d4 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 09:02:22 -0300 Subject: [PATCH 74/80] Changes the auth-proxy path. --- WordPress/Classes/Networking/MediaRequestAuthenticator.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 3e5c78658bd8..7d16c47a8c82 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -214,11 +214,12 @@ class MediaRequestAuthenticator { return } - let contentPath = components.path[wpContentRange.lowerBound ..< components.path.endIndex] + let contentPath = String(components.path[wpContentRange.lowerBound ..< components.path.endIndex]) components.scheme = secureHttpScheme components.host = wpComApiHost - components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file\(contentPath)" + components.path = "/wpcom/v2/sites/\(siteID)/atomic-auth-proxy/file" + components.queryItems = [URLQueryItem(name: "path", value: contentPath)] guard let finalURL = components.url else { fail(Error.cannotCreateAtomicProxyURL(components: components)) From eecf0c55e2efad4e419a6de185781ad7fff94676 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Wed, 8 Apr 2020 16:51:52 +0200 Subject: [PATCH 75/80] Update release notes for 14.6 --- WordPress/Resources/release_notes.txt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index fd93b37ce068..5cf44566382e 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -1,15 +1,11 @@ -New blocks! Welcome the Latest Posts block, for highlighting your new stuff in posts and pages. +Site Creation has been streamlined by removing some intermediate steps. Pick the kind of site you want, enter a domain name, and you’re done. -When you create a new page, we’ll show you a few templates — pick one, and we’ll add all the necessary blocks to your page to get you started quickly. +The Block editor got more improvements: The Cover block is now available; there are alignment options in the Heading block; and a dropdown toolbar makes alignment easier for Heading, Paragraph, Image, MediaText blocks. And we removed the dimming effect on unselected blocks. -We fixed up a few block editor issues; no more crashes when pasting in HTML content or missing Quote block borders in Dark Mode. +The layout of the Notifications screen caused text to get lost under the casing of some models of mobile phone. That’s fixed! -If you scheduled a post to publish in the past, it would display as “scheduled” instead of “published.” That’s fixed, along with an issue where dates displayed incorrectly if your site’s timezone wasn’t the same as your device’s timezone. +Sometimes you’d try to @-mention someone in a comment, and the app would suggest names but then not let you select one — that’s also fixed. We also took care of a few other issues that caused the app to crash when you tried to access the Site Pages screen or use the Quick Action buttons on iPads to get to your blog posts. And speaking of pages and posts, previews are now bigger for iPads running iOS 13. -We fixed some UI inconsistencies in Dark Mode and with the Reader toolbar. +We like our Dark Mode like we like our coffee: really dark, and without any bugs floating around in it. There were some glitches in Light and Dark Modes, and things sometimes behaved oddly when you switched from one to the other. There are far fewer of those glitches now. -You can use @-mentions when writing comments in fullscreen mode. - -Sometimes Signup and Login Magic Link emails would go to your spam folder, so we added a note to the confirmation screen suggesting that folks check their spam/junk folders if they don’t see the email promptly. - -Stay safe, everyone. \ No newline at end of file +Stay safe, y’all. \ No newline at end of file From de7ad4d670093b05069a66489dc22fa88b48b7a4 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Wed, 8 Apr 2020 16:51:57 +0200 Subject: [PATCH 76/80] Update metadata strings --- WordPress/Resources/AppStoreStrings.po | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/WordPress/Resources/AppStoreStrings.po b/WordPress/Resources/AppStoreStrings.po index 62b6142e298f..5c073702a5ed 100644 --- a/WordPress/Resources/AppStoreStrings.po +++ b/WordPress/Resources/AppStoreStrings.po @@ -38,23 +38,19 @@ msgctxt "app_store_keywords" msgid "social,network,notes,jetpack,photos,writing,geotagging,media,blog,wordpress,website,blogging,design\n" msgstr "" -msgctxt "v14.5-whats-new" +msgctxt "v14.6-whats-new" msgid "" -"New blocks! Welcome the Latest Posts block, for highlighting your new stuff in posts and pages.\n" +"Site Creation has been streamlined by removing some intermediate steps. Pick the kind of site you want, enter a domain name, and you’re done.\n" "\n" -"When you create a new page, we’ll show you a few templates — pick one, and we’ll add all the necessary blocks to your page to get you started quickly.\n" +"The Block editor got more improvements: The Cover block is now available; there are alignment options in the Heading block; and a dropdown toolbar makes alignment easier for Heading, Paragraph, Image, MediaText blocks. And we removed the dimming effect on unselected blocks.\n" "\n" -"We fixed up a few block editor issues; no more crashes when pasting in HTML content or missing Quote block borders in Dark Mode.\n" +"The layout of the Notifications screen caused text to get lost under the casing of some models of mobile phone. That’s fixed!\n" "\n" -"If you scheduled a post to publish in the past, it would display as “scheduled” instead of “published.” That’s fixed, along with an issue where dates displayed incorrectly if your site’s timezone wasn’t the same as your device’s timezone.\n" +"Sometimes you’d try to @-mention someone in a comment, and the app would suggest names but then not let you select one — that’s also fixed. We also took care of a few other issues that caused the app to crash when you tried to access the Site Pages screen or use the Quick Action buttons on iPads to get to your blog posts. And speaking of pages and posts, previews are now bigger for iPads running iOS 13.\n" "\n" -"We fixed some UI inconsistencies in Dark Mode and with the Reader toolbar.\n" +"We like our Dark Mode like we like our coffee: really dark, and without any bugs floating around in it. There were some glitches in Light and Dark Modes, and things sometimes behaved oddly when you switched from one to the other. There are far fewer of those glitches now.\n" "\n" -"You can use @-mentions when writing comments in fullscreen mode.\n" -"\n" -"Sometimes Signup and Login Magic Link emails would go to your spam folder, so we added a note to the confirmation screen suggesting that folks check their spam/junk folders if they don’t see the email promptly.\n" -"\n" -"Stay safe, everyone.\n" +"Stay safe, y’all.\n" msgstr "" #. translators: This is a standard chunk of text used to tell a user what's new with a release when nothing major has changed. From d3155d130f0ee79aad8b9ca14b52610e5d945d53 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 12:00:31 -0300 Subject: [PATCH 77/80] Removed some unused code and files. --- .../System/WordPress-Bridging-Header.h | 1 - .../ViewRelated/Post/PrivateSiteURLProtocol.h | 23 -- .../ViewRelated/Post/PrivateSiteURLProtocol.m | 241 ------------------ WordPress/WordPress.xcodeproj/project.pbxproj | 6 - 4 files changed, 271 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h delete mode 100644 WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index bb15a59638c9..2f5a4465c89f 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -55,7 +55,6 @@ #import "WPProgressTableViewCell.h" #import "PostTag.h" #import "PostTagService.h" -#import "PrivateSiteURLProtocol.h" #import "ReachabilityUtils.h" #import "ReaderCommentsViewController.h" diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h deleted file mode 100644 index 83d18e55c7ff..000000000000 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.h +++ /dev/null @@ -1,23 +0,0 @@ -#import - -@interface PrivateSiteURLProtocol : NSURLProtocol - -/** - (Un)RegisterPrivateSiteURLProtocol are convenience methods for registering and - unregistering the protocol safely. - - For performance reasons we do not want to register the protocol for the - lifecycle of the app -- potentially `canInitWithRequest` would be called for every - http request. Register the protocol for use when its needed and unregister it when - its not. - - Use registerPrivateSiteURLProtocol and unregisterPrivateSiteURLProtocol to - keep track of the number of users of the protocol. The call to - `NSURLProtcol unregisterClass:` is only made when there are no longer any uses - remaining. This will help avoid edgecases where the protcol could be potentially - unregistered by one user immediately after another user had registered it. - */ -+ (void)registerPrivateSiteURLProtocol; -+ (void)unregisterPrivateSiteURLProtocol; - -@end diff --git a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m b/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m deleted file mode 100644 index ad8cd857c1b7..000000000000 --- a/WordPress/Classes/ViewRelated/Post/PrivateSiteURLProtocol.m +++ /dev/null @@ -1,241 +0,0 @@ -#import "PrivateSiteURLProtocol.h" -#import "AccountService.h" -#import "ContextManager.h" -#import "WPAccount.h" -#import "Blog.h" - -@interface PrivateSiteURLProtocolSession: NSObject - -+ (instancetype) sharedInstance; -- (NSURLSessionTask *)createSessionTaskForRequest:(NSURLRequest *)request forProtocol:(NSURLProtocol *)protocol; -- (void)stopSessionTask:(NSURLSessionTask *)sessionTask; - -@end - -@interface PrivateSiteURLProtocol() - -@property (nonatomic, strong) NSURLSessionTask *sessionTask; - -@end - -static NSInteger regcount = 0; -static NSString const * mutex = @"PrivateSiteURLProtocol-Mutex"; -static NSString *cachedToken; - -@implementation PrivateSiteURLProtocol - -+ (void)registerPrivateSiteURLProtocol -{ - @synchronized(mutex) { - if (regcount == 0) { - if (![NSURLProtocol registerClass:[self class]]) { - NSAssert(YES, @"Unable to register protocol"); - DDLogInfo(@"Unable to register protocol"); - } - } - regcount++; - } -} - -+ (void)unregisterPrivateSiteURLProtocol -{ - @synchronized(mutex) { - cachedToken = nil; - if (regcount > 0) { - regcount--; - if (regcount == 0) { - [NSURLProtocol unregisterClass:[self class]]; - } - } else { - DDLogInfo(@"Detected unbalanced register/unregister private site protocol."); - } - } -} - -+ (BOOL)canInitWithRequest:(NSURLRequest *)request -{ - NSString *authHeader = [request.allHTTPHeaderFields stringForKey:@"Authorization"]; - if (authHeader && [authHeader rangeOfString:@"Bearer"].location != NSNotFound){ - return NO; - } - if (![self requestGoesToWPComSite:request]){ - return NO; - } - if (![self bearerToken]) { - return NO; - } - return YES; -} - -+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request -{ - return request; -} - -+ (NSString *)bearerToken -{ - if (cachedToken) { - return cachedToken; - } - // Thread Safety: Make sure we're running on the Main Thread - if ([NSThread isMainThread]) { - NSManagedObjectContext *context = [[ContextManager sharedInstance] mainContext]; - AccountService *service = [[AccountService alloc] initWithManagedObjectContext:context]; - return service.defaultWordPressComAccount.authToken; - } - - // Otherwise, let's use a Derived Context - __block NSString *authToken = nil; - NSManagedObjectContext *derived = [[ContextManager sharedInstance] newDerivedContext]; - AccountService *service = [[AccountService alloc] initWithManagedObjectContext:derived]; - - [derived performBlockAndWait:^{ - authToken = service.defaultWordPressComAccount.authToken; - }]; - cachedToken = authToken; - return cachedToken; -} - -+ (BOOL)requestGoesToWPComSite:(NSURLRequest *)request -{ - return [self urlGoesToWPComSite:request.URL]; -} - -+ (BOOL)urlGoesToWPComSite:(NSURL *)url -{ - if ([url.scheme isEqualToString:@"https"] && [url.host hasSuffix:@".wordpress.com"]) { - return YES; - } - - return NO; -} - -- (void)startLoading -{ - NSMutableURLRequest *mRequest = [self.request mutableCopy]; - [mRequest addValue:[NSString stringWithFormat:@"Bearer %@", [[self class] bearerToken]] forHTTPHeaderField:@"Authorization"]; - self.sessionTask = [[PrivateSiteURLProtocolSession sharedInstance] createSessionTaskForRequest:mRequest forProtocol:self]; - -} - -- (void)stopLoading -{ - [[PrivateSiteURLProtocolSession sharedInstance] stopSessionTask:self.sessionTask]; - self.sessionTask = nil; -} - -@end - -@interface PrivateSiteURLProtocolSession() - -@property (nonatomic, strong) NSMutableDictionary *taskToProtocolMapping; -@property (nonatomic, strong) NSURLSession *session; - -@end - -@implementation PrivateSiteURLProtocolSession - -+ (instancetype) sharedInstance -{ - static id _sharedInstance = nil; - static dispatch_once_t _onceToken; - dispatch_once(&_onceToken, ^{ - _sharedInstance = [[self alloc] init]; - }); - - return _sharedInstance; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _taskToProtocolMapping = [NSMutableDictionary dictionary]; - } - return self; -} - -- (NSURLSession *)session -{ - if (_session == nil) { - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; - } - return _session; -} - -- (NSURLSessionTask *)createSessionTaskForRequest:(NSURLRequest *)request forProtocol:(NSURLProtocol *)protocol -{ - NSURLSessionTask *sessionTask = [self.session dataTaskWithRequest:request]; - [sessionTask resume]; - - self.taskToProtocolMapping[sessionTask] = protocol; - return sessionTask; -} - -- (void)stopSessionTask:(NSURLSessionTask *)sessionTask -{ - if (sessionTask == nil) { - return; - } - [self.taskToProtocolMapping removeObjectForKey:sessionTask]; - - [sessionTask cancel]; -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data -{ - NSURLProtocol *protocol = self.taskToProtocolMapping[dataTask]; - id client = protocol.client; - - [client URLProtocol:protocol didLoadData:data]; -} - -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error -{ - NSURLProtocol *protocol = self.taskToProtocolMapping[task]; - id client = protocol.client; - - if (error) { - [client URLProtocol:protocol didFailWithError:error]; - } else { - [client URLProtocolDidFinishLoading:protocol]; - } - [self.taskToProtocolMapping removeObjectForKey:task]; -} - -- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error -{ - if (session == _session) { - _session = nil; - } -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didReceiveResponse:(NSURLResponse *)response - completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler -{ - NSURLProtocol *protocol = self.taskToProtocolMapping[dataTask]; - id client = protocol.client; - - [client URLProtocol:protocol didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; - completionHandler(NSURLSessionResponseAllow); -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -willPerformHTTPRedirection:(NSHTTPURLResponse *)response - newRequest:(NSURLRequest *)request - completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler -{ - NSURLProtocol *protocol = self.taskToProtocolMapping[task]; - id client = protocol.client; - - [client URLProtocol:protocol wasRedirectedToRequest:request redirectResponse:response]; - completionHandler(nil); -} - -@end diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ef228f5920c7..8e9a281ecae6 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -635,7 +635,6 @@ 5D1181E71B4D6DEB003F3084 /* WPStyleGuide+Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1181E61B4D6DEB003F3084 /* WPStyleGuide+Reader.swift */; }; 5D13FA571AF99C2100F06492 /* PageListSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */; }; 5D146EBB189857ED0068FDC6 /* FeaturedImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */; }; - 5D17F0BE1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D17F0BD1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m */; }; 5D18FE9F1AFBB17400EFEED0 /* RestorePageTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D18FE9D1AFBB17400EFEED0 /* RestorePageTableViewCell.m */; }; 5D18FEA01AFBB17400EFEED0 /* RestorePageTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D18FE9E1AFBB17400EFEED0 /* RestorePageTableViewCell.xib */; }; 5D1D04751B7A50B100CDE646 /* Reader.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5D1D04731B7A50B100CDE646 /* Reader.storyboard */; }; @@ -2963,8 +2962,6 @@ 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PageListSectionHeaderView.xib; sourceTree = ""; }; 5D146EB9189857ED0068FDC6 /* FeaturedImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeaturedImageViewController.h; sourceTree = ""; usesTabs = 0; }; 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeaturedImageViewController.m; sourceTree = ""; usesTabs = 0; }; - 5D17F0BC1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivateSiteURLProtocol.h; sourceTree = ""; }; - 5D17F0BD1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivateSiteURLProtocol.m; sourceTree = ""; }; 5D18FE9C1AFBB17400EFEED0 /* RestorePageTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RestorePageTableViewCell.h; sourceTree = ""; }; 5D18FE9D1AFBB17400EFEED0 /* RestorePageTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RestorePageTableViewCell.m; sourceTree = ""; }; 5D18FE9E1AFBB17400EFEED0 /* RestorePageTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RestorePageTableViewCell.xib; sourceTree = ""; }; @@ -8362,8 +8359,6 @@ FFEECFFB2084DE2B009B8CDB /* PostSettingsViewController+FeaturedImageUpload.swift */, 593F26601CAB00CA00F14073 /* PostSharingController.swift */, E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */, - 5D17F0BC1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.h */, - 5D17F0BD1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m */, 5903AE1C19B60AB9009D5354 /* WPButtonForNavigationBar.h */, 5903AE1A19B60A98009D5354 /* WPButtonForNavigationBar.m */, FF0AAE0B1A16550D0089841D /* WPMediaProgressTableViewController.h */, @@ -12204,7 +12199,6 @@ E6C0ED3B231DA23400A08B57 /* AccountService+MergeDuplicates.swift in Sources */, 0845B8C61E833C56001BA771 /* URL+Helpers.swift in Sources */, 17D975AF1EF7F6F100303D63 /* WPStyleGuide+Aztec.swift in Sources */, - 5D17F0BE1A1D4C5F0087CCB8 /* PrivateSiteURLProtocol.m in Sources */, E14B40FF1C58B93F005046F6 /* SettingsCommon.swift in Sources */, B50C0C661EF42B6400372C65 /* FormatBarItemProviders.swift in Sources */, 439F4F352196537500F8D0C7 /* RevisionDiffViewController.swift in Sources */, From 126da01bfe1cef335cf8fa6fa35e01b3fd8e55dd Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 12:46:56 -0300 Subject: [PATCH 78/80] Fixes a bug with private sites in the reader. --- .../MediaHost+ReaderPostContentProvider.swift | 13 +++++++++++++ WordPress/Classes/Networking/MediaHost.swift | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift b/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift index ef7e4d55f0fd..d7dada938682 100644 --- a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift +++ b/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift @@ -5,16 +5,29 @@ import Foundation /// extension MediaHost { enum ReaderPostContentProviderError: Swift.Error { + case noDefaultWordPressComAccount case baseInitializerError(error: Error, readerPostContentProvider: ReaderPostContentProvider) } init(with readerPostContentProvider: ReaderPostContentProvider, failure: (ReaderPostContentProviderError) -> ()) { let isAccessibleThroughWPCom = readerPostContentProvider.isWPCom() || readerPostContentProvider.isJetpack() + // This is the only way in which we can obtain the username and authToken here. + // It'd be nice if all data was associated with an account instead, for transparency + // and cleanliness of the code - but this'll have to do for now. + let accountService = AccountService(managedObjectContext: ContextManager.shared.mainContext) + + // We allow a nil account in case the user connected only self-hosted sites. + let account = accountService.defaultWordPressComAccount() + let username = account?.username + let authToken = account?.authToken + self.init(isAccessibleThroughWPCom: isAccessibleThroughWPCom, isPrivate: readerPostContentProvider.isPrivate(), isAtomic: readerPostContentProvider.isAtomic(), siteID: readerPostContentProvider.siteID()?.intValue, + username: username, + authToken: authToken, failure: { error in // We just associate a ReaderPostContentProvider with the underlying error for simpler debugging. failure(ReaderPostContentProviderError.baseInitializerError( diff --git a/WordPress/Classes/Networking/MediaHost.swift b/WordPress/Classes/Networking/MediaHost.swift index fe0b81f2ae17..12741a6396c7 100644 --- a/WordPress/Classes/Networking/MediaHost.swift +++ b/WordPress/Classes/Networking/MediaHost.swift @@ -19,9 +19,9 @@ enum MediaHost: Equatable { isAccessibleThroughWPCom: Bool, isPrivate: Bool, isAtomic: Bool, - siteID: Int? = nil, - username: String? = nil, - authToken: String? = nil, + siteID: Int?, + username: String?, + authToken: String?, failure: (Error) -> Void) { guard isPrivate else { From 7e5f78c7785e7783ed2910a1cd23a815c2727532 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 13:11:42 -0300 Subject: [PATCH 79/80] Fixes some broken unit tests. --- WordPress/WordPressTest/MediaHostTests.swift | 46 ++++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/WordPress/WordPressTest/MediaHostTests.swift b/WordPress/WordPressTest/MediaHostTests.swift index bdaab3d450ae..af97c30856e3 100644 --- a/WordPress/WordPressTest/MediaHostTests.swift +++ b/WordPress/WordPressTest/MediaHostTests.swift @@ -3,24 +3,42 @@ import XCTest class MediaHostTests: XCTestCase { func testInitializationWithPublicSite() { - let host = MediaHost(isAccessibleThroughWPCom: false, isPrivate: false, isAtomic: false) { error in - XCTFail("This should not be called.") + let host = MediaHost( + isAccessibleThroughWPCom: false, + isPrivate: false, + isAtomic: false, + siteID: nil, + username: nil, + authToken: nil) { error in + XCTFail("This should not be called.") } XCTAssertEqual(host, .publicSite) } func testInitializationWithPublicWPComSite() { - let host = MediaHost(isAccessibleThroughWPCom: true, isPrivate: false, isAtomic: false) { error in - XCTFail("This should not be called.") + let host = MediaHost( + isAccessibleThroughWPCom: true, + isPrivate: false, + isAtomic: false, + siteID: nil, + username: nil, + authToken: nil) { error in + XCTFail("This should not be called.") } XCTAssertEqual(host, .publicWPComSite) } func testInitializationWithPrivateSelfHostedSite() { - let host = MediaHost(isAccessibleThroughWPCom: false, isPrivate: true, isAtomic: false) { error in - XCTFail("This should not be called.") + let host = MediaHost( + isAccessibleThroughWPCom: false, + isPrivate: true, + isAtomic: false, + siteID: nil, + username: nil, + authToken: nil) { error in + XCTFail("This should not be called.") } XCTAssertEqual(host, .privateSelfHostedSite) @@ -33,6 +51,8 @@ class MediaHostTests: XCTestCase { isAccessibleThroughWPCom: true, isPrivate: true, isAtomic: false, + siteID: nil, + username: nil, authToken: authToken) { error in XCTFail("This should not be called.") @@ -70,7 +90,8 @@ class MediaHostTests: XCTestCase { isPrivate: true, isAtomic: true, siteID: siteID, - username: username) { error in + username: username, + authToken: nil) { error in if error == .wpComPrivateSiteWithoutAuthToken { expectation.fulfill() } @@ -89,6 +110,7 @@ class MediaHostTests: XCTestCase { isPrivate: true, isAtomic: true, siteID: siteID, + username: nil, authToken: authToken) { error in if error == .wpComPrivateSiteWithoutUsername { expectation.fulfill() @@ -101,8 +123,14 @@ class MediaHostTests: XCTestCase { func testInitializationWithPrivateAtomicWPComSiteWithoutSiteIDFails() { let expectation = self.expectation(description: "The error closure will be called") - let _ = MediaHost(isAccessibleThroughWPCom: true, isPrivate: true, isAtomic: true) { error in - expectation.fulfill() + let _ = MediaHost( + isAccessibleThroughWPCom: true, + isPrivate: true, + isAtomic: true, + siteID: nil, + username: nil, + authToken: nil) { error in + expectation.fulfill() } waitForExpectations(timeout: 0.05) From bb7d122e8b23edc75c1cea27bdc143d6ef3204e3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 8 Apr 2020 17:38:24 -0300 Subject: [PATCH 80/80] Fixes the blog icons for Atomic Private sites in the reader. --- .../Reader/ReaderCrossPostCell.swift | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderCrossPostCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderCrossPostCell.swift index 9d268b7ac65f..deb3e25840b5 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderCrossPostCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderCrossPostCell.swift @@ -1,4 +1,6 @@ +import AlamofireImage import Foundation +import AutomatticTracks import WordPressShared.WPStyleGuide open class ReaderCrossPostCell: UITableViewCell { @@ -90,11 +92,23 @@ open class ReaderCrossPostCell: UITableViewCell { let placeholder = UIImage(named: blavatarPlaceholder) let size = blavatarImageView.frame.size.width * UIScreen.main.scale - let url = contentProvider?.siteIconForDisplay(ofSize: Int(size)) - if url != nil { - blavatarImageView.downloadImage(from: url, placeholderImage: placeholder) - } else { - blavatarImageView.image = placeholder + + guard let contentProvider = contentProvider, + let url = contentProvider.siteIconForDisplay(ofSize: Int(size)) else { + blavatarImageView.image = placeholder + return + } + + let host = MediaHost(with: contentProvider) { error in + CrashLogging.logError(error) + } + + let mediaAuthenticator = MediaRequestAuthenticator() + mediaAuthenticator.authenticatedRequest(for: url, from: host, onComplete: { [weak self] request in + self?.blavatarImageView.af_setImage(withURLRequest: request, placeholderImage: placeholder) + }) { [weak self] error in + CrashLogging.logError(error) + self?.blavatarImageView.image = placeholder } }