Aztec: Implements basic state machine for editor state#6527
Aztec: Implements basic state machine for editor state#6527astralbodies merged 30 commits intodevelopfrom
Conversation
# Conflicts: # WordPress/Classes/ViewRelated/Post/AztecPostViewController.swift
jleandroperez
left a comment
There was a problem hiding this comment.
Beautiful code!! Adding just a few comments. About to begin testing on the actual device. Zero blockers so far!
| originalPostStatus = PostStatus(rawValue: postStatus) ?? .draft | ||
| } else { | ||
| originalPostStatus = .draft | ||
| } |
There was a problem hiding this comment.
Nipticky // feel free to disregard!!
Potential candidate for a helper method:
enum PostStatus {
static func fromPost(post: Post) -> PostStatus {
//...
}
}
There was a problem hiding this comment.
Could be a good idea – defaulting to .draft. Could also be used in observeValue(forKey:...)
|
|
||
| super.init(nibName: nil, bundle: nil) | ||
|
|
||
| addObservers(toPost: post) |
There was a problem hiding this comment.
I think it's safe to remove this addObservers, since it's already being done in the didSet.
Unless i'm missing something!
There was a problem hiding this comment.
@jleandroperez didSet isn't called when an init method sets the properties before super is called. Once super is called then didSet is called every future time the property is set. Stabby!!
| fileprivate var isBeingPublished = false { | ||
| didSet { | ||
| if isBeingPublished != oldValue { | ||
| publishActionAllowed = hasContent && !isBeingPublished |
There was a problem hiding this comment.
Still thinking if this snippet deserves a method its own method, or not (refreshActionAllowedState). It's just a line... perhaps i'm overthinking this one!
| } | ||
|
|
||
| var publishButtonText: String { | ||
| return editorState.action().publishActionLabel |
There was a problem hiding this comment.
action.publishActionLabel
| } | ||
|
|
||
| var publishVerbText: String { | ||
| return editorState.action().publishingActionLabel |
There was a problem hiding this comment.
action.publishingActionLabel (nipticky, sorry!)
| } | ||
|
|
||
| var isPostPostShown: Bool { | ||
| return editorState.action().isPostPostShown |
There was a problem hiding this comment.
action.isPostPostShown (sorry, nipticky++)
There was a problem hiding this comment.
Agreed - I'll turn action() into a protocol property.
# Conflicts: # WordPress/Classes/ViewRelated/Post/AztecPostViewController.swift
Note 1:Drafts are displaying the
Is this expected? Update: Possibly related to #6534 |
…atus-state-machine
Note 3:The IE: From Revision to Original. |
Note 4:Post's Status Context We could either mimic KVO's "Post Initial Value" flag, or just add that to the |
|
A few notes from putting this through its paces:
Taking a look through the code now... :) |
frosty
left a comment
There was a problem hiding this comment.
This is looking great, @astralbodies! I've left some code comments, and it seems like @jleandroperez has everything else covered :)
| } | ||
|
|
||
| func testContextChangedNewPostToDraft() { | ||
| context = PostEditorStateContext(originalPostStatus: .draft, userCanPublish: true, delegate: self) |
There was a problem hiding this comment.
Isn't a new post created with publish status, not draft?
There was a problem hiding this comment.
(although note that if you change this, we already have a publish -> draft test below.
There was a problem hiding this comment.
Just tested with the existing editor. Posts begin in the publish status. If you change it to draft, the button shows Save.
There was a problem hiding this comment.
I fixed this with a check-in - new posts will pass in a nil originalPostStatus now since it's not on the server. I was trying to match logic in Calypso which makes every post a draft when creating it which is something we should eventually emulate. 😄
There was a problem hiding this comment.
I definitely agree we should start every post off as a draft!
| func testContextDraftPost() { | ||
| context = PostEditorStateContext(originalPostStatus: .draft, userCanPublish: true, delegate: self) | ||
|
|
||
| XCTAssertEqual(PostEditorAction.publish, context.action, "should return Publish if the post is a draft") |
There was a problem hiding this comment.
The hybrid editor shows Update if the post is a draft.
| XCTAssertEqual(PostEditorAction.submitForReview, context.action, "should return 'Submit for Review' if the post is a draft and user can't publish") | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Should we add another test ensuring that a post originally in the scheduled status, if we don't change the date at all, shows an update action?
There was a problem hiding this comment.
Added unit tests to cover these scenarios.
|
|
||
| // Missing test: should return false if not dirty and has no content | ||
|
|
||
| func testPublishDisabledNoContent() { |
There was a problem hiding this comment.
This is very similar to the first test (testContextNoContentPublishButtonDisabled). Perhaps we should name them more differently or more consistently, and perhaps group them together.
| } | ||
| } | ||
|
|
||
| var isPostPostShown: Bool { |
There was a problem hiding this comment.
Looks like this isn't used yet and I might be misunderstanding its use, but with the hybrid editor we also show post-post for scheduled posts.
There was a problem hiding this comment.
It's not currently used yet - will be in another PR.
|
|
||
| fileprivate var hasContent = false { | ||
| didSet { | ||
| publishActionAllowed = hasContent && !isBeingPublished |
There was a problem hiding this comment.
I know it's only one line, but we could extract this to a private updatePublishActionAllowed func and call it from both didSets?
There was a problem hiding this comment.
Yup I'll make that change. 👍
| } | ||
|
|
||
| func updated(postStatus: PostStatus, context: PostEditorStateContext) -> PostEditorActionState { | ||
| return self |
There was a problem hiding this comment.
Presumably this is why I can't seem to get the button out of Schedule state once I've entered it?
| fileprivate extension PostEditorActionState { | ||
| func isFutureDated(_ date: Date) -> Bool { | ||
| let oneMinute: TimeInterval = 60.0 | ||
| let dateOneMinuteFromNow = Date(timeInterval: oneMinute, since: Date()) |
There was a problem hiding this comment.
I would instead use Calendar.current.date(byAdding: .minute, value: 1, to: date)
Or you could potentially use Calendar.current.compare(Date(), to: date, toGranularity: .minute) to compare them instead.
| } | ||
|
|
||
| internal func context(_ context: PostEditorStateContext, didChangeAction: PostEditorAction) { | ||
| publishButton.title = context.publishButtonText |
There was a problem hiding this comment.
In both of these delegate methods, is there a reason you're going via the context instead of using the passed in action / bool?
|
Responding to @frosty's main thread comments: A few notes from putting this through its paces:
Done. Had to add a target/action for the editing changed event on the textview to watch for this. The postTitle property on the post isn't updated until editing is done.
Fixed.
Fixed.
I'm forcing us to match what Calypso says. I think @mattmiklic agreed when I asked about it. 😄 |
|
@jleandroperez Here are responses to your notes:
Fixed.
We talked about this and removing the navbar title makes the most sense. It's getting in the way and we'll mark the editor as being Aztec is some other way like slightly changing the title bar color or something. Will address in a different PR.
For now I moved post revision creation to happen before the UI is set up. I think it makes sense we do the revision operation first.
I think letting the context emit the current state is a good idea. (There's a similar feature in RxSwift ... just sayin...) For now I'll leave it as there are a few things I want to add to the context (like the secondary publish button and post-post functionality) and I'll add this in. |
|
@jleandroperez @frosty I think this is ready for another pass. |
💯. |
|
@astralbodies Changes look good! 👍 for all the comments you've added. Fixes the issues I had, and the tests all pass.
|
# Conflicts: # WordPress/Classes/ViewRelated/Post/AztecPostViewController.swift
|
Beautiful code @astralbodies, all looking good!!
|
|
Thank you both so much for the time to review! |

Closes #6422
This adds the Publish button (previously called Post) to the Aztec editor. Instead of using a series of hacky methods to determine the state of the button it extracts the logic into clearly defined units of state that is unit testable. Other variables that affect state of the editor are also in the state context.
This is partially based off of @frosty's PostEditorSaveAction enum and WPPostViewController extension but made to be more abstract.
The main things this solves are:
What it can/should solve but isn't part of this PR:
Some other TODOs that aren't part of this PR:
How to Test
Make sure the publish button shows the right text and is enabled in these scenarios:
There are a bunch of other scenarios but you get the point.
Needs review: @frosty, @jleandroperez