From 4639a752eac8b3a217538620053bbf73836d2255 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 5 Aug 2013 18:24:39 +0200 Subject: [PATCH] # Syncing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Creating, editing, and deleting issue and comments._ ## GHStore Creating, edit and deleting issues and comments are accomplished via two methods: 1. The over-arching `save` methods will add, edit, or delete, according to the current state of the object passed in as an argument. 2. The `delete` methods delete canceled new objects, then call through to the appropriate `save` method if necessary. ## LETalk * Add a `topic` property to return the current issue’s title, regardless of whether the object is an issue or comment. ## GHManagedObject+LETalk * Implement `plainTitle` method to return the current title including changes. * Implement `topic` to return the current issue’s title regardless of whether the current talk is an issue or a comment. * The default behavior throws an exception and meant to be overridden by subclasses . ## GHManagedObject * Move dictionary encoding for GitHub keys out of `dictionaryWithValuesForKeys:` into its own method. ## GHComment * Expose commentID property * Override `die` method * Override `topic` method ## GHIssue * Override `styledTitle` to experiment with delivering a different title for issue than for comments. * Override `topic` method ## LETalkListController * Remove the default accessory to save space. * Set a custom disclosure accessory in an attempt to save space. ## LETalkViewController * Reload the list on `viewWillAppear:` to ensure current changes are reflected in the table view. * Enable the `CreateComment` segue. ## LEWorkViewController * Implement `save` to send the current issue or comment to the server and pop back to the previous table. * Save changes to the issue title. * Toggle save and delete on comments based on whether there is a body. * If you want to delete the comment, delete the text, and the save button becomes delete. * Deleting comments seems to work. * Toggle save and delete on issues based on whether there is a title set. * If you want to delete the issue, delete the title, and the save button becomes delete. * Deleting issues does not seem to work. I don’t know why. (Refs #40) * Ensure the correct segment is selected when edit mode is toggled. * Add a topic field for editing the current topic. * When editing an issue, as opposed to a comment, move the topic from the prompt into the topic field. * This doesn’t work well and will probably be refactored away (Refs #31) ## iPhone UI * Add some word bubbles in the table view cells. * Give the app a nice bluish color to make the bubble pop. * Adjust the layout to accommodate these changes and fix autolayout conflicts. --- Lemacs/Base.lproj/Main_iPhone.storyboard | 35 +++-- Lemacs/GitHub/GHComment.h | 1 + Lemacs/GitHub/GHComment.m | 24 ++- Lemacs/GitHub/GHIssue.m | 35 +++++ Lemacs/GitHub/GHManagedObject+LETalk.m | 10 +- Lemacs/GitHub/GHManagedObject.h | 2 + Lemacs/GitHub/GHManagedObject.m | 37 ++--- Lemacs/GitHub/GHStore.h | 8 +- Lemacs/GitHub/GHStore.m | 182 ++++++++++++++--------- Lemacs/LETalk.h | 2 +- Lemacs/LETalkListController.m | 7 +- Lemacs/LETalkViewController.h | 1 - Lemacs/LETalkViewController.m | 23 ++- Lemacs/LEWorkViewController.h | 4 +- Lemacs/LEWorkViewController.m | 93 ++++++++++-- 15 files changed, 327 insertions(+), 137 deletions(-) diff --git a/Lemacs/Base.lproj/Main_iPhone.storyboard b/Lemacs/Base.lproj/Main_iPhone.storyboard index ba8392d..04be6e8 100644 --- a/Lemacs/Base.lproj/Main_iPhone.storyboard +++ b/Lemacs/Base.lproj/Main_iPhone.storyboard @@ -17,8 +17,8 @@ - - + + @@ -35,11 +35,23 @@ + + + + + + + + + - + + + + @@ -67,6 +79,7 @@ + @@ -79,6 +92,8 @@ + + @@ -92,16 +107,16 @@ - + - + - + @@ -144,17 +159,17 @@ - + - + - + @@ -212,6 +227,6 @@ - + \ No newline at end of file diff --git a/Lemacs/GitHub/GHComment.h b/Lemacs/GitHub/GHComment.h index 4a2c2f6..d6707fb 100644 --- a/Lemacs/GitHub/GHComment.h +++ b/Lemacs/GitHub/GHComment.h @@ -15,6 +15,7 @@ + (instancetype)newCommentInContext:(NSManagedObjectContext *)context; + (instancetype)commentNumber:(NSInteger)commentNumber context:(NSManagedObjectContext *)context; +@property (nonatomic) NSInteger commentID; @property (nonatomic, strong) NSDate *createdDate; @property (nonatomic, strong) NSString *body, *commentURL; @property (nonatomic, strong) GHIssue *issue; diff --git a/Lemacs/GitHub/GHComment.m b/Lemacs/GitHub/GHComment.m index a6a9bf8..3e7d03b 100644 --- a/Lemacs/GitHub/GHComment.m +++ b/Lemacs/GitHub/GHComment.m @@ -7,6 +7,7 @@ // #import "GHComment.h" +#import "GHIssue.h" #import "GHStore.h" @implementation GHComment @@ -54,10 +55,31 @@ + (NSString *)indexGitHubKey; #pragma mark - API -@dynamic body, commentURL, createdDate, issue, user; +@dynamic body, commentID, commentURL, createdDate, issue, user; @end + +@implementation GHComment (Deletion) + +- (IBAction)die; +{ // ???: Can we do this? Should we do it after a delay? A request deletion method? + [[GHStore sharedStore] deleteComment:self]; +} + +@end + + +@implementation GHComment (LETalk) + +- (NSString *)topic; +{ + return [self.issue currentValueForKey:kLETalkTitleKey]; +} + +@end + + NSString * const kGHCommentEntityName = @"GHComment"; NSString * const kGHCommentIDGitHubKey = @"id"; diff --git a/Lemacs/GitHub/GHIssue.m b/Lemacs/GitHub/GHIssue.m index 95f846d..2407bf9 100644 --- a/Lemacs/GitHub/GHIssue.m +++ b/Lemacs/GitHub/GHIssue.m @@ -10,6 +10,7 @@ #import "GHComment.h" #import "GHStore.h" +#import "GHUser.h" #import "NSAttributedStringMarkdownParser+GHMarkdown.h" @implementation GHIssue @@ -128,6 +129,40 @@ - (IBAction)die; @end +@implementation GHIssue (LETalk) + +- (NSAttributedString *)styledTitle; +{ // RealName (username) replied in|started topic + if (![self respondsToSelector:@selector(user)]) + return nil; + + GHUser *user = [self valueForKey:@"user"]; + + NSDictionary *boldBlackStyle = @{NSFontAttributeName : [UIFont boldSystemFontOfSize:10.0f], + NSForegroundColorAttributeName : [UIColor blackColor]}; + NSDictionary *lightGrayStyle = @{NSFontAttributeName : [UIFont systemFontOfSize:10.0f], + NSForegroundColorAttributeName : [UIColor lightGrayColor]}; + + NSString *name = IsEmpty(user.displayName) ? user.userName : user.displayName; + if (!name) + name = NSLocalizedString(@"You", @"Second person pronoun"); + + NSMutableAttributedString *styledTitle = [[NSMutableAttributedString alloc] initWithString:name attributes:boldBlackStyle]; + + NSString *verb = [self isKindOfClass:[GHComment class]] ? NSLocalizedString(@" replied to ", @"reply verb") : NSLocalizedString(@" started ", @"initiate verb"); + [styledTitle appendAttributedString:[[NSAttributedString alloc] initWithString:verb attributes:lightGrayStyle]]; + + return styledTitle; +} + +- (NSString *)topic; +{ + return [self currentValueForKey:kLETalkTitleKey]; +} + +@end + + NSString * const kGHIssueEntityName = @"GHIssue"; NSString * const kGHIssueClosedGitHubKey = @"state"; diff --git a/Lemacs/GitHub/GHManagedObject+LETalk.m b/Lemacs/GitHub/GHManagedObject+LETalk.m index 53c1f90..34a055a 100644 --- a/Lemacs/GitHub/GHManagedObject+LETalk.m +++ b/Lemacs/GitHub/GHManagedObject+LETalk.m @@ -60,11 +60,6 @@ - (NSAttributedString *)styledBody; return [[NSAttributedStringMarkdownParser sharedParser] attributedStringFromMarkdownString:NonNil(self.plainBody, @"")]; } -- (NSString *)plainTitle; -{ - return [self currentValueForKey:kLETalkTitleKey]; -} - - (NSAttributedString *)styledTitle; { if (![self respondsToSelector:@selector(user)]) @@ -87,5 +82,10 @@ - (NSAttributedString *)styledTitle; return styledTitle; } +- (NSString *)topic; +{ + assert(NO); // Subclasses should override this + return [self currentValueForKey:kLETalkTitleKey]; +} @end diff --git a/Lemacs/GitHub/GHManagedObject.h b/Lemacs/GitHub/GHManagedObject.h index fa947b0..cae69c3 100644 --- a/Lemacs/GitHub/GHManagedObject.h +++ b/Lemacs/GitHub/GHManagedObject.h @@ -19,6 +19,8 @@ @property (nonatomic, readonly) BOOL needsUpdating; @property (nonatomic, strong) NSDate *lastUpdated; +- (NSDictionary *)dictionaryWithValuesForGitHubKeys:(NSArray *)keys; + @end diff --git a/Lemacs/GitHub/GHManagedObject.m b/Lemacs/GitHub/GHManagedObject.m index 7bbf60a..855f986 100644 --- a/Lemacs/GitHub/GHManagedObject.m +++ b/Lemacs/GitHub/GHManagedObject.m @@ -98,27 +98,6 @@ - (void)setValue:(id)value forUndefinedKey:(NSString *)key; [super setValue:value forUndefinedKey:key]; } -- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys; -{ - if (keys) - return [super dictionaryWithValuesForKeys:keys]; - - NSDictionary *GitHubKeysToPropertyNames = [[self class] GitHubKeysToPropertyNames]; - NSArray *GitHubKeys = GitHubKeysToPropertyNames.allKeys; - __block NSMutableDictionary *GitHubKeysToCurrentValues = [NSMutableDictionary dictionaryWithCapacity:GitHubKeys.count]; - GHManagedObject * __weak currentObject = self; - [GitHubKeys enumerateObjectsUsingBlock:^(NSString *GitHubKey, NSUInteger uselessIndex, BOOL *stop) { - if ([GitHubKey isEqualToString:[[currentObject class] indexGitHubKey]]) - return; - - NSString *propertyName = [[self class] GitHubKeysToPropertyNames][GitHubKey]; - id propertyValue = [currentObject currentValueForKey:propertyName]; - [GitHubKeysToCurrentValues setValue:propertyValue forKey:GitHubKey]; - }]; - - return [GitHubKeysToCurrentValues copy]; -} - - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues; { NSDate *modifiedDate = keyedValues[kGHModifiedDatePropertyName]; @@ -270,6 +249,22 @@ - (BOOL)needsUpdating; return -[self.lastUpdated timeIntervalSinceNow] > kGHStoreUpdateLimit; } +- (NSDictionary *)dictionaryWithValuesForGitHubKeys:(NSArray *)keys; +{ + __block NSMutableDictionary *GitHubKeysToCurrentValues = [NSMutableDictionary dictionaryWithCapacity:keys.count]; + GHManagedObject * __weak currentObject = self; + [keys enumerateObjectsUsingBlock:^(NSString *GitHubKey, NSUInteger uselessIndex, BOOL *stop) { + if ([GitHubKey isEqualToString:[[currentObject class] indexGitHubKey]]) + return; + + NSString *propertyName = [[self class] GitHubKeysToPropertyNames][GitHubKey]; + id propertyValue = [currentObject currentValueForKey:propertyName]; + [GitHubKeysToCurrentValues setValue:propertyValue forKey:GitHubKey]; + }]; + + return [GitHubKeysToCurrentValues copy]; +} + - (void)setUpRelationship:(NSRelationshipDescription *)relationship withValue:(id)value forKey:(NSString *)key; { if (relationship.isToMany) diff --git a/Lemacs/GitHub/GHStore.h b/Lemacs/GitHub/GHStore.h index 3c7ea50..80cd9cf 100644 --- a/Lemacs/GitHub/GHStore.h +++ b/Lemacs/GitHub/GHStore.h @@ -24,14 +24,14 @@ - (void)logInWithUsername:(NSString *)username password:(NSString *)password; // Issues -- (void)addIssue:(GHIssue *)issue; -- (void)deleteIssue:(GHIssue *)issue; - (void)loadIssues:(BOOL)freshStart; - (void)loadCommentsForIssue:(GHIssue *)issue; - (void)loadUser:(GHUser *)user; -// Comments -- (void)addComment:(GHComment *)comment toIssue:(GHIssue *)issue; +// Saving - (void)deleteComment:(GHComment *)comment; +- (void)deleteIssue:(GHIssue *)issue; +- (void)saveComment:(GHComment *)comment; +- (void)saveIssue:(GHIssue *)issue; @end diff --git a/Lemacs/GitHub/GHStore.m b/Lemacs/GitHub/GHStore.m index 71bd7fb..a270146 100644 --- a/Lemacs/GitHub/GHStore.m +++ b/Lemacs/GitHub/GHStore.m @@ -242,58 +242,11 @@ - (IBAction)sync; // Scrub placeholder data // ???: Can we push issues with comments already included? - - // Re-start sync } #pragma mark Loading -- (void)addIssue:(GHIssue *)issue; -{ - assert((IsEmpty(issue.body) && IsEmpty(issue.title))); // Otherwise defer to changeIssue: - if (IsEmpty(issue.plainBody) || IsEmpty(issue.plainTitle)) - return; // Let the user know it's not ready yet or hide save button - - [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; - - NSDictionary *valuesForGitHubKeys = [issue dictionaryWithValuesForKeys:nil]; - [self.GitHub addIssueForRepository:self.repositoryPath withDictionary:valuesForGitHubKeys success:^(id results) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - assert([results isKindOfClass:[NSArray class]]); - NSDictionary *dictionary = [results lastObject]; - assert([results isKindOfClass:[NSDictionary class]]); - [issue setValuesForKeysWithDictionary:dictionary]; - [[GHStore sharedStore] save]; - } failure:^(NSError *error) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - NSLog(@"Failure %@", error.localizedDescription); - }]; -} - -- (void)deleteIssue:(GHIssue *)issue; -{ - if (IsEmpty(issue.body)) { - [issue.managedObjectContext deleteObject:issue]; - [self save]; - return ; - } - - [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; - - [self.GitHub deleteIssue:issue.number inRepository:self.repositoryPath success:^(BOOL success) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - if (!success) - return; // ???: What does this state represent? - - [issue.managedObjectContext deleteObject:issue]; - [[GHStore sharedStore] save]; - } failure:^(NSError *error) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - NSLog(@"Failure %@", error.localizedDescription); - }]; -} - - (void)loadIssues:(BOOL)freshStart; { if (freshStart) @@ -349,8 +302,6 @@ - (void)loadCommentsForIssue:(GHIssue *)issue; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; NSLog(@"%@ %@", NSStringFromSelector(_cmd), error.localizedDescription); }]; - - // [self.talkList reloadList]; } - (void)loadUser:(GHUser *)user; @@ -366,7 +317,7 @@ - (void)loadUser:(GHUser *)user; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; assert([results isKindOfClass:[NSArray class]]); NSDictionary *dictionary = [results lastObject]; - assert([results isKindOfClass:[NSDictionary class]]); + assert([dictionary isKindOfClass:[NSDictionary class]]); [user setValuesForKeysWithDictionary:dictionary]; [[GHStore sharedStore] save]; } failure:^(NSError *error) { @@ -376,33 +327,124 @@ - (void)loadUser:(GHUser *)user; } -#pragma mark Comments +#pragma mark Saving + +- (void)deleteComment:(GHComment *)comment; +{ + if (IsEmpty(comment.body)) { + [comment.managedObjectContext deleteObject:comment]; + [self save]; + return; + } -- (void)addComment:(GHComment *)comment toIssue:(GHIssue *)issue; + [self saveComment:comment]; +} + +- (void)deleteIssue:(GHIssue *)issue; { - // TODO: clean up placeholder data + if (IsEmpty(issue.body)) { + [issue.managedObjectContext deleteObject:issue]; + [self save]; + return ; + } + + [self saveIssue:issue]; +} +- (void)saveComment:(GHComment *)comment; +{ [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; - [self.GitHub addComment:comment.body toIssue:issue.number forRepository:self.repositoryPath success:^(id results) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - assert([results isKindOfClass:[NSArray class]]); - NSLog(@"%@", results); - NSDictionary *dictionary = [results lastObject]; - assert([results isKindOfClass:[NSDictionary class]]); - [comment setValuesForKeysWithDictionary:dictionary]; - comment.changes = nil; - NSLog(@"%@", dictionary); - [[GHStore sharedStore] save]; - } failure:^(NSError *error) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; - NSLog(@"%@ %@", NSStringFromSelector(_cmd), error.localizedDescription); - }]; + if (IsEmpty(comment.body) && !IsEmpty(comment.plainBody)) // Add + [self.GitHub addComment:comment.plainBody toIssue:comment.issue.number forRepository:self.repositoryPath success:^(id results) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + assert([results isKindOfClass:[NSArray class]]); + NSLog(@"%@", results); + NSDictionary *dictionary = [results lastObject]; + assert([dictionary isKindOfClass:[NSDictionary class]]); + [comment setValuesForKeysWithDictionary:dictionary]; + comment.changes = nil; + NSLog(@"%@", dictionary); + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"%@ %@", NSStringFromSelector(_cmd), error.localizedDescription); + }]; + else if (!IsEmpty(comment.body) && !IsEmpty(comment.plainBody)) // Edit + [self.GitHub editComment:comment.commentID forRepository:self.repositoryPath withBody:comment.plainBody success:^(id results) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + assert([results isKindOfClass:[NSArray class]]); + NSLog(@"%@", results); + NSDictionary *dictionary = [results lastObject]; + assert([dictionary isKindOfClass:[NSDictionary class]]); + [comment setValuesForKeysWithDictionary:dictionary]; + comment.changes = nil; + NSLog(@"%@", dictionary); + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"%@ %@", NSStringFromSelector(_cmd), error.localizedDescription); + }]; + else if (!IsEmpty(comment.body) && IsEmpty(comment.plainBody)) // Delete + [self.GitHub deleteComment:comment.commentID forRepository:self.repositoryPath success:^(BOOL buhweeted) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + if (buhweeted) + [comment.managedObjectContext deleteObject:comment]; + + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"%@ %@", NSStringFromSelector(_cmd), error.localizedDescription); + }]; + else // Unhandled states + assert(NO); } -- (void)deleteComment:(GHComment *)comment; +- (void)saveIssue:(GHIssue *)issue; { - // ???: Do we have to call validateForDelete: or is that involked automatically? - [self.managedObjectContext deleteObject:comment]; + if (IsEmpty(issue.topic)) { // Delete + [self.GitHub deleteIssue:issue.number inRepository:self.repositoryPath success:^(BOOL success) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + if (success) + [issue.managedObjectContext deleteObject:issue]; + + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"Failure %@", error.localizedDescription); + }]; + return; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + NSDictionary *valuesForGitHubKeys = [issue dictionaryWithValuesForGitHubKeys:@[kLETalkTitleKey, kLETalkBodyKey]]; + if (IsEmpty(issue.title) && !IsEmpty(issue.plainBody)) // Add + [self.GitHub addIssueForRepository:self.repositoryPath withDictionary:valuesForGitHubKeys success:^(id results) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + assert([results isKindOfClass:[NSArray class]]); + NSDictionary *dictionary = [results lastObject]; + assert([dictionary isKindOfClass:[NSDictionary class]]); + [issue setValuesForKeysWithDictionary:dictionary]; + issue.changes = nil; + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"Failure %@", error.localizedDescription); + }]; + else if (!IsEmpty(issue.body) && !IsEmpty(issue.plainBody)) // Edit + [self.GitHub editIssue:issue.number inRepository:self.repositoryPath withDictionary:valuesForGitHubKeys success:^(id results) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + assert([results isKindOfClass:[NSArray class]]); + NSDictionary *dictionary = [results lastObject]; + assert([dictionary isKindOfClass:[NSDictionary class]]); + [issue setValuesForKeysWithDictionary:dictionary]; + issue.changes = nil; + [[GHStore sharedStore] save]; + } failure:^(NSError *error) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + NSLog(@"Failure %@", error.localizedDescription); + }]; + else // Unhandled states + assert(NO); } @end diff --git a/Lemacs/LETalk.h b/Lemacs/LETalk.h index b434f3c..e2522a8 100644 --- a/Lemacs/LETalk.h +++ b/Lemacs/LETalk.h @@ -9,7 +9,7 @@ @protocol LETalk @property (nonatomic, readonly) BOOL hasChanges; @property (nonatomic, readonly) NSAttributedString *styledBody, *styledTitle; -@property (nonatomic, readonly) NSString *bodyHTML, *displayedTime, *plainBody, *plainTitle; +@property (nonatomic, readonly) NSString *bodyHTML, *displayedTime, *plainBody, *topic; @property (nonatomic, readonly) NSURL *baseURL; @property (nonatomic, readonly) UIImage *avatar; @end diff --git a/Lemacs/LETalkListController.m b/Lemacs/LETalkListController.m index f1c89d5..9ff0239 100644 --- a/Lemacs/LETalkListController.m +++ b/Lemacs/LETalkListController.m @@ -92,7 +92,6 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { GHIssue *issue; if ([[segue identifier] isEqualToString:@"CreateIssue"]) { - // Create issue issue = [GHIssue newIssueInContext:self.managedObjectContext]; assert([segue.destinationViewController isKindOfClass:[LEWorkViewController class]]); [[segue destinationViewController] setTalk:issue]; @@ -111,7 +110,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; assert([segue.destinationViewController isKindOfClass:[LETalkViewController class]]); LETalkViewController *talkViewController = (LETalkViewController *)segue.destinationViewController; talkViewController.issue = issue; - talkViewController.navigationItem.prompt = issue.plainTitle; + talkViewController.navigationItem.prompt = issue.topic; } else if ([[segue identifier] isEqualToString:@"SelectTalk"]) { assert([segue.destinationViewController isKindOfClass:[LEWorkViewController class]]); [[segue destinationViewController] setTalk:issue]; @@ -393,8 +392,10 @@ - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPa NSInteger commentsCount = issue.commentsCount; if (commentsCount) { talkCell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; + talkCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TalkDisclosure"]]; } else { - talkCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + talkCell.accessoryType = UITableViewCellAccessoryNone; + talkCell.accessoryView = nil; } } diff --git a/Lemacs/LETalkViewController.h b/Lemacs/LETalkViewController.h index 52045c9..92a96b6 100644 --- a/Lemacs/LETalkViewController.h +++ b/Lemacs/LETalkViewController.h @@ -12,7 +12,6 @@ @property (strong, nonatomic) GHIssue *issue; -- (IBAction)reloadList; - (IBAction)sortList:(UISegmentedControl *)sortControl; @end diff --git a/Lemacs/LETalkViewController.m b/Lemacs/LETalkViewController.m index 7afc43f..f6e81e9 100644 --- a/Lemacs/LETalkViewController.m +++ b/Lemacs/LETalkViewController.m @@ -26,6 +26,7 @@ @interface LETalkViewController () @property (nonatomic) BOOL reverseSort; - (IBAction)insertNewObject; +- (IBAction)reloadList; - (IBAction)saveContext; - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; @@ -40,6 +41,7 @@ + (void)initialize; [[NSUserDefaults standardUserDefaults] registerDefaults:@{kLETalkViewSortOrder : @(NO)}]; } + #pragma mark - NSObject (UINibLoadingAdditions) - (void)awakeFromNib; @@ -59,8 +61,15 @@ - (void)viewDidLoad; [super viewDidLoad]; [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([LETalkCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([LETalkCell class])]; +} +- (void)viewWillAppear:(BOOL)animated; +{ self.reverseSort = [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkViewSortOrder]; + + [self reloadList]; + + [super viewWillAppear:animated]; } - (void)didReceiveMemoryWarning; @@ -74,13 +83,19 @@ - (void)didReceiveMemoryWarning; - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { + id talk; + if ([[segue identifier] isEqualToString:@"SelectTalk"]) { NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; - id talk = (indexPath.section < self.fetchedResultsController.sections.count) ? [self.fetchedResultsController objectAtIndexPath:indexPath] : self.issue; + talk = (indexPath.section < self.fetchedResultsController.sections.count) ? [self.fetchedResultsController objectAtIndexPath:indexPath] : self.issue; assert([talk conformsToProtocol:@protocol(LETalk)]); - assert([segue.destinationViewController isKindOfClass:[LEWorkViewController class]]); - [[segue destinationViewController] setTalk:talk]; - } + } else if ([[segue identifier] isEqualToString:@"CreateComment"]) + talk = [self.issue addComment]; + else + assert(NO); + + assert([segue.destinationViewController isKindOfClass:[LEWorkViewController class]]); + [[segue destinationViewController] setTalk:talk]; } diff --git a/Lemacs/LEWorkViewController.h b/Lemacs/LEWorkViewController.h index 2641f19..79793d2 100644 --- a/Lemacs/LEWorkViewController.h +++ b/Lemacs/LEWorkViewController.h @@ -8,13 +8,15 @@ #import "LETalk.h" -@interface LEWorkViewController : UIViewController +@interface LEWorkViewController : UIViewController +@property (nonatomic, weak) IBOutlet UITextField *topicField; @property (nonatomic, weak) IBOutlet UITextView *textView; @property (nonatomic, weak) IBOutlet UISegmentedControl *segmentedControl; @property (strong, nonatomic) id talk; - (IBAction)cancel; +- (IBAction)delete; - (IBAction)reply; - (IBAction)save; - (IBAction)togglePreview:(UISegmentedControl *)segmentedControl; diff --git a/Lemacs/LEWorkViewController.m b/Lemacs/LEWorkViewController.m index 6f1d13c..7aacd4e 100644 --- a/Lemacs/LEWorkViewController.m +++ b/Lemacs/LEWorkViewController.m @@ -61,6 +61,32 @@ - (void)splitViewController:(UISplitViewController *)splitController willShowVie } +#pragma mark - UITextFieldDelegate + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; // return NO to not change text +{ + if (IsEmpty(textField.text) && !IsEmpty(string)) + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save)]; + else if (!IsEmpty(textField.text) && IsEmpty([textField.text stringByReplacingCharactersInRange:range withString:string])) + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(delete)]; + + return YES; +} + +- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField; +{ + return [self.talk isKindOfClass:[GHIssue class]]; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField; +{ + GHIssue *issue = (GHIssue *)self.talk; + assert([self.talk isKindOfClass:[GHIssue class]]); + [issue setChangeValue:textField.text forPropertyNamed:kLETalkTitleKey]; + [[GHStore sharedStore] save]; +} + + #pragma mark - UITextViewDelegate - (void)textViewDidBeginEditing:(UITextView *)textView; @@ -80,11 +106,13 @@ - (void)textViewDidChange:(UITextView *)textView; GHManagedObject *editedObject = (GHManagedObject *)self.talk; assert([editedObject isKindOfClass:[GHManagedObject class]]); - if (!self.talk.plainBody.length && textView.text.length) + if (!self.talk.plainBody.length && textView.text.length) { self.navigationItem.leftBarButtonItem = nil; - else if (!textView.text.length && self.talk.plainBody.length) + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save)]; + } else if (!textView.text.length && self.talk.plainBody.length) { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)]; - + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(delete)]; + } [editedObject setChangeValue:textView.text forPropertyNamed:kLETalkBodyKey]; } @@ -98,8 +126,21 @@ - (void)setEditing:(BOOL)editing; self.textView.editable = editing; self.textView.font = [UIFont markdownParagraphFont]; // To clear style + super.editing = editing; + + if ([self.talk isKindOfClass:[GHIssue class]] && editing) { + self.navigationItem.prompt = nil; + self.topicField.hidden = NO; + self.topicField.text = self.talk.topic; + } else { + self.navigationItem.prompt = self.talk.topic; + self.topicField.hidden = YES; + self.topicField.text = nil; + } if (editing) { + self.segmentedControl.selectedSegmentIndex = 1; + [self.textView becomeFirstResponder]; self.textView.text = self.talk.plainBody; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save)]; if (!self.talk.hasChanges || (!self.talk.plainBody.length && !self.textView.text.length)) @@ -107,13 +148,14 @@ - (void)setEditing:(BOOL)editing; else self.navigationItem.leftBarButtonItem = nil; } else { + self.segmentedControl.selectedSegmentIndex = 0; + [self.textView resignFirstResponder]; self.textView.attributedText = self.talk.styledBody; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(reply)]; - self.navigationItem.leftBarButtonItem = nil; + if (!self.talk.hasChanges) { + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(reply)]; + self.navigationItem.leftBarButtonItem = nil; + } } - - super.editing = editing; - [self.textView becomeFirstResponder]; } - (void)setTalk:(id )talk; @@ -122,7 +164,7 @@ - (void)setTalk:(id )talk; return; _talk = talk; - + [self configureView]; } @@ -140,8 +182,7 @@ - (IBAction)cancel; BOOL delete = isEmpty && isNew; BOOL revert = isEmpty && !isNew; - if (delete) { // Otherwise, you have to kill it from the list. - // In the future, we can + if (delete) { GHManagedObject *doomedObject = (GHManagedObject *)self.talk; assert([doomedObject isKindOfClass:[GHManagedObject class]]); self.talk = nil; @@ -157,6 +198,18 @@ - (IBAction)cancel; [self.navigationController popViewControllerAnimated:YES]; } +- (IBAction)delete; +{ + self.editing = NO; + if ([self.talk isKindOfClass:[GHIssue class]]) { + [[GHStore sharedStore] saveIssue:(GHIssue *)self.talk]; + [self.navigationController popToRootViewControllerAnimated:YES]; + } else if ([self.talk isKindOfClass:[GHComment class]]) { + [[GHStore sharedStore] saveComment:(GHComment *)self.talk]; + [self.navigationController popViewControllerAnimated:YES]; + } +} + - (IBAction)reply; { GHIssue *issue; @@ -173,10 +226,18 @@ - (IBAction)reply; - (IBAction)save; { NSLog(@"%@", NSStringFromSelector(_cmd)); - // If new - // Commit and sync - // else - // Show commit screen + assert(!IsEmpty(self.talk.plainBody) && self.talk.hasChanges); // Otherwise the save button shouldn't be available. + + // Save it locally, start the server push + [[GHStore sharedStore] sync]; + + self.editing = NO; + if ([self.talk isKindOfClass:[GHIssue class]]) + [[GHStore sharedStore] saveIssue:(GHIssue *)self.talk]; + else if ([self.talk isKindOfClass:[GHComment class]]) + [[GHStore sharedStore] saveComment:(GHComment *)self.talk]; + + [self.navigationController popViewControllerAnimated:YES]; } - (IBAction)togglePreview:(UISegmentedControl *)segmentedControl; @@ -188,10 +249,10 @@ - (void)configureView; { if (!self.talk) return; // This should only happen while the controller is still being set up + // It also happens during cancel // Default to editing mode if this is an uncommited talk self.editing = IsEmpty([(NSObject *)self.talk valueForKey:kLETalkBodyKey]) || self.talk.hasChanges; - self.navigationItem.prompt = self.talk.plainTitle; self.segmentedControl.selectedSegmentIndex = self.editing ? 1 : 0; }