//
// CDIListViewController.m
// Cheddar for iOS
//
// Created by Sam Soffes on 3/31/12.
// Copyright (c) 2012 Nothing Magical. All rights reserved.
//
#import "CDIListViewController.h"
#import "CDITaskTableViewCell.h"
#import "CDIAddTaskView.h"
#import "CDIAddTaskAnimationView.h"
#import "CDIAttributedLabel.h"
#import "CDICreateListViewController.h"
#import "CDITasksPlaceholderView.h"
#import "CDIRenameTaskViewController.h"
#import "CDIWebViewController.h"
#import "CDISettingsTapPickerViewController.h"
#import "UIColor+CheddariOSAdditions.h"
#import "UIFont+CheddariOSAdditions.h"
@interface CDIListViewController () <CDIAddTaskViewDelegate, TTTAttributedLabelDelegate, UITextFieldDelegate, UIActionSheetDelegate, UIAlertViewDelegate>
@property (nonatomic, strong) CDIAddTaskView *addTaskView;
@property (nonatomic, strong) NSMutableArray *currentTags;
- (void)_renameList:(id)sender;
- (void)_archiveTasks:(id)sender;
- (void)_archiveAllTasks:(id)sender;
- (void)_archiveCompletedTasks:(id)sender;
- (void)_editTask:(CDKTask *)task;
@end
@implementation CDIListViewController {
NSIndexPath *_textEditingIndexPath;
dispatch_semaphore_t _createTaskSemaphore;
}
@synthesize addTaskView = _addTaskView;
@synthesize currentTags = _currentTags;
@synthesize focusKeyboard = _focusKeyboard;
- (void)setManagedObject:(SSManagedObject *)managedObject {
CDKList *list = (CDKList *)self.managedObject;
void *context = (__bridge void *)self;
if (list) {
[list removeObserver:self forKeyPath:@"title" context:context];
[list removeObserver:self forKeyPath:@"archivedAt" context:context];
}
[super setManagedObject:managedObject];
list = (CDKList *)self.managedObject;
self.title = self.list.title;
self.tableView.hidden = self.list == nil;
if (list == nil) {
return;
}
[list addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:context];
[list addObserver:self forKeyPath:@"archivedAt" options:NSKeyValueObservingOptionNew context:context];
self.ignoreChange = YES;
[self closeTags];
self.fetchedResultsController.fetchRequest.predicate = self.predicate;
[self.fetchedResultsController performFetch:nil];
[self.tableView reloadData];
self.ignoreChange = NO;
[self setEditing:NO animated:NO];
[self setLoading:NO animated:NO];
[SSRateLimit executeBlock:^{
[self refresh:nil];
} name:[NSString stringWithFormat:@"refresh-list-%@", self.list.remoteID] limit:30.0];
}
- (CDKList *)list {
return (CDKList *)self.managedObject;
}
- (CDIAddTaskView *)addTaskView {
if (!_addTaskView) {
_addTaskView = [[CDIAddTaskView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, [CDIAddTaskView height])];
_addTaskView.delegate = self;
[_addTaskView.renameListButton addTarget:self action:@selector(_renameList:) forControlEvents:UIControlEventTouchUpInside];
[_addTaskView.archiveTasksButton addTarget:self action:@selector(_archiveTasks:) forControlEvents:UIControlEventTouchUpInside];
[_addTaskView.archiveAllTasksButton addTarget:self action:@selector(_archiveAllTasks:) forControlEvents:UIControlEventTouchUpInside];
[_addTaskView.archiveCompletedTasksButton addTarget:self action:@selector(_archiveCompletedTasks:) forControlEvents:UIControlEventTouchUpInside];
}
return _addTaskView;
}
#pragma mark - NSObject
- (id)init {
if ((self = [super init])) {
_createTaskSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(_createTaskSemaphore);
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Tasks" style:UIBarButtonItemStyleBordered target:nil action:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.managedObject = nil;
dispatch_semaphore_wait(_createTaskSemaphore, DISPATCH_TIME_FOREVER);
dispatch_release(_createTaskSemaphore);
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setEditing:NO animated:NO];
self.view.backgroundColor = [UIColor cheddarArchesColor];
self.tableView.hidden = self.list == nil;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake([CDIAddTaskView height], 0.0f, 0.0f, 0.0f);
self.pullToRefreshView.bottomBorderColor = [UIColor colorWithWhite:0.8f alpha:1.0f];
self.noContentView = [[CDITasksPlaceholderView alloc] initWithFrame:CGRectZero];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[SSRateLimit executeBlock:^{
[self refresh:nil];
} name:[NSString stringWithFormat:@"refresh-list-%@", self.list.remoteID] limit:30.0];
if (_focusKeyboard) {
[self.addTaskView.textField becomeFirstResponder];
_focusKeyboard = NO;
}
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
if (!self.navigationItem.rightBarButtonItem) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Edit" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleEditMode:)];
}
self.navigationItem.rightBarButtonItem.title = editing ? @"Done" : @"Edit";
self.navigationItem.rightBarButtonItem.enabled = self.currentTags.count == 0 && self.list;
[self.addTaskView setEditing:editing animated:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return YES;
}
return toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}
#pragma mark - SSManagedViewController
+ (Class)fetchedResultsControllerClass {
return [SSFilterableFetchedResultsController class];
}
- (Class)entityClass {
return [CDKTask class];
}
- (NSPredicate *)predicate {
return [NSPredicate predicateWithFormat:@"list = %@ AND archivedAt = nil", self.list];
}
#pragma mark - SSManagedTableViewController
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
CDITaskTableViewCell *taskCell = (CDITaskTableViewCell *)cell;
taskCell.task = [self objectForViewIndexPath:indexPath];
taskCell.attributedLabel.tag = indexPath.row;
}
#pragma mark - CDIManagedTableViewController
- (void)editRow:(UIGestureRecognizer *)editingGestureRecognizer {
NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)editingGestureRecognizer.view];
if (!indexPath) {
return;
}
CDKTask *task = [self objectForViewIndexPath:indexPath];
[self _editTask:task];
}
- (void)coverViewTapped:(id)sender {
[self.addTaskView.textField resignFirstResponder];
}
#pragma mark - Actions
- (void)refresh:(id)sender {
if (self.list == nil || self.loading) {
return;
}
CDKList *list = self.list;
self.loading = YES;
[[CDKHTTPClient sharedClient] getTasksWithList:list success:^(AFJSONRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
self.loading = NO;
});
} failure:^(AFJSONRequestOperation *operation, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[SSRateLimit resetLimitForName:[NSString stringWithFormat:@"refresh-list-%@", self.list.remoteID]];
self.loading = NO;
});
}];
}
#pragma mark - Tags
- (void)addTag:(CDKTag *)tag {
if (!tag || [_currentTags containsObject:tag]) {
return;
}
if (!_currentTags) {
_currentTags = [[NSMutableArray alloc] init];
}
[_currentTags addObject:tag];
SSFilterableFetchedResultsController *controller = (SSFilterableFetchedResultsController *)self.fetchedResultsController;
NSString *filterName = [_currentTags componentsJoinedByString:@","];
[self.addTaskView showTags:_currentTags];
[controller addFilterPredicate:^BOOL(id obj) {
return [(CDKTask *)obj hasTags:_currentTags];
} forKey:filterName];
[controller setActiveFilterByKey:filterName];
self.navigationItem.rightBarButtonItem.enabled = NO;
[self setEditing:NO animated:YES];
}
- (void)closeTags {
[self.addTaskView closeTags];
SSFilterableFetchedResultsController *controller = (SSFilterableFetchedResultsController *)self.fetchedResultsController;
[controller removeCurrentFilter];
[_currentTags removeAllObjects];
self.navigationItem.rightBarButtonItem.enabled = YES;
}
#pragma mark - Private
- (void)updateTableViewOffsets {
CGFloat offset = self.tableView.contentOffset.y;
CGFloat top = [CDIAddTaskView height] - fminf(0.0f, offset);
CGFloat bottom = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? self.keyboardRect.size.height : 0.0f;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(top, 0.0f, bottom, 0.0f);
self.pullToRefreshView.defaultContentInset = UIEdgeInsetsMake(0.0f, 0.0f, bottom, 0.0f);
self.addTaskView.shadowView.alpha = fmaxf(0.0f, fminf(offset / 24.0f, 1.0f));
}
- (void)_renameList:(id)sender {
CDICreateListViewController *viewController = [[CDICreateListViewController alloc] init];
viewController.list = self.list;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
}
- (void)_archiveTasks:(id)sender {
// TODO: This is super ugly
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:@"Archive Completed", @"Archive All", nil];
[actionSheet showFromRect:[sender frame] inView:self.view animated:YES];
} else {
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Archive Completed", @"Archive All", nil];
actionSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
[actionSheet showInView:self.navigationController.view];
}
}
- (void)_archiveAllTasks:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Archive All Tasks" message:@"Do you want to archive all of the tasks in this list?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Archive", nil];
alert.tag = 1;
[alert show];
}
- (void)_archiveCompletedTasks:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Archive Completed Tasks" message:@"Do you want to archive all of the completed tasks in this list?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Archive", nil];
alert.tag = 2;
[alert show];
}
- (void)_editTask:(CDKTask *)task {
CDIRenameTaskViewController *viewController = [[CDIRenameTaskViewController alloc] init];
viewController.task = task;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
}
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *const cellIdentifier = @"cellIdentifier";
CDITaskTableViewCell *cell = (CDITaskTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[CDITaskTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.attributedLabel.delegate = self;
[cell setEditingAction:@selector(editRow:) forTarget:self];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.addTaskView;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *action = [CDISettingsTapPickerViewController selectedKey];
// Nothing
if ([action isEqualToString:kCDITapActionNothingKey]) {
return;
}
CDKTask *task = [self objectForViewIndexPath:indexPath];
// Complete
if ([action isEqualToString:kCDITapActionCompleteKey]) {
[task toggleCompleted];
return;
}
// Edit
if ([action isEqualToString:kCDITapActionEditKey]) {
[self _editTask:task];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
return @"Archive";
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle != UITableViewCellEditingStyleDelete) {
return;
}
CDKTask *task = [self objectForViewIndexPath:indexPath];
task.archivedAt = [NSDate date];
[task save];
[task update];
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
if (sourceIndexPath.row == destinationIndexPath.row) {
return;
}
self.ignoreChange = YES;
NSMutableArray *tasks = [self.fetchedResultsController.fetchedObjects mutableCopy];
CDKTask *task = [self objectForViewIndexPath:sourceIndexPath];
[tasks removeObject:task];
[tasks insertObject:task atIndex:destinationIndexPath.row];
NSInteger i = 0;
for (task in tasks) {
task.position = [NSNumber numberWithInteger:i++];
}
[self.managedObjectContext save:nil];
self.ignoreChange = NO;
[CDKTask sortWithObjects:tasks];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return [CDIAddTaskView height];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CDKTask *task = [self objectForViewIndexPath:indexPath];
return [CDITaskTableViewCell cellHeightForTask:task width:tableView.frame.size.width];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([self showingCoverView]) {
[self.addTaskView.textField resignFirstResponder];
}
[super scrollViewDidScroll:scrollView];
}
#pragma mark - CDIAddTaskViewDelegate
- (void)addTaskView:(CDIAddTaskView *)addTaskView didReturnWithTitle:(NSString *)title {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
dispatch_semaphore_wait(_createTaskSemaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
CDIAddTaskAnimationView *animation = [[CDIAddTaskAnimationView alloc] initWithFrame:self.view.bounds];
animation.title = title;
[self.view addSubview:animation];
self.ignoreChange = YES;
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:numberOfRows inSection:0];
CDKTask *task = [[CDKTask alloc] init];
task.text = title;
task.displayText = title;
task.list = self.list;
task.position = [NSNumber numberWithInteger:self.list.highestPosition + 1];
CGPoint point = CGPointZero;
if (numberOfRows > 0) {
CGRect rect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:numberOfRows - 1 inSection:0]];
point = rect.origin;
point.y += rect.size.height;
} else {
point.y = [CDIAddTaskView height];
}
[animation animationToPoint:point height:self.tableView.bounds.size.height insertTask:^{
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
self.ignoreChange = NO;
} completion:^{
[animation removeFromSuperview];
dispatch_semaphore_signal(_createTaskSemaphore);
}];
[task createWithSuccess:nil failure:^(AFJSONRequestOperation *remoteOperation, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
addTaskView.textField.text = title;
SSHUDView *hud = [[SSHUDView alloc] init];
[hud failQuicklyWithTitle:@"Failed to create task"];
});
}];
});
});
}
- (void)addTaskViewDidBeginEditing:(CDIAddTaskView *)addTaskView {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[self showCoverView];
}
}
- (void)addTaskViewDidEndEditing:(CDIAddTaskView *)addTaskView {
[self hideCoverView];
}
- (void)addTaskViewShouldCloseTags:(CDIAddTaskView *)addTaskView; {
[self closeTags];
}
#pragma mark - TTTAttributedLabelDelegate
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
// Open tag
if ([url.scheme isEqualToString:@"x-cheddar-tag"]) {
CDKTag *tag = [CDKTag existingTagWithName:url.host];
[self addTag:tag];
return;
}
// Open browser
if ([url.scheme.lowercaseString isEqualToString:@"http"] || [url.scheme.lowercaseString isEqualToString:@"https"]) {
CDIWebViewController *viewController = [[CDIWebViewController alloc] init];
[viewController loadURL:url];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self.splitViewController presentModalViewController:navigationController animated:YES];
} else {
[self.navigationController pushViewController:viewController animated:YES];
}
return;
}
// Open other URLs
[[UIApplication sharedApplication] openURL:url];
}
#pragma mark - UIActionSheetDelegate
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
[self setEditing:NO animated:YES];
[self.list archiveCompletedTasks];
} else if (buttonIndex == 1) {
[self setEditing:NO animated:YES];
[self.list archiveAllTasks];
}
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
return;
}
[self setEditing:NO animated:YES];
if (alertView.tag == 1) {
[self.list archiveAllTasks];
[self setEditing:NO animated:YES];
} else if (alertView.tag == 2) {
[self.list archiveCompletedTasks];
}
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
CDKTask *task = [self objectForViewIndexPath:self.editingIndexPath];
task.text = textField.text;
task.displayText = textField.text;
task.entities = nil;
[task save];
[task update];
[self endCellTextEditing];
return NO;
}
#pragma mark - NSKeyValueObserving
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != (__bridge void *)self) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if ([keyPath isEqualToString:@"title"]) {
self.title = [change objectForKey:NSKeyValueChangeNewKey];
} else if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad && [keyPath isEqualToString:@"archivedAt"]) {
if ([change objectForKey:NSKeyValueChangeNewKey] != [NSNull null]) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
}
@end
Typing.io uses JavaScript to analyze your typing. Please enable or whitelist JavaScript.