// // TrainerViewController.m // NewsBlur // // Created by Samuel Clay on 12/24/12. // Copyright (c) 2012 NewsBlur. All rights reserved. // #import "TrainerViewController.h" #import "NBContainerViewController.h" #import "StringHelper.h" #import "Utilities.h" #import "Base64.h" #import "AFNetworking.h" #import "StoriesCollection.h" @implementation TrainerViewController @synthesize closeButton; @synthesize webView; @synthesize navBar; @synthesize appDelegate; @synthesize feedTrainer; @synthesize storyTrainer; @synthesize feedLoaded; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.appDelegate = [NewsBlurAppDelegate sharedAppDelegate]; UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithTitle:@"Done Training" style:UIBarButtonSystemItemDone target:self action:@selector(doCloseDialog:)]; self.navigationItem.rightBarButtonItem = done; [self hideGradientBackground:webView]; [self.webView.scrollView setDelaysContentTouches:YES]; [self.webView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal]; } - (void) hideGradientBackground:(UIView*)theView { for (UIView * subview in theView.subviews) { if ([subview isKindOfClass:[UIImageView class]]) subview.hidden = YES; [self hideGradientBackground:subview]; } } - (void)viewWillAppear:(BOOL)animated { [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects: [[UIMenuItem alloc] initWithTitle:@"👎 Hide" action:@selector(hideTitle:)], [[UIMenuItem alloc] initWithTitle:@"👍 Focus" action:@selector(focusTitle:)], nil]]; UILabel *titleLabel = (UILabel *)[appDelegate makeFeedTitle:appDelegate.storiesCollection.activeFeed]; self.navigationItem.titleView = titleLabel; [MBProgressHUD hideHUDForView:self.view animated:NO]; if (!feedLoaded) { MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; HUD.labelText = @"Loading trainer..."; NSString *feedId = [self feedId]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/reader/feeds_trainer?feed_id=%@", NEWSBLUR_URL, feedId]]; AFJSONRequestOperation *request = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url] success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { [MBProgressHUD hideHUDForView:self.view animated:YES]; NSDictionary *results = [JSON objectAtIndex:0]; NSMutableDictionary *newClassifiers = [[results objectForKey:@"classifiers"] mutableCopy]; [appDelegate.storiesCollection.activeClassifiers setObject:newClassifiers forKey:feedId]; appDelegate.storiesCollection.activePopularAuthors = [results objectForKey:@"feed_authors"]; appDelegate.storiesCollection.activePopularTags = [results objectForKey:@"feed_tags"]; [self renderTrainer]; } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { NSLog(@"Failed fetch trainer."); [self informError:@"Could not load trainer"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [appDelegate.masterContainerViewController hidePopover]; } else { [appDelegate.navigationController dismissViewControllerAnimated:YES completion:nil]; } }); }]; [request start]; } else { [self renderTrainer]; } } - (NSString *)feedId { NSString *feedId; if (appDelegate.storiesCollection.activeFeed && !appDelegate.storiesCollection.isSocialView) { feedId = [NSString stringWithFormat:@"%@", [appDelegate.storiesCollection.activeFeed objectForKey:@"id"]]; } else if (appDelegate.activeStory) { feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; } return feedId; } - (void)renderTrainer { NSString *path = [[NSBundle mainBundle] bundlePath]; NSURL *baseURL = [NSURL fileURLWithPath:path]; [self.webView loadHTMLString:[self makeTrainerHTML] baseURL:baseURL]; } - (void)refresh { if (self.view.hidden || self.view.superview == nil) { NSLog(@"Trainer hidden, ignoring redraw."); return; } NSString *headerString = [[[self makeTrainerSections] stringByReplacingOccurrencesOfString:@"\'" withString:@"\\'"] stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; NSString *jsString = [NSString stringWithFormat:@"document.getElementById('NB-trainer').innerHTML = '%@';", headerString]; [self.webView stringByEvaluatingJavaScriptFromString:jsString]; [self.webView stringByEvaluatingJavaScriptFromString:@"attachFastClick({skipEvent: true});"]; } - (void)viewDidAppear:(BOOL)animated { } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"about:blank"]]; [[UIMenuController sharedMenuController] setMenuItems:nil]; } #pragma mark - #pragma mark Story layout - (NSString *)makeTrainerHTML { NSString *trainerSections = [self makeTrainerSections]; int contentWidth = self.view.frame.size.width; NSString *contentWidthClass; if (contentWidth > 700) { contentWidthClass = @"NB-ipad-wide"; } else if (contentWidth > 480) { contentWidthClass = @"NB-ipad-narrow"; } else { contentWidthClass = @"NB-iphone"; } // set up layout values based on iPad/iPhone NSString *headerString = [NSString stringWithFormat:@ "" "", contentWidth]; NSString *footerString = [NSString stringWithFormat:@ "" "" ""]; NSString *htmlString = [NSString stringWithFormat:@ "" "%@" // header string "" "
%@
" "%@" // footer "" "", headerString, contentWidthClass, trainerSections, footerString ]; // NSLog(@"\n\n\n\nhtmlString:\n\n\n%@\n\n\n", htmlString); return htmlString; } - (NSString *)makeTrainerSections { NSString *storyAuthor = self.feedTrainer ? [self makeFeedAuthors] : [self makeStoryAuthor]; NSString *storyTags = self.feedTrainer ? [self makeFeedTags] : [self makeStoryTags]; NSString *storyTitle = self.feedTrainer ? @"" : [self makeTitle]; NSString *storyPublisher = [self makePublisher]; NSString *htmlString = [NSString stringWithFormat:@ "
" "
%@
" "
%@
" "
%@
" "
%@
" "
", storyTitle, storyAuthor, storyTags, storyPublisher]; return htmlString; } - (NSString *)makeStoryAuthor { NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; NSString *storyAuthor = @""; if ([[appDelegate.activeStory objectForKey:@"story_authors"] class] != [NSNull class] && [[appDelegate.activeStory objectForKey:@"story_authors"] length]) { NSString *author = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_authors"]]; if (author && [author class] != [NSNull class]) { int authorScore = [[[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"authors"] objectForKey:author] intValue]; storyAuthor = [NSString stringWithFormat:@"
" "
Story Authors
" "
" " " "
" "
", author, authorScore > 0 ? @"NB-story-author-positive" : authorScore < 0 ? @"NB-story-author-negative" : @"", [self makeClassifier:author withType:@"Author" score:authorScore]]; } } return storyAuthor; } - (NSString *)makeFeedAuthors { NSString *feedId = [self feedId]; NSString *feedAuthors = @""; NSArray *authorArray = appDelegate.storiesCollection.activePopularAuthors; NSMutableArray *userAuthorArray = [NSMutableArray array]; for (NSString *trainedAuthor in [[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"authors"] allKeys]) { BOOL found = NO; for (NSArray *classifierAuthor in authorArray) { if ([trainedAuthor isEqualToString:[classifierAuthor objectAtIndex:0]]) { found = YES; break; } } if (!found) { [userAuthorArray addObject:@[trainedAuthor, [NSNumber numberWithInt:0]]]; } } NSArray *authors = [userAuthorArray arrayByAddingObjectsFromArray:authorArray]; if ([authors count] > 0) { NSMutableArray *authorStrings = [NSMutableArray array]; for (NSArray *authorObj in authors) { NSString *author = [authorObj objectAtIndex:0]; int authorCount = [[authorObj objectAtIndex:1] intValue]; int authorScore = [[[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"authors"] objectForKey:author] intValue]; NSString *authorCountString = @""; if (authorCount) { authorCountString = [NSString stringWithFormat:@"×  %d", authorCount]; } NSString *authorHtml = [NSString stringWithFormat:@"
" " %@" " %@" "
", author, authorScore > 0 ? @"NB-story-author-positive" : authorScore < 0 ? @"NB-story-author-negative" : @"", [self makeClassifier:author withType:@"author" score:authorScore], authorCountString]; [authorStrings addObject:authorHtml]; } feedAuthors = [NSString stringWithFormat:@"
" "
Authors
" "
" " " "
" "
", [authorStrings componentsJoinedByString:@""]]; } return feedAuthors; } - (NSString *)makeStoryTags { NSString *feedId = [self feedId]; NSString *storyTags = @""; if ([appDelegate.activeStory objectForKey:@"story_tags"]) { NSArray *tagArray = [appDelegate.activeStory objectForKey:@"story_tags"]; if ([tagArray count] > 0) { NSMutableArray *tagStrings = [NSMutableArray array]; for (NSString *tag in tagArray) { int tagScore = [[[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"tags"] objectForKey:tag] intValue]; NSString *tagHtml = [NSString stringWithFormat:@"
" " %@" "
", tag, tagScore > 0 ? @"NB-story-tag-positive" : tagScore < 0 ? @"NB-story-tag-negative" : @"", [self makeClassifier:tag withType:@"Tag" score:tagScore]]; [tagStrings addObject:tagHtml]; } storyTags = [NSString stringWithFormat:@"
" "
Story Tags
" "
" " " "
" "
", [tagStrings componentsJoinedByString:@""]]; } } return storyTags; } - (NSString *)makeFeedTags { NSString *feedId = [self feedId]; NSString *feedTags = @""; NSArray *tagArray = appDelegate.storiesCollection.activePopularTags; NSMutableArray *userTagArray = [NSMutableArray array]; for (NSString *trainedTag in [[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"tags"] allKeys]) { BOOL found = NO; for (NSArray *classifierTag in tagArray) { if ([trainedTag isEqualToString:[classifierTag objectAtIndex:0]]) { found = YES; break; } } if (!found) { [userTagArray addObject:@[trainedTag, [NSNumber numberWithInt:0]]]; } } NSArray *tags = [userTagArray arrayByAddingObjectsFromArray:tagArray]; if ([tags count] > 0) { NSMutableArray *tagStrings = [NSMutableArray array]; for (NSArray *tagObj in tags) { NSString *tag = [tagObj objectAtIndex:0]; int tagCount = [[tagObj objectAtIndex:1] intValue]; int tagScore = [[[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"tags"] objectForKey:tag] intValue]; NSString *tagCountString = @""; if (tagCount) { tagCountString = [NSString stringWithFormat:@"×  %d", tagCount]; } NSString *tagHtml = [NSString stringWithFormat:@"
" " %@" " %@" "
", tag, tagScore > 0 ? @"NB-story-tag-positive" : tagScore < 0 ? @"NB-story-tag-negative" : @"", [self makeClassifier:tag withType:@"Tag" score:tagScore], tagCountString]; [tagStrings addObject:tagHtml]; } feedTags = [NSString stringWithFormat:@"
" "
Story Tags
" "
" " " "
" "
", [tagStrings componentsJoinedByString:@""]]; } return feedTags; } - (NSString *)makePublisher { NSString *feedId; NSString *feedTitle; if (self.feedTrainer) { feedId = [NSString stringWithFormat:@"%@", [appDelegate.storiesCollection.activeFeed objectForKey:@"id"]]; feedTitle = [appDelegate.storiesCollection.activeFeed objectForKey:@"feed_title"]; } else { feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; NSDictionary *feed = [appDelegate getFeed:feedId]; feedTitle = [feed objectForKey:@"feed_title"]; } int publisherScore = [[[[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"feeds"] objectForKey:feedId] intValue]; UIImage *favicon = [appDelegate getFavicon:feedId]; NSData *faviconData = UIImagePNGRepresentation(favicon); NSString *feedImageUrl = [NSString stringWithFormat:@"data:image/png;charset=utf-8;base64,%@", [faviconData base64Encoding]]; NSString *publisherTitle = [NSString stringWithFormat:@" %@", feedImageUrl, feedTitle]; NSString *storyPublisher = [NSString stringWithFormat:@"
" "
Publisher
" "
" "
" " %@" "
" "
" " 0 ? @"positive" : publisherScore < 0 ? @"negative" : @"", [self makeClassifier:publisherTitle withType:@"publisher" score:publisherScore]]; return storyPublisher; } - (NSString *)makeTitle { NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; NSString *storyTitle = [appDelegate.activeStory objectForKey:@"story_title"]; if (!storyTitle) { return @""; } NSMutableDictionary *classifiers = [[appDelegate.storiesCollection.activeClassifiers objectForKey:feedId] objectForKey:@"titles"]; NSMutableArray *titleStrings = [NSMutableArray array]; for (NSString *title in classifiers) { if ([storyTitle containsString:title]) { int titleScore = [[classifiers objectForKey:title] intValue]; NSString *titleClassifier = [NSString stringWithFormat:@ "
" " %@" "
", title, titleScore > 0 ? @"positive" : titleScore < 0 ? @"negative" : @"", [self makeClassifier:title withType:@"title" score:titleScore]]; [titleStrings addObject:titleClassifier]; } } NSString *titleClassifiers; if ([titleStrings count]) { titleClassifiers = [titleStrings componentsJoinedByString:@""]; } else { titleClassifiers = @"
Tap and hold the title
"; } NSString *titleTrainer = [NSString stringWithFormat:@"
" "
Story Title
" "
" "
" " %@" "
" " %@" "
" "
", storyTitle, titleClassifiers]; return titleTrainer; } - (NSString *)makeClassifier:(NSString *)classifierName withType:(NSString *)classifierType score:(int)score { NSString *classifier = [NSString stringWithFormat:@"" "
" "
" "
" "
" "" "
", classifierType, score > 0 ? @"like" : score < 0 ? @"dislike" : @"", classifierType, classifierName]; return classifier; } #pragma mark - #pragma mark Actions - (IBAction)doCloseDialog:(id)sender { [appDelegate.masterContainerViewController hidePopover]; [appDelegate.trainerViewController dismissViewControllerAnimated:YES completion:nil]; } - (void)changeTitle:(id)sender score:(int)score { NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; NSString *selectedTitle = [self.webView stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"]; [self.appDelegate toggleTitleClassifier:selectedTitle feedId:feedId score:score]; } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSURL *url = [request URL]; NSArray *urlComponents = [url pathComponents]; NSString *action = @""; NSString *feedId = [self feedId]; if ([urlComponents count] > 1) { action = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:1]]; } NSLog(@"Tapped url: %@", url); if ([[url host] isEqualToString: @"ios.newsblur.com"]){ if ([action isEqualToString:@"classify-author"]) { NSString *author = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]]; [self.appDelegate toggleAuthorClassifier:author feedId:feedId]; return NO; } else if ([action isEqualToString:@"classify-tag"]) { NSString *tag = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]]; [self.appDelegate toggleTagClassifier:tag feedId:feedId]; return NO; } else if ([action isEqualToString:@"classify-title"]) { NSString *title = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]]; [self.appDelegate toggleTitleClassifier:title feedId:feedId score:0]; return NO; } else if ([action isEqualToString:@"classify-feed"]) { NSString *feedId = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]]; [self.appDelegate toggleFeedClassifier:feedId]; return NO; } } return YES; } @end @implementation TrainerWebView - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (action == @selector(focusTitle:) || action == @selector(hideTitle:)) { return YES; } else { return NO; } } - (void)focusTitle:(id)sender { NewsBlurAppDelegate *appDelegate = [NewsBlurAppDelegate sharedAppDelegate]; [appDelegate.trainerViewController changeTitle:sender score:1]; } - (void)hideTitle:(id)sender { NewsBlurAppDelegate *appDelegate = [NewsBlurAppDelegate sharedAppDelegate]; [appDelegate.trainerViewController changeTitle:sender score:-1]; } @end