From ab8d533df1966d943b0c4f18e472516246f7a48d Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 10 Nov 2019 14:59:05 -0300 Subject: [PATCH 01/29] Display a status label for autosaved posts A post is considered to have an autosave if the `autosaveModifiedDate` is not nil. --- .../Extensions/AbstractPost+PostInformation.swift | 4 ++++ .../ViewRelated/Post/PostCardStatusViewModel.swift | 8 ++++++++ WordPress/WordPressTest/PostBuilder.swift | 9 +++++++++ WordPress/WordPressTest/PostCardCellTests.swift | 9 +++++++++ 4 files changed, 30 insertions(+) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 78d9caa1cc89..8104aeb7bbbf 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -16,4 +16,8 @@ extension AbstractPost: ImageSourceInformation { var isLocalDraft: Bool { return self.isDraft() && !self.hasRemote() } + + var isAutosaveRevisionAvailable: Bool { + return autosaveModifiedDate != nil + } } diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index facf226d42e4..4b989cd443d1 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -59,6 +59,8 @@ class PostCardStatusViewModel: NSObject { return generateFailedStatusMessage() } else if post.remoteStatus == .pushing { return NSLocalizedString("Uploading post...", comment: "Message displayed on a post's card when the post has failed to upload") + } else if post.isAutosaveRevisionAvailable { + return StatusMessages.autosave } else { return post.statusForDisplay() } @@ -98,6 +100,10 @@ class PostCardStatusViewModel: NSObject { return (autoUploadAction == .upload || post.wasAutoUploadCancelled) ? .warning : .error } + if post.isAutosaveRevisionAvailable { + return .warning(.shade40) + } + switch status { case .pending: return .success @@ -262,5 +268,7 @@ class PostCardStatusViewModel: NSObject { comment: "Message displayed on a post's card when the post has failed to upload") static let localChanges = NSLocalizedString("Local changes", comment: "A status label for a post that only exists on the user's iOS device, and has not yet been published to their blog.") + static let autosave = NSLocalizedString("You've made unsaved changes to this post", + comment: "Message displayed on a post's card when the post has unsaved changes") } } diff --git a/WordPress/WordPressTest/PostBuilder.swift b/WordPress/WordPressTest/PostBuilder.swift index 1cf4689927c2..cfc0c157d699 100644 --- a/WordPress/WordPressTest/PostBuilder.swift +++ b/WordPress/WordPressTest/PostBuilder.swift @@ -63,6 +63,15 @@ class PostBuilder { return self } + func autosaved() -> PostBuilder { + post.autosaveTitle = "a" + post.autosaveExcerpt = "b" + post.autosaveContent = "c" + post.autosaveModifiedDate = Date() + return self + } + + func withImage() -> PostBuilder { post.pathForDisplayImage = "https://localhost/image.png" return self diff --git a/WordPress/WordPressTest/PostCardCellTests.swift b/WordPress/WordPressTest/PostCardCellTests.swift index c20d0e7c0793..970e516e3876 100644 --- a/WordPress/WordPressTest/PostCardCellTests.swift +++ b/WordPress/WordPressTest/PostCardCellTests.swift @@ -490,6 +490,15 @@ class PostCardCellTests: XCTestCase { expect(self.postCell.statusLabel.textColor).to(equal(UIColor.warning)) } + func testShowsUnsavedChangesMessageWhenPostHasAutosave() { + let post = PostBuilder(context).with(remoteStatus: .local).autosaved().build() + + postCell.configure(with: post) + + expect(self.postCell.statusLabel.text).to(equal(i18n("You've made unsaved changes to this post"))) + expect(self.postCell.statusLabel.textColor).to(equal(UIColor.warning(.shade40))) + } + private func postCellFromNib() -> PostCardCell { let bundle = Bundle(for: PostCardCell.self) guard let postCell = bundle.loadNibNamed("PostCardCell", owner: nil)?.first as? PostCardCell else { From 11b1996c7e765e2d6fbc309ad70b769c3f2d5661 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 11 Nov 2019 23:41:14 -0300 Subject: [PATCH 02/29] Load post autosave content if user so chooses When a post containing an autosave is tapped, a dialog is presented to allow the user to make a choice between loading the post's current published version and the post's autosave version. --- .../Post/AlertViewController+Autosave.swift | 62 +++++++++++++++++++ .../Post/PostListViewController.swift | 27 +++++++- WordPress/WordPress.xcodeproj/project.pbxproj | 8 ++- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift new file mode 100644 index 000000000000..c176d9b05727 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -0,0 +1,62 @@ +// +// AlertViewController+Autosave.swift +// WordPress +// +// Created by Paul Von Schrottky on 10/27/19. +// Copyright © 2019 WordPress. All rights reserved. +// + +import UIKit + +extension UIAlertController { + + private static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + + private static let timeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + private class func dateAndTime(for date: Date) -> String { + return dateFormatter.string(from: date) + " @ " + timeFormatter.string(from: date) + } + + /// A dialog giving the user the choice between loading the current version a post or its autosaved version. + static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapSaveOption: @escaping () -> Void, didTapAutosaveOption: @escaping () -> Void) -> UIAlertController { + + let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when use has the option to load unsaved changes") + let body = NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:", comment: "Message displayed in popup when user has the option to load unsaved changes") + let saveLabel = NSLocalizedString("From this device", comment: "Message displayed in popup indicating date of change on device") + let autosaveLabel = NSLocalizedString("From another device", comment: "Message displayed in popup indicating date of change on another device") + + let savedOnLabel = NSLocalizedString("Saved on", comment: "Label shown on save dates") + let saveDateFormatted = savedOnLabel + " " + dateAndTime(for: saveDate) + let autosaveDateFormatted = savedOnLabel + " " + dateAndTime(for: autosaveDate) + let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") + let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") + + let message = + body + "\n\n" + + saveLabel + "\n" + + saveDateFormatted + "\n\n" + + autosaveLabel + "\n" + + autosaveDateFormatted + "\n" + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in + didTapSaveOption() + }) + alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in + didTapAutosaveOption() + }) + + return alertController + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index d89985f1c648..8e1fd24afedc 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -496,10 +496,33 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe presentAlertForPostBeingUploaded() return } + + if post.isAutosaveRevisionAvailable, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { + let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapSaveOption: { [weak self] in + self?.openEditor(with: post, loadingAutosave: false) + }, didTapAutosaveOption: { [weak self] in + self?.openEditor(with: post, loadingAutosave: true) + }) + present(autosaveViewController, animated: true) + } else { + openEditor(with: post, loadingAutosave: false) + } + } + + private func openEditor(with post: Post, loadingAutosave: Bool) { + + if loadingAutosave { + let autosavePost = post.createRevision() + autosavePost.postTitle = post.autosaveTitle + autosavePost.mt_excerpt = post.autosaveExcerpt + autosavePost.content = post.autosaveContent + post.setValue(autosavePost, forKey: "revision") + } + let editor = EditPostViewController(post: post) editor.modalPresentationStyle = .fullScreen - present(editor, animated: false) - WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: apost) + self.present(editor, animated: false) + WPAppAnalytics.track(.postListEditAction, withProperties: self.propertiesForAnalytics(), with: post) } func presentAlertForPostBeingUploaded() { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index c823f69977d8..12dbc5c87bb5 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -329,6 +329,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 */; }; + 32A2ED4D23788989005C5D6A /* AlertViewController+Autosave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */; }; 37022D931981C19000F322B7 /* VerticallyStackedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 37022D901981BF9200F322B7 /* VerticallyStackedButton.m */; }; 374CB16215B93C0800DD0EBC /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374CB16115B93C0800DD0EBC /* AudioToolbox.framework */; }; 37EAAF4D1A11799A006D6306 /* CircularImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAAF4C1A11799A006D6306 /* CircularImageView.swift */; }; @@ -2450,6 +2451,7 @@ 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 = ""; }; 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 93.xcdatamodel"; sourceTree = ""; }; + 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlertViewController+Autosave.swift"; sourceTree = ""; }; 33D5016BDA00B45DFCAF3818 /* Pods-WordPressNotificationServiceExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationServiceExtension/Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; sourceTree = ""; }; 368127CE6F1CA15EC198147D /* Pods-WordPressTodayWidget.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTodayWidget.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTodayWidget/Pods-WordPressTodayWidget.debug.xcconfig"; sourceTree = ""; }; 37022D8F1981BF9200F322B7 /* VerticallyStackedButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VerticallyStackedButton.h; sourceTree = ""; }; @@ -5061,7 +5063,7 @@ path = Networking; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( F14B5F6F208E648200439554 /* config */, @@ -5931,6 +5933,7 @@ 5DF3DD6A1A93772D0051A229 /* Views */ = { isa = PBXGroup; children = ( + 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */, 597421B01CEB6874005D5F38 /* ConfigurablePostView.h */, 57D6C83F229498C4003DDC7E /* InteractivePostView.swift */, 575E126E229779E70041B3EB /* RestorePostTableViewCell.swift */, @@ -9773,7 +9776,7 @@ bg, sk, ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -11529,6 +11532,7 @@ FF0AAE0A1A150A560089841D /* WPProgressTableViewCell.m in Sources */, D8A3A5B5206A4C7800992576 /* StockPhotosPicker.swift in Sources */, E60C2ED71DE5075100488630 /* ReaderCommentCell.swift in Sources */, + 32A2ED4D23788989005C5D6A /* AlertViewController+Autosave.swift in Sources */, 5DBCD9D518F35D7500B32229 /* ReaderTopicService.m in Sources */, 403F57BC20E5CA6A004E889A /* RewindStatusRow.swift in Sources */, E6DE44671B90D251000FA7EF /* ReaderHelpers.swift in Sources */, From 7a2613e900839f14ffd781c1ff0e9a5f5f03ee29 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Wed, 13 Nov 2019 22:39:37 -0300 Subject: [PATCH 03/29] Removed copyrights from new file --- .../ViewRelated/Post/AlertViewController+Autosave.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index c176d9b05727..ee6410ed1a0e 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -1,11 +1,3 @@ -// -// AlertViewController+Autosave.swift -// WordPress -// -// Created by Paul Von Schrottky on 10/27/19. -// Copyright © 2019 WordPress. All rights reserved. -// - import UIKit extension UIAlertController { From 0277ac3720dd4a9bed01db005c35eee726a2dfc4 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Wed, 13 Nov 2019 22:48:04 -0300 Subject: [PATCH 04/29] Removed trailing whitespace violation --- .../Classes/ViewRelated/Post/AlertViewController+Autosave.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index ee6410ed1a0e..41dc3639c737 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -48,7 +48,7 @@ extension UIAlertController { alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in didTapAutosaveOption() }) - + return alertController } } From ce207c3b7817c6935324d0d4a98dc56c88c4cdff Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Wed, 20 Nov 2019 23:31:58 -0300 Subject: [PATCH 05/29] Create edit revision before loading Gutenberg When a post is edited, the editor is loaded with the original post and then a revision is made. This order seems incorrect - the revision should be made _before_ being loaded into the editor. This change will allow autosave content that will be conditionally added to a post revision to be loaded into the editor. --- .../Classes/ViewRelated/Gutenberg/GutenbergViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index bcbeb9fc3dc4..217a106f20ef 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -249,8 +249,8 @@ class GutenbergViewController: UIViewController, PostEditor { override func viewDidLoad() { super.viewDidLoad() - setupGutenbergView() createRevisionOfPost() + setupGutenbergView() configureNavigationBar() refreshInterface() From 26156cf5e59541ab6fb14e1d445f401dcf402d48 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Wed, 20 Nov 2019 23:37:38 -0300 Subject: [PATCH 06/29] Support loading autosave content into the editor If the user chooses to load a post's autosave content, their choice is propogated to both the Gutenberg and Aztec editors where it is loaded into the post revision created by the editors. This revision content is then loaded into the editors using the pre-existing editor functionality. --- WordPress/Classes/Utility/Editor/EditorFactory.swift | 10 +++++----- .../ViewControllers/AztecPostViewController.swift | 6 +++++- .../Gutenberg/GutenbergViewController.swift | 4 ++++ .../ViewRelated/Post/EditPostViewController.swift | 9 ++++++--- .../Classes/ViewRelated/Post/PostEditor+Publish.swift | 9 ++++++++- WordPress/Classes/ViewRelated/Post/PostEditor.swift | 4 ++++ .../ViewRelated/Post/PostListViewController.swift | 11 +---------- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/Utility/Editor/EditorFactory.swift b/WordPress/Classes/Utility/Editor/EditorFactory.swift index b8cecba72a39..42061d345421 100644 --- a/WordPress/Classes/Utility/Editor/EditorFactory.swift +++ b/WordPress/Classes/Utility/Editor/EditorFactory.swift @@ -12,16 +12,16 @@ class EditorFactory { // MARK: - Editor: Instantiation - func instantiateEditor(for post: AbstractPost, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController { + func instantiateEditor(for post: AbstractPost, shouldLoadAutosave: Bool = false, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController { if gutenbergSettings.mustUseGutenberg(for: post) { - return createGutenbergVC(with: post, replaceEditor: replaceEditor) + return createGutenbergVC(with: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) } else { - return AztecPostViewController(post: post, replaceEditor: replaceEditor) + return AztecPostViewController(post: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) } } - private func createGutenbergVC(with post: AbstractPost, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController { - let gutenbergVC = GutenbergViewController(post: post, replaceEditor: replaceEditor) + private func createGutenbergVC(with post: AbstractPost, shouldLoadAutosave: Bool, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController { + let gutenbergVC = GutenbergViewController(post: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) if gutenbergSettings.shouldAutoenableGutenberg(for: post) { gutenbergSettings.setGutenbergEnabled(true, for: post.blog, source: .onBlockPostOpening) diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 2db8c45eb00a..fca53296472b 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -342,6 +342,8 @@ class AztecPostViewController: UIViewController, PostEditor { } } + var shouldLoadAutosave: Bool + /// Active Downloads /// fileprivate var activeMediaRequests = [ImageDownloader.Task]() @@ -442,12 +444,14 @@ class AztecPostViewController: UIViewController, PostEditor { /// required init( post: AbstractPost, + shouldLoadAutosave: Bool = false, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { precondition(post.managedObjectContext != nil) self.post = post + self.shouldLoadAutosave = shouldLoadAutosave self.replaceEditor = replaceEditor self.editorSession = editorSession ?? PostEditorAnalyticsSession(editor: .classic, post: post) @@ -485,7 +489,7 @@ class AztecPostViewController: UIViewController, PostEditor { WPFontManager.loadNotoFontFamily() registerAttachmentImageProviders() - createRevisionOfPost() + createRevisionOfPost(shouldLoadAutosave: shouldLoadAutosave) // Setup configureNavigationBar() diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index 217a106f20ef..ddcaa749e8dc 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -163,6 +163,8 @@ class GutenbergViewController: UIViewController, PostEditor { } } + var shouldLoadAutosave: Bool + let navigationBarManager = PostEditorNavigationBarManager() lazy var attachmentDelegate = AztecAttachmentDelegate(post: post) @@ -218,10 +220,12 @@ class GutenbergViewController: UIViewController, PostEditor { // MARK: - Initializers required init( post: AbstractPost, + shouldLoadAutosave: Bool = false, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { self.post = post + self.shouldLoadAutosave = shouldLoadAutosave self.replaceEditor = replaceEditor verificationPromptHelper = AztecVerificationPromptHelper(account: self.post.blog.account) diff --git a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift index 0116574aea0e..6056d065a784 100644 --- a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift @@ -15,6 +15,7 @@ class EditPostViewController: UIViewController { @objc var openWithPostPost: Bool = false /// appear with media pre-inserted into the post var insertedMedia: [Media]? = nil + let shouldLoadAutosave: Bool @objc fileprivate(set) var post: Post? fileprivate var hasShownEditor = false @@ -42,8 +43,8 @@ class EditPostViewController: UIViewController { /// Initialize as an editor with the provided post /// /// - Parameter post: post to edit - @objc convenience init(post: Post) { - self.init(post: post, blog: post.blog) + @objc convenience init(post: Post, shouldLoadAutosave: Bool = false) { + self.init(post: post, blog: post.blog, shouldLoadAutosave: shouldLoadAutosave) } @@ -60,8 +61,9 @@ class EditPostViewController: UIViewController { /// - post: the post to edit /// - blog: the blog to create a post for, if post is nil /// - Note: it's preferable to use one of the convenience initializers - fileprivate init(post: Post?, blog: Blog) { + fileprivate init(post: Post?, blog: Blog, shouldLoadAutosave: Bool = false) { self.post = post + self.shouldLoadAutosave = shouldLoadAutosave if let post = post { if !post.originalIsDraft() { editingExistingPost = true @@ -125,6 +127,7 @@ class EditPostViewController: UIViewController { fileprivate func showEditor() { let editor = editorFactory.instantiateEditor( for: postToEdit(), + shouldLoadAutosave: shouldLoadAutosave, replaceEditor: { [weak self] (editor, replacement) in self?.replaceEditor(editor: editor, replacement: replacement) }) diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index 7b48d1407b11..f440f54b2841 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -400,7 +400,7 @@ extension PostEditor where Self: UIViewController { } // TODO: Rip this out and put it into the PostService - func createRevisionOfPost() { + func createRevisionOfPost(shouldLoadAutosave: Bool = false) { if post.isLocalRevision, post.original?.postTitle == nil, post.original?.content == nil { // Editing a locally made revision has bit of weirdness in how autosave and @@ -440,6 +440,13 @@ extension PostEditor where Self: UIViewController { managedObjectContext.performAndWait { post = self.post.createRevision() + + if shouldLoadAutosave { + post.postTitle = post.autosaveTitle + post.mt_excerpt = post.autosaveExcerpt + post.content = post.autosaveContent + } + ContextManager.sharedInstance().save(managedObjectContext) } } diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor.swift b/WordPress/Classes/ViewRelated/Post/PostEditor.swift index b6a42e66fa4f..6ee6453f2096 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -25,6 +25,10 @@ protocol PostEditor: class, UIViewControllerTransitioningDelegate { /// var post: AbstractPost { get set } + /// If true, apply autosave content when the editor creates a revision. + /// + var shouldLoadAutosave: Bool { get set } + /// Closure to be executed when the editor gets closed. /// var onClose: ((_ changesSaved: Bool, _ shouldShowPostPost: Bool) -> Void)? { get set } diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 8e1fd24afedc..c90e26754264 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -510,16 +510,7 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe } private func openEditor(with post: Post, loadingAutosave: Bool) { - - if loadingAutosave { - let autosavePost = post.createRevision() - autosavePost.postTitle = post.autosaveTitle - autosavePost.mt_excerpt = post.autosaveExcerpt - autosavePost.content = post.autosaveContent - post.setValue(autosavePost, forKey: "revision") - } - - let editor = EditPostViewController(post: post) + let editor = EditPostViewController(post: post, shouldLoadAutosave: loadingAutosave) editor.modalPresentationStyle = .fullScreen self.present(editor, animated: false) WPAppAnalytics.track(.postListEditAction, withProperties: self.propertiesForAnalytics(), with: post) From c0fae7aec80810e2980d8d3f993dd462f7560582 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 21 Nov 2019 21:21:43 -0300 Subject: [PATCH 07/29] Adhered to better code conventions Renamed variables and removed explicit `self` reference when unnecessary. --- .../ViewRelated/Post/PostListViewController.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index c90e26754264..3b5d37c5c9c8 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -499,21 +499,21 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe if post.isAutosaveRevisionAvailable, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapSaveOption: { [weak self] in - self?.openEditor(with: post, loadingAutosave: false) + self?.openEditor(with: post, shouldLoadAutosave: false) }, didTapAutosaveOption: { [weak self] in - self?.openEditor(with: post, loadingAutosave: true) + self?.openEditor(with: post, shouldLoadAutosave: true) }) present(autosaveViewController, animated: true) } else { - openEditor(with: post, loadingAutosave: false) + openEditor(with: post, shouldLoadAutosave: false) } } - private func openEditor(with post: Post, loadingAutosave: Bool) { - let editor = EditPostViewController(post: post, shouldLoadAutosave: loadingAutosave) + private func openEditor(with post: Post, shouldLoadAutosave: Bool) { + let editor = EditPostViewController(post: post, shouldLoadAutosave: shouldLoadAutosave) editor.modalPresentationStyle = .fullScreen - self.present(editor, animated: false) - WPAppAnalytics.track(.postListEditAction, withProperties: self.propertiesForAnalytics(), with: post) + present(editor, animated: false) + WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: post) } func presentAlertForPostBeingUploaded() { From 0c3148e63cde6dd753e24c86584c081d6e7d8ac1 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 21 Nov 2019 22:01:04 -0300 Subject: [PATCH 08/29] Ignore autosaves on posts with local changes Users are only asked if they wish to load a post's autosave changes if the post has NO local changes. --- .../Classes/ViewRelated/Post/PostListViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 3b5d37c5c9c8..d2f8b22fe876 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -497,7 +497,8 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe return } - if post.isAutosaveRevisionAvailable, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { + // Autosaves are ignored for posts with local changes. + if !post.hasLocalChanges(), post.isAutosaveRevisionAvailable, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapSaveOption: { [weak self] in self?.openEditor(with: post, shouldLoadAutosave: false) }, didTapAutosaveOption: { [weak self] in From 750a19f9d79641a75f15bd0fba7593e26413d819 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 21 Nov 2019 23:03:44 -0300 Subject: [PATCH 09/29] Fix bug where autosave was ignored A recent commit undid the code to load autosaves, this is now fixed. --- .../Classes/ViewRelated/Gutenberg/GutenbergViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index ddcaa749e8dc..79a942a9c6c6 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -253,7 +253,7 @@ class GutenbergViewController: UIViewController, PostEditor { override func viewDidLoad() { super.viewDidLoad() - createRevisionOfPost() + createRevisionOfPost(shouldLoadAutosave: shouldLoadAutosave) setupGutenbergView() configureNavigationBar() refreshInterface() From 76a04b0554b48ed15a6e2631be77e8c8decc75ea Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 21 Nov 2019 23:22:02 -0300 Subject: [PATCH 10/29] Clarify localized string comments --- .../Classes/Extensions/AbstractPost+PostInformation.swift | 1 + .../ViewRelated/Post/AlertViewController+Autosave.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 8104aeb7bbbf..9d88490a92c4 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -17,6 +17,7 @@ extension AbstractPost: ImageSourceInformation { return self.isDraft() && !self.hasRemote() } + /// An autosave revision may include post title, content and/or excerpt. var isAutosaveRevisionAvailable: Bool { return autosaveModifiedDate != nil } diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index 41dc3639c737..59f47afe1658 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -23,12 +23,12 @@ extension UIAlertController { /// A dialog giving the user the choice between loading the current version a post or its autosaved version. static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapSaveOption: @escaping () -> Void, didTapAutosaveOption: @escaping () -> Void) -> UIAlertController { - let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when use has the option to load unsaved changes") + let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") let body = NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:", comment: "Message displayed in popup when user has the option to load unsaved changes") let saveLabel = NSLocalizedString("From this device", comment: "Message displayed in popup indicating date of change on device") let autosaveLabel = NSLocalizedString("From another device", comment: "Message displayed in popup indicating date of change on another device") - let savedOnLabel = NSLocalizedString("Saved on", comment: "Label shown on save dates") + let savedOnLabel = NSLocalizedString("Saved on", comment: "Label shown on post save dates when user has the option to load unsaved changes") let saveDateFormatted = savedOnLabel + " " + dateAndTime(for: saveDate) let autosaveDateFormatted = savedOnLabel + " " + dateAndTime(for: autosaveDate) let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") From 70e2da781cadb85a8494730d47a8cffc4a3ca276 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 21 Nov 2019 23:22:29 -0300 Subject: [PATCH 11/29] Tighten access control --- WordPress/Classes/ViewRelated/Post/EditPostViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift index 6056d065a784..b2d7a778c51f 100644 --- a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift @@ -15,7 +15,7 @@ class EditPostViewController: UIViewController { @objc var openWithPostPost: Bool = false /// appear with media pre-inserted into the post var insertedMedia: [Media]? = nil - let shouldLoadAutosave: Bool + private let shouldLoadAutosave: Bool @objc fileprivate(set) var post: Post? fileprivate var hasShownEditor = false From 7903a99c79fabb040dc720d2e0eed273f346a25f Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 20:22:18 -0300 Subject: [PATCH 12/29] Update release notes for load autosave feature --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 354fbba3a506..7559cf98643c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,7 @@ +13.8 +----- +* When a post has an autosave, the autosave version can be loaded into the editor. + 13.7 ----- * Updated the mobile apps blog address to a non-retired blog. From c067c91ef7e75e72e73bbcfe6ff33be8f91100b2 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 20:32:47 -0300 Subject: [PATCH 13/29] Make autosave status string name more descriptive --- .../Classes/ViewRelated/Post/PostCardStatusViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index 4b989cd443d1..a34be1a85b47 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -60,7 +60,7 @@ class PostCardStatusViewModel: NSObject { } else if post.remoteStatus == .pushing { return NSLocalizedString("Uploading post...", comment: "Message displayed on a post's card when the post has failed to upload") } else if post.isAutosaveRevisionAvailable { - return StatusMessages.autosave + return StatusMessages.hasUnsavedChanges } else { return post.statusForDisplay() } @@ -268,7 +268,7 @@ class PostCardStatusViewModel: NSObject { comment: "Message displayed on a post's card when the post has failed to upload") static let localChanges = NSLocalizedString("Local changes", comment: "A status label for a post that only exists on the user's iOS device, and has not yet been published to their blog.") - static let autosave = NSLocalizedString("You've made unsaved changes to this post", + static let hasUnsavedChanges = NSLocalizedString("You've made unsaved changes to this post", comment: "Message displayed on a post's card when the post has unsaved changes") } } From 71cc8cc9a698ee9d94ad4ab077019d603221200b Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 20:57:32 -0300 Subject: [PATCH 14/29] Join autosave localized copy into one string Using one string gives translators more freedom in structuring the copy for each locale. --- .../Post/AlertViewController+Autosave.swift | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index 59f47afe1658..ff9ba48b299f 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -24,23 +24,14 @@ extension UIAlertController { static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapSaveOption: @escaping () -> Void, didTapAutosaveOption: @escaping () -> Void) -> UIAlertController { let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") - let body = NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:", comment: "Message displayed in popup when user has the option to load unsaved changes") - let saveLabel = NSLocalizedString("From this device", comment: "Message displayed in popup indicating date of change on device") - let autosaveLabel = NSLocalizedString("From another device", comment: "Message displayed in popup indicating date of change on another device") - let savedOnLabel = NSLocalizedString("Saved on", comment: "Label shown on post save dates when user has the option to load unsaved changes") - let saveDateFormatted = savedOnLabel + " " + dateAndTime(for: saveDate) - let autosaveDateFormatted = savedOnLabel + " " + dateAndTime(for: autosaveDate) + let saveDateFormatted = dateAndTime(for: saveDate) + let autosaveDateFormatted = dateAndTime(for: autosaveDate) + let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes"), saveDateFormatted, autosaveDateFormatted) + let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") - let message = - body + "\n\n" + - saveLabel + "\n" + - saveDateFormatted + "\n\n" + - autosaveLabel + "\n" + - autosaveDateFormatted + "\n" - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in didTapSaveOption() From 1fded19bd464bb2fe934cdb2abfcdc3a51ebe4c7 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 21:08:48 -0300 Subject: [PATCH 15/29] Clarified autosave copy comment for translators --- .../Classes/ViewRelated/Post/AlertViewController+Autosave.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index ff9ba48b299f..e31094a89cba 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -27,7 +27,7 @@ extension UIAlertController { let saveDateFormatted = dateAndTime(for: saveDate) let autosaveDateFormatted = dateAndTime(for: autosaveDate) - let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes"), saveDateFormatted, autosaveDateFormatted) + let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively."), saveDateFormatted, autosaveDateFormatted) let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") From 599b62c08ba30b3f07a83e7fb50112b63a6b9996 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 21:27:16 -0300 Subject: [PATCH 16/29] Improved post.hasAutosaveRevision to read better Was previously `post.isAutosaveRevisionAvailable`. --- .../Classes/Extensions/AbstractPost+PostInformation.swift | 2 +- .../Classes/ViewRelated/Post/PostCardStatusViewModel.swift | 4 ++-- .../Classes/ViewRelated/Post/PostListViewController.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 9d88490a92c4..902e9924def4 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -18,7 +18,7 @@ extension AbstractPost: ImageSourceInformation { } /// An autosave revision may include post title, content and/or excerpt. - var isAutosaveRevisionAvailable: Bool { + var hasAutosaveRevision: Bool { return autosaveModifiedDate != nil } } diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index a34be1a85b47..5ddbda84e1ea 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -59,7 +59,7 @@ class PostCardStatusViewModel: NSObject { return generateFailedStatusMessage() } else if post.remoteStatus == .pushing { return NSLocalizedString("Uploading post...", comment: "Message displayed on a post's card when the post has failed to upload") - } else if post.isAutosaveRevisionAvailable { + } else if post.hasAutosaveRevision { return StatusMessages.hasUnsavedChanges } else { return post.statusForDisplay() @@ -100,7 +100,7 @@ class PostCardStatusViewModel: NSObject { return (autoUploadAction == .upload || post.wasAutoUploadCancelled) ? .warning : .error } - if post.isAutosaveRevisionAvailable { + if post.hasAutosaveRevision { return .warning(.shade40) } diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index d2f8b22fe876..4ca58828333d 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -498,7 +498,7 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe } // Autosaves are ignored for posts with local changes. - if !post.hasLocalChanges(), post.isAutosaveRevisionAvailable, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { + if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapSaveOption: { [weak self] in self?.openEditor(with: post, shouldLoadAutosave: false) }, didTapAutosaveOption: { [weak self] in From 9c7146fd5db7f1fe4a4dd69035a93914efb64c25 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 21:39:57 -0300 Subject: [PATCH 17/29] Simplified autosave dialog closures --- .../ViewRelated/Post/AlertViewController+Autosave.swift | 6 +++--- .../Classes/ViewRelated/Post/PostListViewController.swift | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index e31094a89cba..8a5d072d32a7 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -21,7 +21,7 @@ extension UIAlertController { } /// A dialog giving the user the choice between loading the current version a post or its autosaved version. - static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapSaveOption: @escaping () -> Void, didTapAutosaveOption: @escaping () -> Void) -> UIAlertController { + static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ shouldLoadAutosave: Bool) -> Void) -> UIAlertController { let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") @@ -34,10 +34,10 @@ extension UIAlertController { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in - didTapSaveOption() + didTapOption(false) }) alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in - didTapAutosaveOption() + didTapOption(true) }) return alertController diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 4ca58828333d..82453e8497e3 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -499,10 +499,8 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe // Autosaves are ignored for posts with local changes. if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { - let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapSaveOption: { [weak self] in - self?.openEditor(with: post, shouldLoadAutosave: false) - }, didTapAutosaveOption: { [weak self] in - self?.openEditor(with: post, shouldLoadAutosave: true) + let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { [weak self] shouldLoadAutosave in + self?.openEditor(with: post, shouldLoadAutosave: shouldLoadAutosave) }) present(autosaveViewController, animated: true) } else { From 6dc90284975d1370142fff7448a2fa3a9addf8dc Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 24 Nov 2019 21:58:35 -0300 Subject: [PATCH 18/29] Renamed post.loadAutosaveRevision for clarity --- WordPress/Classes/Utility/Editor/EditorFactory.swift | 10 +++++----- .../ViewControllers/AztecPostViewController.swift | 8 ++++---- .../Gutenberg/GutenbergViewController.swift | 8 ++++---- .../Post/AlertViewController+Autosave.swift | 2 +- .../ViewRelated/Post/EditPostViewController.swift | 12 ++++++------ .../ViewRelated/Post/PostEditor+Publish.swift | 4 ++-- WordPress/Classes/ViewRelated/Post/PostEditor.swift | 2 +- .../ViewRelated/Post/PostListViewController.swift | 10 +++++----- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/WordPress/Classes/Utility/Editor/EditorFactory.swift b/WordPress/Classes/Utility/Editor/EditorFactory.swift index 42061d345421..ce2a209eb798 100644 --- a/WordPress/Classes/Utility/Editor/EditorFactory.swift +++ b/WordPress/Classes/Utility/Editor/EditorFactory.swift @@ -12,16 +12,16 @@ class EditorFactory { // MARK: - Editor: Instantiation - func instantiateEditor(for post: AbstractPost, shouldLoadAutosave: Bool = false, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController { + func instantiateEditor(for post: AbstractPost, loadAutosaveRevision: Bool = false, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController { if gutenbergSettings.mustUseGutenberg(for: post) { - return createGutenbergVC(with: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) + return createGutenbergVC(with: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor) } else { - return AztecPostViewController(post: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) + return AztecPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor) } } - private func createGutenbergVC(with post: AbstractPost, shouldLoadAutosave: Bool, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController { - let gutenbergVC = GutenbergViewController(post: post, shouldLoadAutosave: shouldLoadAutosave, replaceEditor: replaceEditor) + private func createGutenbergVC(with post: AbstractPost, loadAutosaveRevision: Bool, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController { + let gutenbergVC = GutenbergViewController(post: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor) if gutenbergSettings.shouldAutoenableGutenberg(for: post) { gutenbergSettings.setGutenbergEnabled(true, for: post.blog, source: .onBlockPostOpening) diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index fca53296472b..4940ba27b7b7 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -342,7 +342,7 @@ class AztecPostViewController: UIViewController, PostEditor { } } - var shouldLoadAutosave: Bool + var loadAutosaveRevision: Bool /// Active Downloads /// @@ -444,14 +444,14 @@ class AztecPostViewController: UIViewController, PostEditor { /// required init( post: AbstractPost, - shouldLoadAutosave: Bool = false, + loadAutosaveRevision: Bool = false, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { precondition(post.managedObjectContext != nil) self.post = post - self.shouldLoadAutosave = shouldLoadAutosave + self.loadAutosaveRevision = loadAutosaveRevision self.replaceEditor = replaceEditor self.editorSession = editorSession ?? PostEditorAnalyticsSession(editor: .classic, post: post) @@ -489,7 +489,7 @@ class AztecPostViewController: UIViewController, PostEditor { WPFontManager.loadNotoFontFamily() registerAttachmentImageProviders() - createRevisionOfPost(shouldLoadAutosave: shouldLoadAutosave) + createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision) // Setup configureNavigationBar() diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index 79a942a9c6c6..30b2ae749f93 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -163,7 +163,7 @@ class GutenbergViewController: UIViewController, PostEditor { } } - var shouldLoadAutosave: Bool + var loadAutosaveRevision: Bool let navigationBarManager = PostEditorNavigationBarManager() @@ -220,12 +220,12 @@ class GutenbergViewController: UIViewController, PostEditor { // MARK: - Initializers required init( post: AbstractPost, - shouldLoadAutosave: Bool = false, + loadAutosaveRevision: Bool = false, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { self.post = post - self.shouldLoadAutosave = shouldLoadAutosave + self.loadAutosaveRevision = loadAutosaveRevision self.replaceEditor = replaceEditor verificationPromptHelper = AztecVerificationPromptHelper(account: self.post.blog.account) @@ -253,7 +253,7 @@ class GutenbergViewController: UIViewController, PostEditor { override func viewDidLoad() { super.viewDidLoad() - createRevisionOfPost(shouldLoadAutosave: shouldLoadAutosave) + createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision) setupGutenbergView() configureNavigationBar() refreshInterface() diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index 8a5d072d32a7..45077c061bcc 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -21,7 +21,7 @@ extension UIAlertController { } /// A dialog giving the user the choice between loading the current version a post or its autosaved version. - static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ shouldLoadAutosave: Bool) -> Void) -> UIAlertController { + static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ loadAutosaveRevision: Bool) -> Void) -> UIAlertController { let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") diff --git a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift index b2d7a778c51f..9b0c61119e63 100644 --- a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift @@ -15,7 +15,7 @@ class EditPostViewController: UIViewController { @objc var openWithPostPost: Bool = false /// appear with media pre-inserted into the post var insertedMedia: [Media]? = nil - private let shouldLoadAutosave: Bool + private let loadAutosaveRevision: Bool @objc fileprivate(set) var post: Post? fileprivate var hasShownEditor = false @@ -43,8 +43,8 @@ class EditPostViewController: UIViewController { /// Initialize as an editor with the provided post /// /// - Parameter post: post to edit - @objc convenience init(post: Post, shouldLoadAutosave: Bool = false) { - self.init(post: post, blog: post.blog, shouldLoadAutosave: shouldLoadAutosave) + @objc convenience init(post: Post, loadAutosaveRevision: Bool = false) { + self.init(post: post, blog: post.blog, loadAutosaveRevision: loadAutosaveRevision) } @@ -61,9 +61,9 @@ class EditPostViewController: UIViewController { /// - post: the post to edit /// - blog: the blog to create a post for, if post is nil /// - Note: it's preferable to use one of the convenience initializers - fileprivate init(post: Post?, blog: Blog, shouldLoadAutosave: Bool = false) { + fileprivate init(post: Post?, blog: Blog, loadAutosaveRevision: Bool = false) { self.post = post - self.shouldLoadAutosave = shouldLoadAutosave + self.loadAutosaveRevision = loadAutosaveRevision if let post = post { if !post.originalIsDraft() { editingExistingPost = true @@ -127,7 +127,7 @@ class EditPostViewController: UIViewController { fileprivate func showEditor() { let editor = editorFactory.instantiateEditor( for: postToEdit(), - shouldLoadAutosave: shouldLoadAutosave, + loadAutosaveRevision: loadAutosaveRevision, replaceEditor: { [weak self] (editor, replacement) in self?.replaceEditor(editor: editor, replacement: replacement) }) diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index f440f54b2841..32e28351f7e9 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -400,7 +400,7 @@ extension PostEditor where Self: UIViewController { } // TODO: Rip this out and put it into the PostService - func createRevisionOfPost(shouldLoadAutosave: Bool = false) { + func createRevisionOfPost(loadAutosaveRevision: Bool = false) { if post.isLocalRevision, post.original?.postTitle == nil, post.original?.content == nil { // Editing a locally made revision has bit of weirdness in how autosave and @@ -441,7 +441,7 @@ extension PostEditor where Self: UIViewController { managedObjectContext.performAndWait { post = self.post.createRevision() - if shouldLoadAutosave { + if loadAutosaveRevision { post.postTitle = post.autosaveTitle post.mt_excerpt = post.autosaveExcerpt post.content = post.autosaveContent diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor.swift b/WordPress/Classes/ViewRelated/Post/PostEditor.swift index 6ee6453f2096..47b4883d4f3b 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -27,7 +27,7 @@ protocol PostEditor: class, UIViewControllerTransitioningDelegate { /// If true, apply autosave content when the editor creates a revision. /// - var shouldLoadAutosave: Bool { get set } + var loadAutosaveRevision: Bool { get set } /// Closure to be executed when the editor gets closed. /// diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 82453e8497e3..99afecf8cfd3 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -499,17 +499,17 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe // Autosaves are ignored for posts with local changes. if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { - let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { [weak self] shouldLoadAutosave in - self?.openEditor(with: post, shouldLoadAutosave: shouldLoadAutosave) + let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { [weak self] loadAutosaveRevision in + self?.openEditor(with: post, loadAutosaveRevision: loadAutosaveRevision) }) present(autosaveViewController, animated: true) } else { - openEditor(with: post, shouldLoadAutosave: false) + openEditor(with: post, loadAutosaveRevision: false) } } - private func openEditor(with post: Post, shouldLoadAutosave: Bool) { - let editor = EditPostViewController(post: post, shouldLoadAutosave: shouldLoadAutosave) + private func openEditor(with post: Post, loadAutosaveRevision: Bool) { + let editor = EditPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision) editor.modalPresentationStyle = .fullScreen present(editor, animated: false) WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: post) From a1e0ca52c8c57ee9c6b03fab132ad765c2905bd0 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 25 Nov 2019 23:47:00 -0300 Subject: [PATCH 19/29] Removed trailing whitespace from line --- .../Classes/ViewRelated/Post/AlertViewController+Autosave.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift index 45077c061bcc..524aa6b474f4 100644 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift @@ -28,7 +28,7 @@ extension UIAlertController { let saveDateFormatted = dateAndTime(for: saveDate) let autosaveDateFormatted = dateAndTime(for: autosaveDate) let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively."), saveDateFormatted, autosaveDateFormatted) - + let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") From dd7674867b5577341d58f2809913a4c012e11d41 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 28 Nov 2019 23:51:27 -0300 Subject: [PATCH 20/29] Include post autosave identifier in core data Post autosave identifier is now persisted via core data to `AbstractPost`. --- MIGRATIONS.md | 7 + WordPress/Classes/Models/AbstractPost.h | 1 + WordPress/Classes/Models/AbstractPost.m | 1 + WordPress/Classes/Services/PostService.m | 1 + .../WordPress.xcdatamodeld/.xccurrentversion | 2 +- .../WordPress 94.xcdatamodel/contents | 843 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 8 +- WordPress/WordPressTest/PostBuilder.swift | 1 + 8 files changed, 860 insertions(+), 4 deletions(-) create mode 100644 WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 2c30aac2716d..28c95f897947 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -3,6 +3,13 @@ This file documents changes in the data model. Please explain any changes to the data model as well as any custom migrations. + +## WordPress 94 + +@guarani 2019-11-28 + +- `AbstractPost` added `autosaveIdentifier` (`nullable` `NSNumber`) property. + ## WordPress 93 @guarani 2019-10-27 diff --git a/WordPress/Classes/Models/AbstractPost.h b/WordPress/Classes/Models/AbstractPost.h index 407f0532a8ed..3175e05fbbc0 100644 --- a/WordPress/Classes/Models/AbstractPost.h +++ b/WordPress/Classes/Models/AbstractPost.h @@ -56,6 +56,7 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { @property (nonatomic, copy, nullable) NSString *autosaveExcerpt; @property (nonatomic, copy, nullable) NSString *autosaveTitle; @property (nonatomic, copy, nullable) NSDate *autosaveModifiedDate; +@property (nonatomic, copy, nullable) NSNumber *autosaveIdentifier; // Revision management - (AbstractPost *)createRevision; diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index c7a86672f5b5..c41a790765d5 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -37,6 +37,7 @@ @implementation AbstractPost @dynamic autosaveExcerpt; @dynamic autosaveTitle; @dynamic autosaveModifiedDate; +@dynamic autosaveIdentifier; @synthesize restorableStatus; diff --git a/WordPress/Classes/Services/PostService.m b/WordPress/Classes/Services/PostService.m index a0b9c9ece1fb..b11de40fa978 100644 --- a/WordPress/Classes/Services/PostService.m +++ b/WordPress/Classes/Services/PostService.m @@ -727,6 +727,7 @@ - (void)updatePost:(AbstractPost *)post withRemotePost:(RemotePost *)remotePost post.autosaveExcerpt = remotePost.autosave.excerpt; post.autosaveContent = remotePost.autosave.content; post.autosaveModifiedDate = remotePost.autosave.modifiedDate; + post.autosaveIdentifier = remotePost.autosave.identifier; if ([post isKindOfClass:[Page class]]) { Page *pagePost = (Page *)post; diff --git a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion index 63d4048a9798..8f55dabda40d 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion +++ b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - WordPress 93.xcdatamodel + WordPress 94.xcdatamodel diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents new file mode 100644 index 000000000000..45e5687b878f --- /dev/null +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents @@ -0,0 +1,843 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 290aaf3983af..32c9f2b73529 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2460,6 +2460,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 = ""; }; + 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 94.xcdatamodel"; sourceTree = ""; }; 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 93.xcdatamodel"; sourceTree = ""; }; 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlertViewController+Autosave.swift"; sourceTree = ""; }; 33D5016BDA00B45DFCAF3818 /* Pods-WordPressNotificationServiceExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationServiceExtension/Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; sourceTree = ""; }; @@ -5084,7 +5085,7 @@ path = Networking; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( F14B5F6F208E648200439554 /* config */, @@ -9840,7 +9841,7 @@ bg, sk, ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -14944,6 +14945,7 @@ E125443B12BF5A7200D87A0A /* WordPress.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */, 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */, 57CCB37E2358E5DC003ECD0C /* WordPress 92.xcdatamodel */, E63EC3632356633B008CEB16 /* WordPress 91.xcdatamodel */, @@ -15038,7 +15040,7 @@ 8350E15911D28B4A00A7B073 /* WordPress.xcdatamodel */, E125443D12BF5A7200D87A0A /* WordPress 2.xcdatamodel */, ); - currentVersion = 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */; + currentVersion = 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */; name = WordPress.xcdatamodeld; path = Classes/WordPress.xcdatamodeld; sourceTree = ""; diff --git a/WordPress/WordPressTest/PostBuilder.swift b/WordPress/WordPressTest/PostBuilder.swift index b363da672e1d..13e773506360 100644 --- a/WordPress/WordPressTest/PostBuilder.swift +++ b/WordPress/WordPressTest/PostBuilder.swift @@ -68,6 +68,7 @@ class PostBuilder { post.autosaveExcerpt = "b" post.autosaveContent = "c" post.autosaveModifiedDate = Date() + post.autosaveIdentifier = 1 return self } From cce27013e9a355c2c377e3857de32dd503c62f05 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 28 Nov 2019 23:54:35 -0300 Subject: [PATCH 21/29] Use identifier to determine autosave presence An autosave is available iff there is an autosave identifier greater than zero. --- .../Classes/Extensions/AbstractPost+PostInformation.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 902e9924def4..39bc42b56466 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -19,6 +19,9 @@ extension AbstractPost: ImageSourceInformation { /// An autosave revision may include post title, content and/or excerpt. var hasAutosaveRevision: Bool { - return autosaveModifiedDate != nil + guard let autosaveRevisionIdentifier = autosaveIdentifier?.intValue else { + return false + } + return autosaveRevisionIdentifier > 0 } } From e8a3107b30fbc9eb807d260bd77b4cc61888e917 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 29 Nov 2019 00:42:52 -0300 Subject: [PATCH 22/29] Abstract edit post logic into separate file To keep PostListViewController lean, move the logic for editing posts (or loading their autosave revision) into a presenter. --- .../Post/PostListEditorPresenter.swift | 27 +++++++++++++++++++ .../Post/PostListViewController.swift | 17 +----------- WordPress/WordPress.xcodeproj/project.pbxproj | 4 +++ 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift diff --git a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift new file mode 100644 index 000000000000..f3d8580a3abb --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift @@ -0,0 +1,27 @@ +import Foundation + +/// Handle a user tapping a post in the post list. If an autosave revision is available, the user +/// is given the option through a dialog alert to load it (or the regular post) into the editor. +/// Analytics are also tracked. +enum PostListEditorPresenter { + + static func handle(post: Post, in postListViewController: PostListViewController) { + + // Autosaves are ignored for posts with local changes. + if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { + let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { loadAutosaveRevision in + openEditor(with: post, loadAutosaveRevision: loadAutosaveRevision, in: postListViewController) + }) + postListViewController.present(autosaveViewController, animated: true) + } else { + openEditor(with: post, loadAutosaveRevision: false, in: postListViewController) + } + } + + private static func openEditor(with post: Post, loadAutosaveRevision: Bool, in postListViewController: PostListViewController) { + let editor = EditPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision) + editor.modalPresentationStyle = .fullScreen + postListViewController.present(editor, animated: false) + WPAppAnalytics.track(.postListEditAction, withProperties: postListViewController.propertiesForAnalytics(), with: post) + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 9ab5deaa2ad3..5b80112c26b9 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -519,22 +519,7 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe return } - // Autosaves are ignored for posts with local changes. - if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { - let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { [weak self] loadAutosaveRevision in - self?.openEditor(with: post, loadAutosaveRevision: loadAutosaveRevision) - }) - present(autosaveViewController, animated: true) - } else { - openEditor(with: post, loadAutosaveRevision: false) - } - } - - private func openEditor(with post: Post, loadAutosaveRevision: Bool) { - let editor = EditPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision) - editor.modalPresentationStyle = .fullScreen - present(editor, animated: false) - WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: post) + PostListEditorPresenter.handle(post: post, in: self) } func presentAlertForPostBeingUploaded() { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 32c9f2b73529..878be30882ac 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -325,6 +325,7 @@ 31C9F82E1A2368A2008BB945 /* BlogDetailHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 31C9F82D1A2368A2008BB945 /* BlogDetailHeaderView.m */; }; 31EC15081A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m in Sources */ = {isa = PBXBuildFile; fileRef = 31EC15071A5B6675009FC8B3 /* WPStyleGuide+Suggestions.m */; }; 32A2ED4D23788989005C5D6A /* AlertViewController+Autosave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */; }; + 32CA6EC02390C61F00B51347 /* PostListEditorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */; }; 37022D931981C19000F322B7 /* VerticallyStackedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 37022D901981BF9200F322B7 /* VerticallyStackedButton.m */; }; 374CB16215B93C0800DD0EBC /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374CB16115B93C0800DD0EBC /* AudioToolbox.framework */; }; 37EAAF4D1A11799A006D6306 /* CircularImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAAF4C1A11799A006D6306 /* CircularImageView.swift */; }; @@ -2463,6 +2464,7 @@ 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 94.xcdatamodel"; sourceTree = ""; }; 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 93.xcdatamodel"; sourceTree = ""; }; 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlertViewController+Autosave.swift"; sourceTree = ""; }; + 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListEditorPresenter.swift; sourceTree = ""; }; 33D5016BDA00B45DFCAF3818 /* Pods-WordPressNotificationServiceExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationServiceExtension/Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; sourceTree = ""; }; 368127CE6F1CA15EC198147D /* Pods-WordPressTodayWidget.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTodayWidget.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTodayWidget/Pods-WordPressTodayWidget.debug.xcconfig"; sourceTree = ""; }; 37022D8F1981BF9200F322B7 /* VerticallyStackedButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VerticallyStackedButton.h; sourceTree = ""; }; @@ -7654,6 +7656,7 @@ 5D146EB9189857ED0068FDC6 /* FeaturedImageViewController.h */, 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */, 93414DE41E2D25AE003143A3 /* PostEditorState.swift */, + 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */, 430693731DD25F31009398A2 /* PostPost.storyboard */, 43D54D121DCAA070007F575F /* PostPostViewController.swift */, ACBAB6840E1247F700F38795 /* PostPreviewViewController.h */, @@ -10813,6 +10816,7 @@ E102B7901E714F24007928E8 /* RecentSitesService.swift in Sources */, E1D7FF381C74EB0E00E7E5E5 /* PlanService.swift in Sources */, F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */, + 32CA6EC02390C61F00B51347 /* PostListEditorPresenter.swift in Sources */, 5D7DEA2919D488DD0032EE77 /* WPStyleGuide+ReaderComments.swift in Sources */, 40C403F62215D66A00E8C894 /* TopViewedPostStatsRecordValue+CoreDataProperties.swift in Sources */, 82FC61251FA8ADAD00A1757E /* ActivityListViewController.swift in Sources */, From c5d09aeaa8a57cabd1452ed32ee47a06f233a30e Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 29 Nov 2019 00:45:18 -0300 Subject: [PATCH 23/29] Define loadAutosaveRevision in editor initializer. All editors (Gutenberg and Aztec) support loading autosave revisions. A required initializer is now defined in the PostEditor protocol to ensure this dependency is always satisfied. --- .../AztecPostViewController.swift | 10 +++------- .../Gutenberg/GutenbergViewController.swift | 4 +++- .../Classes/ViewRelated/Post/PostEditor.swift | 18 ++++++++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 7b1e7c00f940..b78ff081a766 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -342,7 +342,9 @@ class AztecPostViewController: UIViewController, PostEditor { } } - var loadAutosaveRevision: Bool + /// If true, apply autosave content when the editor creates a revision. + /// + private let loadAutosaveRevision: Bool /// Active Downloads /// @@ -436,12 +438,6 @@ class AztecPostViewController: UIViewController, PostEditor { // MARK: - Initializers - /// Initializer - /// - /// - Parameters: - /// - post: the post to edit in this VC. Must be already assigned to a `ManagedObjectContext` - /// since that's necessary for the edits to be saved. - /// required init( post: AbstractPost, loadAutosaveRevision: Bool = false, diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index b0511a01dd41..5d0ec8743fc0 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -163,7 +163,9 @@ class GutenbergViewController: UIViewController, PostEditor { } } - var loadAutosaveRevision: Bool + /// If true, apply autosave content when the editor creates a revision. + /// + private let loadAutosaveRevision: Bool let navigationBarManager = PostEditorNavigationBarManager() diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor.swift b/WordPress/Classes/ViewRelated/Post/PostEditor.swift index 47b4883d4f3b..4b959a0a69f8 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -25,10 +25,6 @@ protocol PostEditor: class, UIViewControllerTransitioningDelegate { /// var post: AbstractPost { get set } - /// If true, apply autosave content when the editor creates a revision. - /// - var loadAutosaveRevision: Bool { get set } - /// Closure to be executed when the editor gets closed. /// var onClose: ((_ changesSaved: Bool, _ shouldShowPostPost: Bool) -> Void)? { get set } @@ -37,6 +33,20 @@ protocol PostEditor: class, UIViewControllerTransitioningDelegate { /// var isOpenedDirectlyForPhotoPost: Bool { get set } + /// Initializer + /// + /// - Parameters: + /// - post: the post to edit. Must be already assigned to a `ManagedObjectContext` since + /// that's necessary for the edits to be saved. + /// - loadAutosaveRevision: if true, apply autosave content when the editor creates a revision. + /// - replaceEditor: a closure that handles switching from one editor to another + /// - editorSession: post editor analytics session + init( + post: AbstractPost, + loadAutosaveRevision: Bool, + replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), + editorSession: PostEditorAnalyticsSession?) + /// Media items to be inserted on the post after creation /// /// - Parameter media: the media items to add From 717654d2033b3db870337d52bbdddb1b9920c52c Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 29 Nov 2019 00:55:27 -0300 Subject: [PATCH 24/29] Remove dialog from UIAlertController namespace The autosave dialog was previously on the UIAlertController static namespace, even though it has a one-off use case. Now it's in a custom presenter and kept private. --- .../Post/AlertViewController+Autosave.swift | 45 ----------------- .../Post/PostListEditorPresenter.swift | 48 +++++++++++++++++-- WordPress/WordPress.xcodeproj/project.pbxproj | 4 -- 3 files changed, 45 insertions(+), 52 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift diff --git a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift b/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift deleted file mode 100644 index 524aa6b474f4..000000000000 --- a/WordPress/Classes/ViewRelated/Post/AlertViewController+Autosave.swift +++ /dev/null @@ -1,45 +0,0 @@ -import UIKit - -extension UIAlertController { - - private static let dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .none - return formatter - }() - - private static let timeFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - return formatter - }() - - private class func dateAndTime(for date: Date) -> String { - return dateFormatter.string(from: date) + " @ " + timeFormatter.string(from: date) - } - - /// A dialog giving the user the choice between loading the current version a post or its autosaved version. - static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ loadAutosaveRevision: Bool) -> Void) -> UIAlertController { - - let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") - - let saveDateFormatted = dateAndTime(for: saveDate) - let autosaveDateFormatted = dateAndTime(for: autosaveDate) - let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively."), saveDateFormatted, autosaveDateFormatted) - - let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") - let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") - - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in - didTapOption(false) - }) - alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in - didTapOption(true) - }) - - return alertController - } -} diff --git a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift index f3d8580a3abb..6b70d4730620 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift @@ -1,7 +1,8 @@ import Foundation -/// Handle a user tapping a post in the post list. If an autosave revision is available, the user -/// is given the option through a dialog alert to load it (or the regular post) into the editor. +/// Handle a user tapping a post in the post list. If an autosave revision is available, give the +/// user the option through a dialog alert to load the autosave (or just load the regular post) into +/// the editor. /// Analytics are also tracked. enum PostListEditorPresenter { @@ -9,7 +10,7 @@ enum PostListEditorPresenter { // Autosaves are ignored for posts with local changes. if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate { - let autosaveViewController = UIAlertController.autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { loadAutosaveRevision in + let autosaveViewController = autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { loadAutosaveRevision in openEditor(with: post, loadAutosaveRevision: loadAutosaveRevision, in: postListViewController) }) postListViewController.present(autosaveViewController, animated: true) @@ -24,4 +25,45 @@ enum PostListEditorPresenter { postListViewController.present(editor, animated: false) WPAppAnalytics.track(.postListEditAction, withProperties: postListViewController.propertiesForAnalytics(), with: post) } + + private static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + + private static let timeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + private static func dateAndTime(for date: Date) -> String { + return dateFormatter.string(from: date) + " @ " + timeFormatter.string(from: date) + } + + /// A dialog giving the user the choice between loading the current version a post or its autosaved version. + private static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ loadAutosaveRevision: Bool) -> Void) -> UIAlertController { + + let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes") + + let saveDateFormatted = dateAndTime(for: saveDate) + let autosaveDateFormatted = dateAndTime(for: autosaveDate) + let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively."), saveDateFormatted, autosaveDateFormatted) + + let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device") + let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device") + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in + didTapOption(false) + }) + alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in + didTapOption(true) + }) + + return alertController + } } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 878be30882ac..092700a1c53a 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -324,7 +324,6 @@ 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 */; }; - 32A2ED4D23788989005C5D6A /* AlertViewController+Autosave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */; }; 32CA6EC02390C61F00B51347 /* PostListEditorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */; }; 37022D931981C19000F322B7 /* VerticallyStackedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 37022D901981BF9200F322B7 /* VerticallyStackedButton.m */; }; 374CB16215B93C0800DD0EBC /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374CB16115B93C0800DD0EBC /* AudioToolbox.framework */; }; @@ -2463,7 +2462,6 @@ 31FA16CC1A49B3C0003E1887 /* WordPress 25.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 25.xcdatamodel"; sourceTree = ""; }; 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 94.xcdatamodel"; sourceTree = ""; }; 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 93.xcdatamodel"; sourceTree = ""; }; - 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlertViewController+Autosave.swift"; sourceTree = ""; }; 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListEditorPresenter.swift; sourceTree = ""; }; 33D5016BDA00B45DFCAF3818 /* Pods-WordPressNotificationServiceExtension.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationServiceExtension/Pods-WordPressNotificationServiceExtension.release-internal.xcconfig"; sourceTree = ""; }; 368127CE6F1CA15EC198147D /* Pods-WordPressTodayWidget.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTodayWidget.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTodayWidget/Pods-WordPressTodayWidget.debug.xcconfig"; sourceTree = ""; }; @@ -5965,7 +5963,6 @@ 5DF3DD6A1A93772D0051A229 /* Views */ = { isa = PBXGroup; children = ( - 32A2ED4C23788989005C5D6A /* AlertViewController+Autosave.swift */, 597421B01CEB6874005D5F38 /* ConfigurablePostView.h */, 57D6C83F229498C4003DDC7E /* InteractivePostView.swift */, 575E126E229779E70041B3EB /* RestorePostTableViewCell.swift */, @@ -11613,7 +11610,6 @@ FF0AAE0A1A150A560089841D /* WPProgressTableViewCell.m in Sources */, D8A3A5B5206A4C7800992576 /* StockPhotosPicker.swift in Sources */, E60C2ED71DE5075100488630 /* ReaderCommentCell.swift in Sources */, - 32A2ED4D23788989005C5D6A /* AlertViewController+Autosave.swift in Sources */, 5DBCD9D518F35D7500B32229 /* ReaderTopicService.m in Sources */, 403F57BC20E5CA6A004E889A /* RewindStatusRow.swift in Sources */, E6DE44671B90D251000FA7EF /* ReaderHelpers.swift in Sources */, From b373cf00de1548f1d40b3f8ff0a895f67c89a919 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 29 Nov 2019 01:53:29 -0300 Subject: [PATCH 25/29] Posts with local changes do not show autosave Added check to post card status view model to ignore autosave status label if post has local changes. --- .../Classes/ViewRelated/Post/PostCardStatusViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index 5ddbda84e1ea..f1d27ae46531 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -59,7 +59,7 @@ class PostCardStatusViewModel: NSObject { return generateFailedStatusMessage() } else if post.remoteStatus == .pushing { return NSLocalizedString("Uploading post...", comment: "Message displayed on a post's card when the post has failed to upload") - } else if post.hasAutosaveRevision { + } else if !post.hasLocalChanges() && post.hasAutosaveRevision { return StatusMessages.hasUnsavedChanges } else { return post.statusForDisplay() From 3a1dc36d1a7d73d75850c21fe2081a0ce7b5011a Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 29 Nov 2019 02:11:11 -0300 Subject: [PATCH 26/29] Autosave status test uses sync remote status Using a "local" remote status for the post is wrong because it interfers with the requirement that posts with local changes should not show the status. --- WordPress/WordPressTest/PostCardCellTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/WordPressTest/PostCardCellTests.swift b/WordPress/WordPressTest/PostCardCellTests.swift index 970e516e3876..e36daca80974 100644 --- a/WordPress/WordPressTest/PostCardCellTests.swift +++ b/WordPress/WordPressTest/PostCardCellTests.swift @@ -491,7 +491,7 @@ class PostCardCellTests: XCTestCase { } func testShowsUnsavedChangesMessageWhenPostHasAutosave() { - let post = PostBuilder(context).with(remoteStatus: .local).autosaved().build() + let post = PostBuilder(context).with(remoteStatus: .sync).autosaved().build() postCell.configure(with: post) From 785316ef5e096ce13d470b66af8786571e911a15 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 1 Dec 2019 22:20:06 -0300 Subject: [PATCH 27/29] Remove autosaveIdentifier default value of zero --- .../WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents index 45e5687b878f..b4da9e313b1c 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents @@ -1,9 +1,9 @@ - + - + From a1a034de9a206e8605e40f17a22781bc98a429d5 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Sun, 1 Dec 2019 22:38:13 -0300 Subject: [PATCH 28/29] Make PostListEditorPresenter struct not enum An enum has the advantage of not being able to be instantiated, but does hinder the readability of the code. --- .../Classes/ViewRelated/Post/PostListEditorPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift index 6b70d4730620..28e8a8019861 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift @@ -4,7 +4,7 @@ import Foundation /// user the option through a dialog alert to load the autosave (or just load the regular post) into /// the editor. /// Analytics are also tracked. -enum PostListEditorPresenter { +struct PostListEditorPresenter { static func handle(post: Post, in postListViewController: PostListViewController) { From ff57ba681783a7f8b9351d9828b5c6b082dfd23b Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 2 Dec 2019 06:46:50 -0300 Subject: [PATCH 29/29] Remove unintended change from release notes I noticed that there was a line in release notes that shouldn't be there (it duplicated an existing line): > * Block editor: Images from Image Block can now be previewed full screen by tapping on them. This line was introduced in 6716fc84ecc and moved in ffdbfdb03877. It looks like a merge or conflict was not properly resolved at some point on my feature branch resulting in the move commit not being cleanly applied (line was added to a new line position but not deleted from its old position). This looks like the only inadvertent change - all else looks fine. --- RELEASE-NOTES.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 2f4b6a6af4a5..cbdc0bfa8aeb 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,6 @@ 13.8 ----- * When a post has an autosave, the autosave version can be loaded into the editor. -* Block editor: Images from Image Block can now be previewed full screen by tapping on them. * Support: Fix issue that caused 'Message failed to send' error. * WebView: Fix iOS 13 crash with popover. * Fixed an issue where the Me screen would sometimes be blank.