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;
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 ModelStore;
@class BlogService;
@ -39,9 +29,9 @@ extern NSString *BlogPostDeletedNotification;
- (PMKPromise *)requestCreateDraft:(Post *)draft;
- (PMKPromise *)requestUpdatePost:(Post *)post;
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path;
- (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path;
- (PMKPromise *)requestDeletePostWithPath:(NSString *)path;
- (PMKPromise *)requestPublishDraft:(Post *)post;
- (PMKPromise *)requestUnpublishPost:(Post *)post;
- (PMKPromise *)requestDeletePost:(Post *)post;
- (PMKPromise *)requestPublishToStagingEnvironment;
- (PMKPromise *)requestPublishToProductionEnvironment;

View file

@ -13,16 +13,6 @@
#import "BlogStatus.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 {
BlogService *_service;
ModelStore *_store;
@ -113,26 +103,26 @@ NSString *BlogPostDeletedNotification = @"BlogPostDeletedNotification";
});
}
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path {
return [_service requestPublishDraftWithPath:path].then(^(Post *post) {
[_store removeDraftWithPath:path];
- (PMKPromise *)requestPublishDraft:(Post *)post {
return [_service requestPublishDraftWithPath:post.path].then(^(Post *post) {
[_store removeDraft:post];
[_store addPublishedPost:post];
return post;
});
}
- (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path {
return [_service requestUnpublishPostWithPath:path].then(^(Post *post) {
[_store removePostWithPath:path];
- (PMKPromise *)requestUnpublishPost:(Post *)post {
return [_service requestUnpublishPostWithPath:post.path].then(^(Post *post) {
[_store removePost:post];
[_store addDraft:post];
return post;
});
}
- (PMKPromise *)requestDeletePostWithPath:(NSString *)path {
return [_service requestDeletePostWithPath:path].then(^(id _) {
[_store removePostWithPath:path];
[_store removeDraftWithPath:path];
- (PMKPromise *)requestDeletePost:(Post *)post {
return [_service requestDeletePostWithPath:post.path].then(^(id _) {
[_store removePost:post];
[_store removeDraft:post];
return _;
});
}

View file

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

View file

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

View file

@ -8,6 +8,14 @@
#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 YapDatabaseConnection;
@class BlogStatus;
@ -31,7 +39,7 @@
- (PMKPromise *)addDraft:(Post *)post;
- (PMKPromise *)addPublishedPost:(Post *)post;
- (PMKPromise *)removeDraftWithPath:(NSString *)path;
- (PMKPromise *)removePostWithPath:(NSString *)path;
- (PMKPromise *)removeDraft:(Post *)post;
- (PMKPromise *)removePost:(Post *)post;
@end

View file

@ -13,6 +13,14 @@
#import "Post.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 {
YapDatabaseConnection *_connection;
}
@ -43,13 +51,18 @@
- (PMKPromise *)saveBlogStatus:(BlogStatus *)blogStatus {
return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:blogStatus forKey:@"status" inCollection:@"BlogStatus" withMetadata:@{@"timestamp": @([NSDate date].timeIntervalSince1970)}];
[transaction setObject:blogStatus forKey:@"status" inCollection:@"BlogStatus" withMetadata:@{@"timestamp" : @([NSDate date].timeIntervalSince1970)}];
} completionBlock:^{
fulfill(blogStatus);
}];
}];
}
- (void)postPostUpdatedNotificationForPost:(Post *)post {
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:PostUpdatedNotification object:self userInfo:info];
}
- (Post *)postWithPath:(NSString *)path {
__block Post *post = nil;
[_connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
@ -63,6 +76,7 @@
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:post forKey:post.path inCollection:@"Post"];
} completionBlock:^{
[self postPostUpdatedNotificationForPost:post];
fulfill(post);
}];
}];
@ -111,21 +125,25 @@
[transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"];
}
} completionBlock:^{
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:DraftAddedNotification object:self userInfo:info];
fulfill(post);
}];
}];
}
- (PMKPromise *)removeDraftWithPath:(NSString *)path {
- (PMKPromise *)removeDraft:(Post *)post {
return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSMutableArray *postPaths = [[transaction objectForKey:@"drafts" inCollection:@"PostCollection"] mutableCopy];
if ([postPaths containsObject:path]) {
[postPaths removeObject:path];
if ([postPaths containsObject:post.path]) {
[postPaths removeObject:post.path];
[transaction setObject:postPaths forKey:@"drafts" inCollection:@"PostCollection"];
}
} 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"];
}
} completionBlock:^{
NSDictionary *info = @{PostPathUserInfoKey : post.path, PostUserInfoKey : post};
[[NSNotificationCenter defaultCenter] postNotificationName:PublishedPostAddedNotification object:self userInfo:info];
fulfill(post);
}];
}];
}
- (PMKPromise *)removePostWithPath:(NSString *)path {
- (PMKPromise *)removePost:(Post *)post {
return [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {
[_connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSMutableArray *postPaths = [[transaction objectForKey:@"published" inCollection:@"PostCollection"] mutableCopy];
if ([postPaths containsObject:path]) {
[postPaths removeObject:path];
if ([postPaths containsObject:post.path]) {
[postPaths removeObject:post.path];
[transaction setObject:postPaths forKey:@"published" inCollection:@"PostCollection"];
}
} 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 "UIColor+Hex.h"
#import "PostCollection.h"
#import "ModelStore.h"
@interface PostsViewController ()
@ -106,6 +107,7 @@ static const NSUInteger SectionPublished = 1;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(postUpdated:) name:PostUpdatedNotification object:nil];
[self setupBlogStatusTimer];
[self requestStatusWithCaching:YES];
if (!self.postCollections) {
@ -115,6 +117,7 @@ static const NSUInteger SectionPublished = 1;
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:PostUpdatedNotification object:nil];
[self teardownBlogStatusTimer];
}
@ -211,6 +214,25 @@ static const NSUInteger SectionPublished = 1;
});
}
- (void)postUpdated:(NSNotification *)note {
Post *post = note.userInfo[PostUserInfoKey];
BOOL (^isThisPost)(Post *, NSUInteger, BOOL *) = ^BOOL(Post *p, NSUInteger idx, BOOL *stop) {
return [p.objectID isEqualToString:post.objectID];
};
NSUInteger section = SectionDrafts;
NSUInteger row = [self.drafts indexOfObjectPassingTest:isThisPost];
if (row == NSNotFound) {
section = SectionPublished;
row = [self.publishedPosts indexOfObjectPassingTest:isThisPost];
}
if (row != NSNotFound) {
PostCollection *collection = [self postCollectionForSection:section];
[collection.posts replaceObjectAtIndex:row withObject:post];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
@ -222,25 +244,6 @@ static const NSUInteger SectionPublished = 1;
[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) {
return [p.objectID isEqualToString:post.objectID];
};
NSUInteger section = SectionDrafts;
NSUInteger row = [self.drafts indexOfObjectPassingTest:isThisPost];
if (row == NSNotFound) {
section = SectionPublished;
row = [self.publishedPosts indexOfObjectPassingTest:isThisPost];
}
if (row != NSNotFound) {
PostCollection *collection = [self postCollectionForSection:section];
[collection.posts replaceObjectAtIndex:row withObject:post];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
};
}
}
@ -308,7 +311,7 @@ static NSString *const StateRestorationBlogStatusTextKey = @"blogStatusText";
PostCollection *collection = [self postCollectionForSection:indexPath.section];
Post *post = [self postForIndexPath:indexPath];
// TODO: activity indicator
[self.blogController requestDeletePostWithPath:post.path].then(^{
[self.blogController requestDeletePost:post].then(^{
[collection.posts removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
});