use notifications to communicate changes to posts

This commit is contained in:
Sami Samhuri 2015-05-03 21:27:31 -07:00
parent 99e7cc4108
commit 32829036ce
7 changed files with 101 additions and 77 deletions

View file

@ -8,16 +8,6 @@
@import Foundation; @import Foundation;
extern NSString *BlogStatusChangedNotification;
extern NSString *BlogDraftsChangedNotification;
extern NSString *BlogDraftAddedNotification;
extern NSString *BlogDraftRemovedNotification;
extern NSString *BlogPublishedPostsChangedNotification;
extern NSString *BlogPublishedPostAddedNotification;
extern NSString *BlogPublishedPostRemovedNotification;
extern NSString *BlogPostChangedNotification;
extern NSString *BlogPostDeletedNotification;
@class PMKPromise; @class PMKPromise;
@class ModelStore; @class ModelStore;
@class BlogService; @class BlogService;
@ -39,9 +29,9 @@ extern NSString *BlogPostDeletedNotification;
- (PMKPromise *)requestCreateDraft:(Post *)draft; - (PMKPromise *)requestCreateDraft:(Post *)draft;
- (PMKPromise *)requestUpdatePost:(Post *)post; - (PMKPromise *)requestUpdatePost:(Post *)post;
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path; - (PMKPromise *)requestPublishDraft:(Post *)post;
- (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path; - (PMKPromise *)requestUnpublishPost:(Post *)post;
- (PMKPromise *)requestDeletePostWithPath:(NSString *)path; - (PMKPromise *)requestDeletePost:(Post *)post;
- (PMKPromise *)requestPublishToStagingEnvironment; - (PMKPromise *)requestPublishToStagingEnvironment;
- (PMKPromise *)requestPublishToProductionEnvironment; - (PMKPromise *)requestPublishToProductionEnvironment;

View file

@ -13,16 +13,6 @@
#import "BlogStatus.h" #import "BlogStatus.h"
#import "Post.h" #import "Post.h"
NSString *BlogStatusChangedNotification = @"BlogStatusChangedNotification";
NSString *BlogDraftsChangedNotification = @"BlogDraftsChangedNotification";
NSString *BlogDraftAddedNotification = @"BlogDraftAddedNotification";
NSString *BlogDraftRemovedNotification = @"BlogDraftRemovedNotification";
NSString *BlogPublishedPostsChangedNotification = @"BlogPublishedPostsChangedNotification";
NSString *BlogPublishedPostAddedNotification = @"BlogPostAddedNotification";
NSString *BlogPublishedPostRemovedNotification = @"BlogPostRemovedNotification";
NSString *BlogPostChangedNotification = @"BlogPostChangedNotification";
NSString *BlogPostDeletedNotification = @"BlogPostDeletedNotification";
@implementation BlogController { @implementation BlogController {
BlogService *_service; BlogService *_service;
ModelStore *_store; ModelStore *_store;
@ -113,26 +103,26 @@ NSString *BlogPostDeletedNotification = @"BlogPostDeletedNotification";
}); });
} }
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path { - (PMKPromise *)requestPublishDraft:(Post *)post {
return [_service requestPublishDraftWithPath:path].then(^(Post *post) { return [_service requestPublishDraftWithPath:post.path].then(^(Post *post) {
[_store removeDraftWithPath:path]; [_store removeDraft:post];
[_store addPublishedPost:post]; [_store addPublishedPost:post];
return post; return post;
}); });
} }
- (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path { - (PMKPromise *)requestUnpublishPost:(Post *)post {
return [_service requestUnpublishPostWithPath:path].then(^(Post *post) { return [_service requestUnpublishPostWithPath:post.path].then(^(Post *post) {
[_store removePostWithPath:path]; [_store removePost:post];
[_store addDraft:post]; [_store addDraft:post];
return post; return post;
}); });
} }
- (PMKPromise *)requestDeletePostWithPath:(NSString *)path { - (PMKPromise *)requestDeletePost:(Post *)post {
return [_service requestDeletePostWithPath:path].then(^(id _) { return [_service requestDeletePostWithPath:post.path].then(^(id _) {
[_store removePostWithPath:path]; [_store removePost:post];
[_store removeDraftWithPath:path]; [_store removeDraft:post];
return _; return _;
}); });
} }

View file

@ -15,7 +15,6 @@
@property (nonatomic, strong) BlogController *blogController; @property (nonatomic, strong) BlogController *blogController;
@property (nonatomic, strong) Post *post; @property (nonatomic, strong) Post *post;
@property (nonatomic, copy) void (^postUpdatedBlock)(Post *post);
- (void)configureWithPost:(Post *)post; - (void)configureWithPost:(Post *)post;

View file

@ -12,6 +12,7 @@
#import "Post.h" #import "Post.h"
#import "PreviewViewController.h" #import "PreviewViewController.h"
#import "ChangeTitleViewController.h" #import "ChangeTitleViewController.h"
#import "ModelStore.h"
@interface EditorViewController () <UITextViewDelegate, UIPopoverPresentationControllerDelegate> @interface EditorViewController () <UITextViewDelegate, UIPopoverPresentationControllerDelegate>
@ -74,7 +75,7 @@
#pragma mark - Managing the detail item #pragma mark - Managing the detail item
- (void)configureWithPost:(Post *)post { - (void)configureWithPost:(Post *)post {
if (![post isEqual:self.post]) { if (!(post && [post isEqual:self.post])) {
self.post = post; self.post = post;
self.modifiedPost = post; self.modifiedPost = post;
[self configureView]; [self configureView];
@ -93,6 +94,12 @@
} }
- (void)configureTitleView { - (void)configureTitleView {
if (!self.post) {
self.titleLabel.text = nil;
self.statusLabel.text = nil;
return;
}
self.titleLabel.text = self.modifiedPost.title.length ? self.modifiedPost.title : @"Untitled"; self.titleLabel.text = self.modifiedPost.title.length ? self.modifiedPost.title : @"Untitled";
NSString *statusText = [self statusText]; NSString *statusText = [self statusText];
if (self.statusLabel && ![self.statusLabel.text isEqualToString:statusText]) { if (self.statusLabel && ![self.statusLabel.text isEqualToString:statusText]) {
@ -111,7 +118,7 @@
- (void)configureLinkView { - (void)configureLinkView {
NSURL *url = self.modifiedPost.url; NSURL *url = self.modifiedPost.url;
if (url || [self pasteboardHasLink]) { if (self.post && url || [self pasteboardHasLink]) {
NSString *title = url ? url.absoluteString : @"Add Link from Pasteboard"; NSString *title = url ? url.absoluteString : @"Add Link from Pasteboard";
[self.linkButton setTitle:title forState:UIControlStateNormal]; [self.linkButton setTitle:title forState:UIControlStateNormal];
self.removeLinkButton.hidden = !url; self.removeLinkButton.hidden = !url;
@ -136,7 +143,6 @@
CGPoint scrollOffset = CGPointZero; CGPoint scrollOffset = CGPointZero;
Post *post = self.modifiedPost; Post *post = self.modifiedPost;
if (post) { if (post) {
// FIXME: date, status (draft, published)
body = post.body; body = post.body;
// TODO: restore scroll offset for this post ... user defaults? // TODO: restore scroll offset for this post ... user defaults?
} }
@ -166,10 +172,13 @@
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
NSAssert(self.blogController, @"blogController is required");
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [notificationCenter addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[notificationCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [notificationCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[notificationCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; [notificationCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[notificationCenter addObserver:self selector:@selector(postDeleted:) name:DraftRemovedNotification object:nil];
[notificationCenter addObserver:self selector:@selector(postDeleted:) name:PublishedPostRemovedNotification object:nil];
[self configureView]; [self configureView];
} }
@ -179,6 +188,8 @@
[notificationCenter removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; [notificationCenter removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [notificationCenter removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil]; [notificationCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil];
[notificationCenter removeObserver:self name:DraftRemovedNotification object:nil];
[notificationCenter removeObserver:self name:PublishedPostRemovedNotification object:nil];
[self savePost]; [self savePost];
} }
@ -208,6 +219,13 @@
[self hideHideKeyboardButton]; [self hideHideKeyboardButton];
} }
- (void)postDeleted:(NSNotification *)note {
NSString *path = note.userInfo[PostPathUserInfoKey];
if ([path isEqualToString:self.post.path]) {
[self configureWithPost:nil];
}
}
#pragma mark - State restoration #pragma mark - State restoration
static NSString *const StateRestorationPostKey = @"post"; static NSString *const StateRestorationPostKey = @"post";
@ -247,7 +265,7 @@ static NSString *const StateRestorationModifiedPostKey = @"modifiedPost";
- (BOOL)isDirty; - (BOOL)isDirty;
{ {
return self.modifiedPost.new || ![self.modifiedPost isEqualToPost:self.post]; return self.post && (self.modifiedPost.new || ![self.modifiedPost isEqualToPost:self.post]);
} }
- (PMKPromise *)savePost { - (PMKPromise *)savePost {
@ -293,9 +311,6 @@ static NSString *const StateRestorationModifiedPostKey = @"modifiedPost";
// update our post because "new" may have changed, which is essential to correct operation // update our post because "new" may have changed, which is essential to correct operation
[self configureWithPost:newPost]; [self configureWithPost:newPost];
if (self.postUpdatedBlock) {
self.postUpdatedBlock(self.post);
}
return newPost; return newPost;
}).catch(^(NSError *error) { }).catch(^(NSError *error) {
NSLog(@"Failed to %@ post at path %@: %@ %@", verb, path, error.localizedDescription, error.userInfo); NSLog(@"Failed to %@ post at path %@: %@ %@", verb, path, error.localizedDescription, error.userInfo);
@ -332,18 +347,15 @@ static NSString *const StateRestorationModifiedPostKey = @"modifiedPost";
PMKPromise *promise = nil; PMKPromise *promise = nil;
Post *post = self.modifiedPost; Post *post = self.modifiedPost;
if (post.draft) { if (post.draft) {
promise = [self.blogController requestPublishDraftWithPath:post.path]; promise = [self.blogController requestPublishDraft:post];
} }
else { else {
promise = [self.blogController requestUnpublishPostWithPath:post.path]; promise = [self.blogController requestUnpublishPost:post];
} }
promise.then(^(Post *post) { promise.then(^(Post *post) {
self.post = post; self.post = post;
self.modifiedPost = post; self.modifiedPost = post;
[self configureView]; [self configureView];
if (self.postUpdatedBlock) {
self.postUpdatedBlock(post);
}
}); });
}); });
} }

View file

@ -8,6 +8,14 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
extern NSString *const PostUpdatedNotification;
extern NSString *const DraftRemovedNotification;
extern NSString *const DraftAddedNotification;
extern NSString *const PublishedPostAddedNotification;
extern NSString *const PublishedPostRemovedNotification;
extern NSString *const PostUserInfoKey;
extern NSString *const PostPathUserInfoKey;
@class PMKPromise; @class PMKPromise;
@class YapDatabaseConnection; @class YapDatabaseConnection;
@class BlogStatus; @class BlogStatus;
@ -31,7 +39,7 @@
- (PMKPromise *)addDraft:(Post *)post; - (PMKPromise *)addDraft:(Post *)post;
- (PMKPromise *)addPublishedPost:(Post *)post; - (PMKPromise *)addPublishedPost:(Post *)post;
- (PMKPromise *)removeDraftWithPath:(NSString *)path; - (PMKPromise *)removeDraft:(Post *)post;
- (PMKPromise *)removePostWithPath:(NSString *)path; - (PMKPromise *)removePost:(Post *)post;
@end @end

View file

@ -13,6 +13,14 @@
#import "Post.h" #import "Post.h"
#import "NSArray+ObjectiveSugar.h" #import "NSArray+ObjectiveSugar.h"
NSString *const PostUpdatedNotification = @"PostUpdatedNotification";
NSString *const DraftRemovedNotification = @"DraftRemovedNotification";
NSString *const DraftAddedNotification = @"DraftAddedNotification";
NSString *const PublishedPostAddedNotification = @"PublishedPostAddedNotification";
NSString *const PublishedPostRemovedNotification = @"PublishedPostRemovedNotification";
NSString *const PostUserInfoKey = @"PostUserInfoKey";
NSString *const PostPathUserInfoKey = @"PostPathUserInfoKey";
@implementation ModelStore { @implementation ModelStore {
YapDatabaseConnection *_connection; YapDatabaseConnection *_connection;
} }
@ -50,6 +58,11 @@
}]; }];
} }
- (void)postPostUpdatedNotificationForPost:(Post *)post {
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:PostUpdatedNotification object:self userInfo:info];
}
- (Post *)postWithPath:(NSString *)path { - (Post *)postWithPath:(NSString *)path {
__block Post *post = nil; __block Post *post = nil;
[_connection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [_connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
@ -63,6 +76,7 @@
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:post forKey:post.path inCollection:@"Post"]; [transaction setObject:post forKey:post.path inCollection:@"Post"];
} completionBlock:^{ } completionBlock:^{
[self postPostUpdatedNotificationForPost:post];
fulfill(post); fulfill(post);
}]; }];
}]; }];
@ -111,21 +125,25 @@
[transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"]; [transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"];
} }
} completionBlock:^{ } completionBlock:^{
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:DraftAddedNotification object:self userInfo:info];
fulfill(post); fulfill(post);
}]; }];
}]; }];
} }
- (PMKPromise *)removeDraftWithPath:(NSString *)path { - (PMKPromise *)removeDraft:(Post *)post {
return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) { return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSMutableArray *postPaths = [[transaction objectForKey:@"drafts" inCollection:@"PostCollection"] mutableCopy]; NSMutableArray *postPaths = [[transaction objectForKey:@"drafts" inCollection:@"PostCollection"] mutableCopy];
if ([postPaths containsObject:path]) { if ([postPaths containsObject:post.path]) {
[postPaths removeObject:path]; [postPaths removeObject:post.path];
[transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"]; [transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"];
} }
} completionBlock:^{ } completionBlock:^{
fulfill(path); NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:DraftRemovedNotification object:self userInfo:info];
fulfill(post);
}]; }];
}]; }];
} }
@ -174,21 +192,25 @@
[transaction setObject:postPaths forKey:@"published" inCollection:@"PostCollection"]; [transaction setObject:postPaths forKey:@"published" inCollection:@"PostCollection"];
} }
} completionBlock:^{ } completionBlock:^{
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:PublishedPostAddedNotification object:self userInfo:info];
fulfill(post); fulfill(post);
}]; }];
}]; }];
} }
- (PMKPromise *)removePostWithPath:(NSString *)path { - (PMKPromise *)removePost:(Post *)post {
return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) { return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSMutableArray *postPaths = [[transaction objectForKey:@"published" inCollection:@"PostCollection"] mutableCopy]; NSMutableArray *postPaths = [[transaction objectForKey:@"published" inCollection:@"PostCollection"] mutableCopy];
if ([postPaths containsObject:path]) { if ([postPaths containsObject:post.path]) {
[postPaths removeObject:path]; [postPaths removeObject:post.path];
[transaction setObject:postPaths forKey:@"published" inCollection:@"PostCollection"]; [transaction setObject:postPaths forKey:@"published" inCollection:@"PostCollection"];
} }
} completionBlock:^{ } completionBlock:^{
fulfill(path); NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:PublishedPostRemovedNotification object:self userInfo:info];
fulfill(post);
}]; }];
}]; }];
} }

View file

@ -16,6 +16,7 @@
#import "NSDate+marshmallows.h" #import "NSDate+marshmallows.h"
#import "UIColor+Hex.h" #import "UIColor+Hex.h"
#import "PostCollection.h" #import "PostCollection.h"
#import "ModelStore.h"
@interface PostsViewController () @interface PostsViewController ()
@ -106,6 +107,7 @@ static const NSUInteger SectionPublished = 1;
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(postUpdated:) name:PostUpdatedNotification object:nil];
[self setupBlogStatusTimer]; [self setupBlogStatusTimer];
[self requestStatusWithCaching:YES]; [self requestStatusWithCaching:YES];
if (!self.postCollections) { if (!self.postCollections) {
@ -115,6 +117,7 @@ static const NSUInteger SectionPublished = 1;
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:PostUpdatedNotification object:nil];
[self teardownBlogStatusTimer]; [self teardownBlogStatusTimer];
} }
@ -211,20 +214,8 @@ static const NSUInteger SectionPublished = 1;
}); });
} }
#pragma mark - Segues - (void)postUpdated:(NSNotification *)note {
Post *post = note.userInfo[PostUserInfoKey];
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Post *post = [self postForIndexPath:indexPath];
EditorViewController *controller = (EditorViewController *)[[segue destinationViewController] topViewController];
controller.blogController = self.blogController;
[controller configureWithPost:post];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
__weak __typeof__(self) welf = self;
controller.postUpdatedBlock = ^(Post *post) {
__typeof__(self) self = welf;
BOOL (^isThisPost)(Post *, NSUInteger, BOOL *) = ^BOOL(Post *p, NSUInteger idx, BOOL *stop) { BOOL (^isThisPost)(Post *, NSUInteger, BOOL *) = ^BOOL(Post *p, NSUInteger idx, BOOL *stop) {
return [p.objectID isEqualToString:post.objectID]; return [p.objectID isEqualToString:post.objectID];
}; };
@ -240,7 +231,19 @@ static const NSUInteger SectionPublished = 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} }
}; }
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Post *post = [self postForIndexPath:indexPath];
EditorViewController *controller = (EditorViewController *)[[segue destinationViewController] topViewController];
controller.blogController = self.blogController;
[controller configureWithPost:post];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
} }
} }
@ -308,7 +311,7 @@ static NSString *const StateRestorationBlogStatusTextKey = @"blogStatusText";
PostCollection *collection = [self postCollectionForSection:indexPath.section]; PostCollection *collection = [self postCollectionForSection:indexPath.section];
Post *post = [self postForIndexPath:indexPath]; Post *post = [self postForIndexPath:indexPath];
// TODO: activity indicator // TODO: activity indicator
[self.blogController requestDeletePostWithPath:post.path].then(^{ [self.blogController requestDeletePost:post].then(^{
[collection.posts removeObjectAtIndex:indexPath.row]; [collection.posts removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}); });