// // FeedDetailViewController.m // NewsBlur // // Created by Samuel Clay on 6/20/10. // Copyright 2010 NewsBlur. All rights reserved. // #import "FeedDetailViewController.h" #import "NewsBlurAppDelegate.h" #import "FeedDetailTableCell.h" #import "ASIFormDataRequest.h" #import "JSON.h" #define kTableViewRowHeight 60; @implementation FeedDetailViewController @synthesize storyTitlesTable, feedViewToolbar, feedScoreSlider, feedMarkReadButton; @synthesize stories; @synthesize appDelegate; @synthesize jsonString; @synthesize feedPage; @synthesize pageFetching; @synthesize pageFinished; @synthesize intelligenceControl; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { } return self; } - (void)viewWillAppear:(BOOL)animated { // NSLog(@"Loaded Feed view: %@", appDelegate.activeFeed); self.pageFinished = NO; self.title = [appDelegate.activeFeed objectForKey:@"feed_title"]; NSMutableArray *indexPaths = [NSMutableArray array]; for (id i in appDelegate.recentlyReadStories) { NSLog(@"Read story %d: %@", [i intValue], appDelegate.recentlyReadStories); NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[i intValue] inSection:0]; [indexPaths addObject:indexPath]; } [appDelegate.recentlyReadStories removeAllObjects]; if ([indexPaths count] > 0) { NSLog(@"Having read %d stories: %@", [appDelegate.recentlyReadStories count], appDelegate.recentlyReadStories); [self.storyTitlesTable beginUpdates]; [self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; [self.storyTitlesTable endUpdates]; } [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_red.png"] forSegmentAtIndex:0]; [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_yellow.png"] forSegmentAtIndex:1]; [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_green.png"] forSegmentAtIndex:2]; [self.intelligenceControl addTarget:self action:@selector(selectIntelligence) forControlEvents:UIControlEventValueChanged]; [self.intelligenceControl setSelectedSegmentIndex:[appDelegate selectedIntelligence]+1]; [super viewWillAppear:animated]; } - (void)viewDidAppear:(BOOL)animated { //[appDelegate showNavigationBar:animated]; [[storyTitlesTable cellForRowAtIndexPath:[storyTitlesTable indexPathForSelectedRow]] setSelected:NO]; // TODO: DESELECT CELL [super viewDidAppear:animated]; } - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; //NSLog(@"Unloading detail view: %@", self); self.appDelegate = nil; self.jsonString = nil; self.intelligenceControl = nil; } - (void)dealloc { [appDelegate release]; [stories release]; [jsonString release]; [intelligenceControl release]; [super dealloc]; } #pragma mark - #pragma mark Initialization - (void)fetchFeedDetail:(int)page { // NSLog(@"fetching page %d. %d fetching, %d finished.", self.feedPage, self.pageFetching, self.pageFinished); if ([appDelegate.activeFeed objectForKey:@"id"] != nil && !self.pageFetching && !self.pageFinished) { self.feedPage = page; self.pageFetching = YES; int storyCount = appDelegate.storyCount; if (storyCount == 0) { [self.storyTitlesTable reloadData]; [storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]; } NSString *theFeedDetailURL = [[NSString alloc] initWithFormat:@"http://nb.local.host:8000/reader/feed/%@?page=%d", [appDelegate.activeFeed objectForKey:@"id"], self.feedPage]; //NSLog(@"Url: %@", theFeedDetailURL); NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL]; [theFeedDetailURL release]; jsonString = [[NSMutableData data] retain]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL: urlFeedDetail]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; [connection release]; [request release]; } } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [jsonString setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [jsonString appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSString *jsonS = [[NSString alloc] initWithData:jsonString encoding:NSUTF8StringEncoding]; NSDictionary *results = [[NSDictionary alloc] initWithDictionary:[jsonS JSONValue]]; NSArray *newStories = [results objectForKey:@"stories"]; NSInteger newStoriesCount = [newStories count]; NSInteger existingStoriesCount = appDelegate.storyCount; int storyCount = 0; if (self.feedPage == 1) { [appDelegate setStories:newStories]; } else if (newStoriesCount > 0) { for (int i=0; i < [appDelegate storyCount]; i++) { NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:i]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; int intelligenceLevel = [appDelegate selectedIntelligence]; if (score >= intelligenceLevel) { storyCount += 1; } } [appDelegate addStories:newStories]; } // NSLog(@"Stories: %d stories, page %d. %d new stories.", existingStoriesCount, self.feedPage, newStoriesCount); if (existingStoriesCount > 0 && newStoriesCount > 0) { NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; int visibleRows = 0; int intelligenceLevel = [appDelegate selectedIntelligence]; for (int i=0; i < newStoriesCount; i++) { NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:existingStoriesCount+i]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; if (score >= intelligenceLevel) { visibleRows += 1; int row = storyCount+visibleRows; [indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:0]]; } } [self.storyTitlesTable insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; [indexPaths release]; } else if (newStoriesCount > 0) { [self.storyTitlesTable reloadData]; } else if (newStoriesCount == 0) { self.pageFinished = YES; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:existingStoriesCount inSection:0]; NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; [self.storyTitlesTable beginUpdates]; [self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; [self.storyTitlesTable endUpdates]; } self.pageFetching = NO; [results release]; [jsonS release]; [jsonString release]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // release the connection, and the data object [connection release]; // receivedData is declared as a method instance elsewhere [jsonString release]; // inform the user NSLog(@"Connection failed! Error - %@", [error localizedDescription]); self.pageFetching = NO; } - (UITableViewCell *)makeLoadingCell { UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"NoReuse"] autorelease]; cell.selectionStyle = UITableViewCellSelectionStyleNone; if (self.pageFinished) { UIView * blue = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 321, 17)]; [cell.contentView addSubview:blue]; blue.backgroundColor = [UIColor whiteColor]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.5f]; blue.backgroundColor = [UIColor colorWithRed:.7f green:0.7f blue:0.7f alpha:1.0f]; [UIView commitAnimations]; [blue release]; } else { cell.textLabel.text = @"Loading..."; UIActivityIndicatorView *spinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease]; // Spacer is a 1x1 transparent png UIImage *spacer = [UIImage imageNamed:@"spacer"]; UIGraphicsBeginImageContext(spinner.frame.size); [spacer drawInRect:CGRectMake(0,0,spinner.frame.size.width,spinner.frame.size.height)]; UIImage* resizedSpacer = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); cell.imageView.image = resizedSpacer; [cell.imageView addSubview:spinner]; [spinner startAnimating]; } return cell; } #pragma mark - #pragma mark Table View - Feed List - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // The + 1 is for the finished/loading bar. NSLog(@"Current intelligence: %d", [appDelegate selectedIntelligence]); int storyCount = 0; for (int i=0; i < [appDelegate storyCount]; i++) { NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:i]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; int intelligenceLevel = [appDelegate selectedIntelligence]; if (score >= intelligenceLevel) { storyCount += 1; } else { NSLog(@"Skipping %@", [story objectForKey:@"story_title"]); } } return storyCount + 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *FeedDetailCellIdentifier = @"FeedDetailCellIdentifier"; FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView dequeueReusableCellWithIdentifier:FeedDetailCellIdentifier]; if (cell == nil) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"FeedDetailTableCell" owner:self options:nil]; for (id oneObject in nib) { if ([oneObject isKindOfClass:[FeedDetailTableCell class]]) { cell = (FeedDetailTableCell *)oneObject; } } } if (indexPath.row >= appDelegate.storyCount) { return [self makeLoadingCell]; } NSDictionary *story = [self getStoryAtRow:indexPath.row]; if ([[story objectForKey:@"story_authors"] class] != [NSNull class]) { cell.storyAuthor.text = [[story objectForKey:@"story_authors"] uppercaseString]; } else { cell.storyAuthor.text = @""; } cell.storyTitle.text = [story objectForKey:@"story_title"]; cell.storyDate.text = [story objectForKey:@"short_parsed_date"]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; if (score > 0) { cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_green.png"]; } else if (score == 0) { cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_yellow.png"]; } else if (score < 0) { cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_red.png"]; } if ([[story objectForKey:@"read_status"] intValue] != 1) { // Unread story cell.storyTitle.textColor = [UIColor colorWithRed:0.1f green:0.1f blue:0.1f alpha:1.0]; cell.storyTitle.font = [UIFont fontWithName:@"Helvetica-Bold" size:12]; cell.storyAuthor.textColor = [UIColor colorWithRed:0.58f green:0.58f blue:0.58f alpha:1.0]; cell.storyAuthor.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; cell.storyDate.textColor = [UIColor colorWithRed:0.14f green:0.18f blue:0.42f alpha:1.0]; cell.storyDate.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; cell.storyUnreadIndicator.alpha = 1; } else { // Read story cell.storyTitle.textColor = [UIColor colorWithRed:0.15f green:0.25f blue:0.25f alpha:0.9]; cell.storyTitle.font = [UIFont fontWithName:@"Helvetica" size:12]; cell.storyAuthor.textColor = [UIColor colorWithRed:0.58f green:0.58f blue:0.58f alpha:0.5]; cell.storyAuthor.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; cell.storyDate.textColor = [UIColor colorWithRed:0.14f green:0.18f blue:0.42f alpha:0.5]; cell.storyDate.font = [UIFont fontWithName:@"Helvetica" size:10]; cell.storyUnreadIndicator.alpha = 0.15f; } return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row < appDelegate.storyCount) { [appDelegate setActiveStory:[[appDelegate activeFeedStories] objectAtIndex:indexPath.row]]; [appDelegate loadStoryDetailView]; } } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // NSLog(@"Height for row: %d of %d stories. (Finished: %d)", indexPath.row, appDelegate.storyCount, self.pageFinished); if (indexPath.row >= appDelegate.storyCount) { if (self.pageFinished) return 16; else return kTableViewRowHeight; } else { NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:indexPath.row]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; int intelligenceLevel = [appDelegate selectedIntelligence]; if (score >= intelligenceLevel) { // NSLog(@"Score: %d on %d %@ - %@", score, intelligenceLevel-1, score >= (intelligenceLevel-1) ? @"SHOW" : @"HIDE", // [story objectForKey:@"story_title"]); return kTableViewRowHeight; } else { return 0.0f; } } } - (void)scrollViewDidScroll: (UIScrollView *)scroll { NSInteger currentOffset = scroll.contentOffset.y; NSInteger maximumOffset = scroll.contentSize.height - scroll.frame.size.height; if (maximumOffset - currentOffset <= 60.0) { [self fetchFeedDetail:self.feedPage+1]; } } - (IBAction)markAllRead { NSString *urlString = @"http://nb.local.host:8000/reader/mark_feed_as_read"; NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; [request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"]; [request setDelegate:nil]; [request startAsynchronous]; [appDelegate markActiveFeedAllRead]; [appDelegate.navigationController popToViewController:[appDelegate.navigationController.viewControllers objectAtIndex:0] animated:YES]; } - (IBAction)selectIntelligence { NSInteger newLevel = [self.intelligenceControl selectedSegmentIndex] - 1; NSInteger previousLevel = [appDelegate selectedIntelligence]; NSMutableArray *insertIndexPaths = [NSMutableArray array]; NSMutableArray *deleteIndexPaths = [NSMutableArray array]; [appDelegate setSelectedIntelligence:newLevel]; for (int i=0; i < [appDelegate storyCount]; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:i]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; if (previousLevel == -1) { if (newLevel == 0 && score == -1) { [deleteIndexPaths addObject:indexPath]; } else if (newLevel == 1 && score < 1) { [deleteIndexPaths addObject:indexPath]; } } else if (previousLevel == 0) { if (newLevel == -1 && score == -1) { [insertIndexPaths addObject:indexPath]; } else if (newLevel == 1 && score == 0) { [deleteIndexPaths addObject:indexPath]; } } else if (previousLevel == 1) { if (newLevel == 0 && score == 0) { [insertIndexPaths addObject:indexPath]; } else if (newLevel == -1 && score < 1) { [insertIndexPaths addObject:indexPath]; } } } NSLog(@"Select: %d deleted, %d inserted. Pre: %d, post: %d", [deleteIndexPaths count], [insertIndexPaths count], previousLevel, newLevel); [self.storyTitlesTable beginUpdates]; [self.storyTitlesTable deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationNone]; [self.storyTitlesTable insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationNone]; [self.storyTitlesTable endUpdates]; } - (NSDictionary *)getStoryAtRow:(NSInteger)indexPathRow { int row = 0; int intelligenceLevel = [appDelegate selectedIntelligence]; for (int i=0; i < [appDelegate storyCount]; i++) { NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:i]; int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; if (score >= intelligenceLevel) { if (row == indexPathRow) { row = i; break; } else { row++; } } } NSLog(@"i: %d, %d, %d", row, indexPathRow, intelligenceLevel); return [appDelegate.activeFeedStories objectAtIndex:row]; } @end