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/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e25e8f2c8f8d..cbdc0bfa8aeb 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,6 @@ 13.8 ----- +* When a post has an autosave, the autosave version can be loaded into the editor. * 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. diff --git a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift index 78d9caa1cc89..39bc42b56466 100644 --- a/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift +++ b/WordPress/Classes/Extensions/AbstractPost+PostInformation.swift @@ -16,4 +16,12 @@ extension AbstractPost: ImageSourceInformation { var isLocalDraft: Bool { return self.isDraft() && !self.hasRemote() } + + /// An autosave revision may include post title, content and/or excerpt. + var hasAutosaveRevision: Bool { + guard let autosaveRevisionIdentifier = autosaveIdentifier?.intValue else { + return false + } + return autosaveRevisionIdentifier > 0 + } } 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/Utility/Editor/EditorFactory.swift b/WordPress/Classes/Utility/Editor/EditorFactory.swift index b8cecba72a39..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, 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, replaceEditor: replaceEditor) + return createGutenbergVC(with: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor) } else { - return AztecPostViewController(post: post, replaceEditor: replaceEditor) + return AztecPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision, 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, 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 84364d7250b5..b78ff081a766 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -342,6 +342,10 @@ class AztecPostViewController: UIViewController, PostEditor { } } + /// If true, apply autosave content when the editor creates a revision. + /// + private let loadAutosaveRevision: Bool + /// Active Downloads /// fileprivate var activeMediaRequests = [ImageDownloader.Task]() @@ -434,20 +438,16 @@ 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, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { precondition(post.managedObjectContext != nil) self.post = post + self.loadAutosaveRevision = loadAutosaveRevision self.replaceEditor = replaceEditor self.editorSession = editorSession ?? PostEditorAnalyticsSession(editor: .classic, post: post) @@ -485,7 +485,7 @@ class AztecPostViewController: UIViewController, PostEditor { WPFontManager.loadNotoFontFamily() registerAttachmentImageProviders() - createRevisionOfPost() + createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision) // Setup configureNavigationBar() diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index b7a96d108ce4..5d0ec8743fc0 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -163,6 +163,10 @@ class GutenbergViewController: UIViewController, PostEditor { } } + /// If true, apply autosave content when the editor creates a revision. + /// + private let loadAutosaveRevision: Bool + let navigationBarManager = PostEditorNavigationBarManager() lazy var attachmentDelegate = AztecAttachmentDelegate(post: post) @@ -218,10 +222,12 @@ class GutenbergViewController: UIViewController, PostEditor { // MARK: - Initializers required init( post: AbstractPost, + loadAutosaveRevision: Bool = false, replaceEditor: @escaping (EditorViewController, EditorViewController) -> (), editorSession: PostEditorAnalyticsSession? = nil) { self.post = post + self.loadAutosaveRevision = loadAutosaveRevision self.replaceEditor = replaceEditor verificationPromptHelper = AztecVerificationPromptHelper(account: self.post.blog.account) @@ -249,8 +255,8 @@ class GutenbergViewController: UIViewController, PostEditor { override func viewDidLoad() { super.viewDidLoad() + createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision) setupGutenbergView() - createRevisionOfPost() configureNavigationBar() refreshInterface() diff --git a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift index 0116574aea0e..9b0c61119e63 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 + private let loadAutosaveRevision: 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, loadAutosaveRevision: Bool = false) { + self.init(post: post, blog: post.blog, loadAutosaveRevision: loadAutosaveRevision) } @@ -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, loadAutosaveRevision: Bool = false) { self.post = post + self.loadAutosaveRevision = loadAutosaveRevision 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(), + loadAutosaveRevision: loadAutosaveRevision, replaceEditor: { [weak self] (editor, replacement) in self?.replaceEditor(editor: editor, replacement: replacement) }) diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index facf226d42e4..f1d27ae46531 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.hasLocalChanges() && post.hasAutosaveRevision { + return StatusMessages.hasUnsavedChanges } else { return post.statusForDisplay() } @@ -98,6 +100,10 @@ class PostCardStatusViewModel: NSObject { return (autoUploadAction == .upload || post.wasAutoUploadCancelled) ? .warning : .error } + if post.hasAutosaveRevision { + 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 hasUnsavedChanges = 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/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index 8217351ed1f2..fa0b9f3e4dde 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(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 @@ -440,6 +440,13 @@ extension PostEditor where Self: UIViewController { managedObjectContext.performAndWait { post = self.post.createRevision() + + if loadAutosaveRevision { + 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..4b959a0a69f8 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -33,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 diff --git a/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift new file mode 100644 index 000000000000..28e8a8019861 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift @@ -0,0 +1,69 @@ +import Foundation + +/// 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. +struct 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 = 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) + } + + 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/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index d3b0cd35bc68..5b80112c26b9 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -518,10 +518,8 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe presentAlertForPostBeingUploaded() return } - let editor = EditPostViewController(post: post) - editor.modalPresentationStyle = .fullScreen - present(editor, animated: false) - WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: apost) + + PostListEditorPresenter.handle(post: post, in: self) } func presentAlertForPostBeingUploaded() { 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..b4da9e313b1c --- /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 f63b5f514a98..6486a92f34ef 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -337,6 +337,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 */; }; + 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 */; }; @@ -2475,7 +2476,9 @@ 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 = ""; }; + 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 = ""; }; @@ -7668,6 +7671,7 @@ 5D146EB9189857ED0068FDC6 /* FeaturedImageViewController.h */, 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */, 93414DE41E2D25AE003143A3 /* PostEditorState.swift */, + 32CA6EBF2390C61F00B51347 /* PostListEditorPresenter.swift */, 430693731DD25F31009398A2 /* PostPost.storyboard */, 43D54D121DCAA070007F575F /* PostPostViewController.swift */, ACBAB6840E1247F700F38795 /* PostPreviewViewController.h */, @@ -10828,6 +10832,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 */, @@ -14972,6 +14977,7 @@ E125443B12BF5A7200D87A0A /* WordPress.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 32282CF82390B614003378A7 /* WordPress 94.xcdatamodel */, 32A29A16236BC8BC009488C2 /* WordPress 93.xcdatamodel */, 57CCB37E2358E5DC003ECD0C /* WordPress 92.xcdatamodel */, E63EC3632356633B008CEB16 /* WordPress 91.xcdatamodel */, @@ -15066,7 +15072,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 18a4eb3228c1..13e773506360 100644 --- a/WordPress/WordPressTest/PostBuilder.swift +++ b/WordPress/WordPressTest/PostBuilder.swift @@ -63,6 +63,16 @@ class PostBuilder { return self } + func autosaved() -> PostBuilder { + post.autosaveTitle = "a" + post.autosaveExcerpt = "b" + post.autosaveContent = "c" + post.autosaveModifiedDate = Date() + post.autosaveIdentifier = 1 + 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..e36daca80974 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: .sync).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 {