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 {