From 32829036ce042cfafb5e49f0f921aa93be5739ca Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 3 May 2015 21:27:31 -0700 Subject: [PATCH] use notifications to communicate changes to posts --- Blog/BlogController.h | 16 +++----------- Blog/BlogController.m | 30 +++++++++----------------- Blog/EditorViewController.h | 1 - Blog/EditorViewController.m | 36 ++++++++++++++++++++----------- Blog/ModelStore.h | 12 +++++++++-- Blog/ModelStore.m | 40 ++++++++++++++++++++++++++-------- Blog/PostsViewController.m | 43 ++++++++++++++++++++----------------- 7 files changed, 101 insertions(+), 77 deletions(-) diff --git a/Blog/BlogController.h b/Blog/BlogController.h index 9d334d5..b092d1a 100644 --- a/Blog/BlogController.h +++ b/Blog/BlogController.h @@ -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; diff --git a/Blog/BlogController.m b/Blog/BlogController.m index 503a2ac..8af17fa 100644 --- a/Blog/BlogController.m +++ b/Blog/BlogController.m @@ -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 _; }); } diff --git a/Blog/EditorViewController.h b/Blog/EditorViewController.h index b1674c7..b000cba 100644 --- a/Blog/EditorViewController.h +++ b/Blog/EditorViewController.h @@ -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; diff --git a/Blog/EditorViewController.m b/Blog/EditorViewController.m index 1a0a944..36f035b 100644 --- a/Blog/EditorViewController.m +++ b/Blog/EditorViewController.m @@ -12,6 +12,7 @@ #import "Post.h" #import "PreviewViewController.h" #import "ChangeTitleViewController.h" +#import "ModelStore.h" @interface EditorViewController () @@ -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); - } }); }); } diff --git a/Blog/ModelStore.h b/Blog/ModelStore.h index a421530..a5e61f8 100644 --- a/Blog/ModelStore.h +++ b/Blog/ModelStore.h @@ -8,6 +8,14 @@ #import +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 diff --git a/Blog/ModelStore.m b/Blog/ModelStore.m index 7e420ae..ac9ca22 100644 --- a/Blog/ModelStore.m +++ b/Blog/ModelStore.m @@ -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); }]; }]; } diff --git a/Blog/PostsViewController.m b/Blog/PostsViewController.m index d8252af..ba9fa6e 100644 --- a/Blog/PostsViewController.m +++ b/Blog/PostsViewController.m @@ -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]; });