diff --git a/WordPress/Classes/ReaderCommentTableViewCell.h b/WordPress/Classes/ReaderCommentTableViewCell.h index ea9589f83cd6..b64586cc9dec 100644 --- a/WordPress/Classes/ReaderCommentTableViewCell.h +++ b/WordPress/Classes/ReaderCommentTableViewCell.h @@ -9,8 +9,9 @@ #import #import "ReaderTableViewCell.h" #import "ReaderComment.h" +#import "WPTableViewCell.h" -@interface ReaderCommentTableViewCell : UITableViewCell +@interface ReaderCommentTableViewCell : WPTableViewCell @property (nonatomic, strong) UIImageView *cellImageView; + (NSAttributedString *)convertHTMLToAttributedString:(NSString *)html withOptions:(NSDictionary *)options; diff --git a/WordPress/Classes/ReaderCommentTableViewCell.m b/WordPress/Classes/ReaderCommentTableViewCell.m index e993c92b4515..8bd923472adb 100644 --- a/WordPress/Classes/ReaderCommentTableViewCell.m +++ b/WordPress/Classes/ReaderCommentTableViewCell.m @@ -14,7 +14,7 @@ #import "NSDate+StringFormatting.h" #define RCTVCVerticalPadding 5.0f -#define RCTVCIndentationWidth 10.0f +#define RCTVCIndentationWidth 15.0f #define RCTVCAuthorLabelHeight 20.0f @interface ReaderCommentTableViewCell() @@ -96,17 +96,15 @@ + (NSAttributedString *)convertHTMLToAttributedString:(NSString *)html withOptio - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { - - UIColor *color = DTColorCreateWithHexString(@"EFEFEF"); CGFloat width = self.frame.size.width; - self.backgroundColor = color; + self.backgroundColor = [WPStyleGuide itsEverywhereGrey]; [self.cellImageView setFrame:CGRectMake(10.0f, 10.0f, 20.0f, 20.0f)]; self.cellImageView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin; self.textContentView = [[DTAttributedTextContentView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, 44.0f)]; _textContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _textContentView.backgroundColor = color; + _textContentView.backgroundColor = [UIColor clearColor]; _textContentView.edgeInsets = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); _textContentView.delegate = self; _textContentView.shouldDrawImages = NO; @@ -118,40 +116,26 @@ - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reus _dateLabel.textColor = [WPStyleGuide littleEddieGrey]; _dateLabel.textAlignment = NSTextAlignmentRight; _dateLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - _dateLabel.backgroundColor = color; + _dateLabel.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:_dateLabel]; - self.authorLabel = [[UILabel alloc] initWithFrame:CGRectMake(40.0f, 10.0f, (_dateLabel.frame.origin.x - 50.0f), 20.0f)]; + self.authorLabel = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 10.0f, (_dateLabel.frame.origin.x - 50.0f), 20.0f)]; [_authorLabel setFont:[WPStyleGuide subtitleFont]]; _authorLabel.textColor = [WPStyleGuide littleEddieGrey]; _authorLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _authorLabel.backgroundColor = color; + _authorLabel.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:_authorLabel]; - UIImageView *separatorImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cell-separator"]]; - separatorImageView.frame = CGRectMake(0.0f, 0.0f, width, 2.0f); + UIImageView *separatorImageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.indentationWidth, 0.0f, width - self.indentationWidth, 1.0f)]; + separatorImageView.backgroundColor = [UIColor colorWithHexString:@"e5e5e5"]; separatorImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth; [self.contentView addSubview:separatorImageView]; self.textContentView.frame = CGRectMake(0.0f, _authorLabel.frame.size.height + 10.0f, width, 44.0f); - UIView *view = [[UIView alloc] initWithFrame:self.bounds]; + UIView *view = [[UIView alloc] initWithFrame:self.frame]; view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - view.backgroundColor = DTColorCreateWithHexString(@"A9E2F3"); - - CGRect rect = CGRectMake(0, 0, 1, 1); - UIGraphicsBeginImageContext(rect.size); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, [color CGColor]); - CGContextFillRect(context, rect); - UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - UIView *colorView = [[UIImageView alloc] initWithImage:img]; - colorView.frame = CGRectMake(0.0f, 0.0f, width, 2.0f); - colorView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - - [view addSubview:colorView]; + view.backgroundColor = DTColorCreateWithHexString(@"e5e5e5"); [self setSelectedBackgroundView:view]; } @@ -165,8 +149,9 @@ - (void)layoutSubviews { // We have to manually update the indentation of the content view? wtf. CGRect frame = self.contentView.frame; - frame.origin.x += (self.indentationWidth * self.indentationLevel); - frame.size.width -= frame.origin.x; + CGFloat indent = self.indentationWidth * self.indentationLevel; + frame.origin.x += indent; + frame.size.width -= indent; self.contentView.frame = frame; [self.cellImageView setFrame:CGRectMake(10.0f, 10.0f, 20.0f, 20.0f)]; diff --git a/WordPress/Classes/ReaderPostDetailView.h b/WordPress/Classes/ReaderPostDetailView.h deleted file mode 100644 index 57caa02a24d6..000000000000 --- a/WordPress/Classes/ReaderPostDetailView.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ReaderPostDetailView.h -// WordPress -// -// Created by Eric J on 5/24/13. -// Copyright (c) 2013 WordPress. All rights reserved. -// - -#import -#import "ReaderPost.h" - -@protocol ReaderPostDetailViewDelegate - -- (void)readerPostDetailViewLayoutChanged; - -@end - -@interface ReaderPostDetailView : UIView - -- (id)initWithFrame:(CGRect)frame post:(ReaderPost *)post delegate:(id)delegate; -- (void)updateLayout; - -@end diff --git a/WordPress/Classes/ReaderPostDetailView.m b/WordPress/Classes/ReaderPostDetailView.m deleted file mode 100644 index 9e839fd4b560..000000000000 --- a/WordPress/Classes/ReaderPostDetailView.m +++ /dev/null @@ -1,669 +0,0 @@ -// -// ReaderPostDetailView.m -// WordPress -// -// Created by Eric J on 5/24/13. -// Copyright (c) 2013 WordPress. All rights reserved. -// - -#import "ReaderPostDetailView.h" -#import -#import -#import -#import "DTTiledLayerWithoutFade.h" -#import "ReaderMediaView.h" -#import "ReaderImageView.h" -#import "ReaderVideoView.h" -#import "WPImageViewController.h" -#import "WordPressAppDelegate.h" -#import "WPWebViewController.h" -#import "WPWebVideoViewController.h" -#import "UIImageView+Gravatar.h" -#import "UILabel+SuggestSize.h" -#import "ReaderPostsViewController.h" -#import "ReaderMediaQueue.h" - -#define ContentTextViewYOffset -32 - -@interface ReaderPostDetailView() { - BOOL _relayoutTextFlag; -} - -@property (nonatomic, strong) ReaderPost *post; -@property (nonatomic, strong) UIView *authorView; -@property (nonatomic, strong) UIImageView *avatarImageView; -@property (nonatomic, strong) UILabel *authorLabel; -@property (nonatomic, strong) UILabel *dateLabel; -@property (nonatomic, strong) UILabel *blogLabel; -@property (nonatomic, strong) UILabel *titleLabel; -@property (nonatomic, strong) UIButton *followButton; -@property (nonatomic, strong) DTAttributedTextContentView *textContentView; -@property (nonatomic, strong) NSMutableArray *mediaArray; -@property (nonatomic, strong) ReaderMediaQueue *mediaQueue; -@property (nonatomic, weak) iddelegate; - -- (void)_updateLayout; -- (void)updateAttributedString:(NSAttributedString *)attrString; -- (BOOL)updateMediaLayout:(ReaderMediaView *)mediaView; -- (void)handleAuthorViewTapped:(id)sender; -- (void)handleImageLinkTapped:(id)sender; -- (void)handleLinkTapped:(id)sender; -- (void)handleVideoTapped:(id)sender; -- (void)handleMediaViewLoaded:(ReaderMediaView *)mediaView; -- (void)handleFollowButtonTapped:(id)sender; -- (void)handleFollowButtonInteraction:(id)sender; -- (BOOL)isEmoji:(NSURL *)url; - -@end - -@implementation ReaderPostDetailView - -- (void)dealloc -{ - _textContentView.delegate = nil; - _mediaQueue.delegate = nil; - [_mediaQueue discardQueuedItems]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (id)initWithFrame:(CGRect)frame post:(ReaderPost *)post delegate:(id)delegate { - self = [super initWithFrame:frame]; - if (self) { - - self.post = post; - self.delegate = delegate; - - self.mediaArray = [NSMutableArray array]; - self.mediaQueue = [[ReaderMediaQueue alloc] initWithDelegate:self]; - - CGFloat width = frame.size.width; - CGFloat padding = 20.0f; - CGFloat labelWidth = width - 100.0f; - CGFloat labelHeight = 20.0f; - CGFloat avatarSize = 60.0f; - - self.authorView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, width, 80.0f)]; - _authorView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [self addSubview:_authorView]; - - - UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; - button.frame = _authorView.frame; - [button addTarget:self action:@selector(handleAuthorViewTapped:) forControlEvents:UIControlEventTouchUpInside]; - [_authorView addSubview:button]; - - self.avatarImageView = [[UIImageView alloc] initWithFrame:CGRectMake(padding, padding, avatarSize, avatarSize)]; - _avatarImageView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin; - - if ([post avatar] != nil) { - [self.avatarImageView setImageWithURL:[NSURL URLWithString:[post avatar]] placeholderImage:[UIImage imageNamed:@"gravatar.jpg"]]; - } else { - NSString *img = ([post isWPCom]) ? @"wpcom_blavatar.png" : @"wporg_blavatar.png"; - [self.avatarImageView setImageWithURL:[self.avatarImageView blavatarURLForHost:[[NSURL URLWithString:post.blogURL] host]] placeholderImage:[UIImage imageNamed:img]]; - } - - [_authorView addSubview:_avatarImageView]; - - self.authorLabel = [[UILabel alloc] initWithFrame:CGRectMake(avatarSize + padding + 10.0f, padding, labelWidth, labelHeight)]; - _authorLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _authorLabel.backgroundColor = [UIColor clearColor]; - _authorLabel.font = [UIFont fontWithName:@"OpenSans" size:13.0f]; - _authorLabel.text = (self.post.author != nil) ? self.post.author : self.post.authorDisplayName; - _authorLabel.textColor = DTColorCreateWithHexString(@"404040"); - [_authorView addSubview:_authorLabel]; - - self.dateLabel = [[UILabel alloc] initWithFrame:CGRectMake(avatarSize + padding + 10.0f, padding + labelHeight, labelWidth, labelHeight)]; - _dateLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _dateLabel.backgroundColor = [UIColor clearColor]; - _dateLabel.font = [UIFont fontWithName:@"OpenSans" size:13.0f]; - _dateLabel.text = [NSString stringWithFormat:@"%@ on", [self.post prettyDateString]]; - _dateLabel.textColor = DTColorCreateWithHexString(@"404040"); - [_authorView addSubview:_dateLabel]; - - self.blogLabel = [[UILabel alloc] initWithFrame:CGRectMake(avatarSize + padding + 10.0f, padding + labelHeight * 2, labelWidth, labelHeight)]; - _blogLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _blogLabel.backgroundColor = [UIColor clearColor]; - _blogLabel.font = [UIFont fontWithName:@"OpenSans" size:13.0f]; - _blogLabel.text = self.post.blogName; - _blogLabel.textColor = DTColorCreateWithHexString(@"278dbc"); - [_authorView addSubview:_blogLabel]; - - CGRect followFrame = _blogLabel.frame; - followFrame.origin.y += 2.0f; - followFrame.size.height += 4.0f; - self.followButton = [UIButton buttonWithType:UIButtonTypeCustom]; - _followButton.frame = followFrame; // Arbitrary width and x. The height and y are correct. - [_followButton setSelected:[post.isFollowing boolValue]]; - _followButton.layer.cornerRadius = 3.0f; - _followButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - _followButton.backgroundColor = [UIColor colorWithRed:234.0f/255.0f green:234.0f/255.0f blue:234.0f/255.0f alpha:1.0f]; - _followButton.titleLabel.font = [UIFont fontWithName:@"OpenSans-Bold" size:10.0f]; - NSString *followString = NSLocalizedString(@"Follow", @"Prompt to follow a blog."); - NSString *followedString = NSLocalizedString(@"Following", @"User is following the blog."); - // -[NSString uppercaseStringWithLocale:] available since iOS6 - if ([followString respondsToSelector:@selector(uppercaseStringWithLocale:)]) { - followString = [followString uppercaseStringWithLocale:[NSLocale currentLocale]]; - followedString = [followedString uppercaseStringWithLocale:[NSLocale currentLocale]]; - } else { - followString = [followString uppercaseString]; - followedString = [followedString uppercaseString]; - } - [_followButton setTitle:followString forState:UIControlStateNormal]; - [_followButton setTitle:followedString forState:UIControlStateSelected]; - [_followButton setImage:[UIImage imageNamed:@"reader-postaction-follow"] forState:UIControlStateNormal]; - [_followButton setImage:[UIImage imageNamed:@"reader-postaction-following"] forState:UIControlStateSelected]; - [_followButton setTitleColor:[UIColor colorWithRed:116.0f/255.0f green:116.0f/255.0f blue:116.0f/255.0f alpha:1.0f] forState:UIControlStateNormal]; - [_followButton addTarget:self action:@selector(handleFollowButtonInteraction:) forControlEvents:UIControlEventAllTouchEvents]; - [_followButton addTarget:self action:@selector(handleFollowButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; - - [_authorView addSubview:_followButton]; - - CGFloat contentY = _authorView.frame.size.height; - - if ([self.post.postTitle length]) { - CGRect titleFrame = CGRectMake(padding, contentY + padding, width - (padding * 2), 44.0f); - self.titleLabel = [[UILabel alloc] initWithFrame:titleFrame]; - _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _titleLabel.backgroundColor = [UIColor whiteColor]; - _titleLabel.font = [WPStyleGuide largePostTitleFont]; - _titleLabel.textColor = [WPStyleGuide littleEddieGrey]; - _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; - _titleLabel.numberOfLines = 0; - if (IS_IOS7) { - _titleLabel.attributedText = [[NSAttributedString alloc] initWithString:self.post.postTitle attributes:[WPStyleGuide largePostTitleAttributes]]; - } else { - _titleLabel.text = self.post.postTitle; - } - [self addSubview:_titleLabel]; - titleFrame.size.height = [_titleLabel suggestedSizeForWidth:_titleLabel.frame.size.width].height; - _titleLabel.frame = titleFrame; - contentY = titleFrame.origin.y + titleFrame.size.height; - } - - [DTAttributedTextContentView setLayerClass:[DTTiledLayerWithoutFade class]]; - self.textContentView = [[DTAttributedTextContentView alloc] initWithFrame:CGRectMake(0.0f, contentY + ContentTextViewYOffset, width, 100.0f)]; // Starting height is arbitrary - _textContentView.delegate = self; - _textContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _textContentView.backgroundColor = [UIColor whiteColor]; - _textContentView.edgeInsets = UIEdgeInsetsMake(0.0f, padding, 0.0f, padding); - _textContentView.shouldDrawImages = NO; - _textContentView.shouldDrawLinks = NO; - [self addSubview:_textContentView]; - - // There seems to be a bug with DTCoreText causing images on the first line to have a negative y origin. - // As a work around, let the first line always be empty. We shift the text view's origin to compensate. - NSString *str = [NSString stringWithFormat:@"

%@", self.post.content]; - [self updateAttributedString: [[NSAttributedString alloc] initWithHTMLData:[str dataUsingEncoding:NSUTF8StringEncoding] - options:[WPStyleGuide defaultDTCoreTextOptions] - documentAttributes:nil]]; - [self sendSubviewToBack:_textContentView]; - } - return self; -} - - -- (void)updateAttributedString:(NSAttributedString *)attrString { - _textContentView.attributedString = attrString; -} - - -- (void)layoutSubviews { - [super layoutSubviews]; - - NSString *str = _followButton.currentTitle; - CGSize sz = [str sizeWithAttributes:@{NSFontAttributeName:_followButton.titleLabel.font}]; - [_followButton sizeToFit]; - sz = _followButton.frame.size; - sz.width += 5.0f; // just a little extra width so the text has better padding on the right. - - CGFloat desiredWidth = [_blogLabel.text sizeWithAttributes:@{NSFontAttributeName:_blogLabel.font}].width; - CGFloat availableWidth = (_authorView.frame.size.width - _blogLabel.frame.origin.x) - 20.0f; - availableWidth -= (sz.width + 10.0f); - - CGRect frame = _blogLabel.frame; - frame.size.width = MIN(availableWidth, desiredWidth); - _blogLabel.frame = frame; - - frame = _followButton.frame; - frame.origin.x = _blogLabel.frame.origin.x + _blogLabel.frame.size.width + 5.0f; - frame.size.width = sz.width; - _followButton.frame = frame; - - // The first time layoutSubviews is called our text control will build all its custom attachments. We're - // rejecting the attachment frame desired by the text control and substituting our own. Because expected - // and actual frames differ, DTCoreText can end up redrawing text on top of the DTLinkButtons. A work - // around is to call updateLayout once after all custom attachments are created. - if (!_relayoutTextFlag) { - _relayoutTextFlag = YES; - [self performSelector:@selector(updateLayout) withObject:self afterDelay:.1]; - } -} - - -- (void)updateLayout { - // Figure out image sizes after orientation change. - for (ReaderMediaView *mediaView in _mediaArray) { - [self updateMediaLayout:mediaView]; - } - - if (_titleLabel) { - CGRect titleFrame = _titleLabel.frame; - titleFrame.size.height = [_titleLabel suggestedSizeForWidth:titleFrame.size.width].height; - titleFrame = CGRectIntegral(titleFrame); - _titleLabel.frame = titleFrame; - - CGRect contentFrame = _textContentView.frame; - contentFrame.origin.y = titleFrame.origin.y + titleFrame.size.height + ContentTextViewYOffset; - _textContentView.frame = contentFrame; - } - - // Then update the layout - // need to reset the layouter because otherwise we get the old framesetter or cached layout frames - _textContentView.layouter = nil; - - // layout might have changed due to image sizes - [_textContentView relayoutText]; - - [self _updateLayout]; -} - - -- (void)_updateLayout { - // Size the textContentView - CGRect frame = _textContentView.frame; - CGFloat height = [_textContentView suggestedFrameSizeToFitEntireStringConstraintedToWidth:frame.size.width].height; - frame.size.height = height; - _textContentView.frame = frame; - - frame = self.frame; - frame.size.height = height + _textContentView.frame.origin.y + 10.0f; // + bottom padding - self.frame = frame; - - [self.delegate readerPostDetailViewLayoutChanged]; -} - - -- (BOOL)updateMediaLayout:(ReaderMediaView *)imageView { - BOOL frameChanged = NO; - NSURL *url = imageView.contentURL; - - CGSize originalSize = imageView.frame.size; - CGSize viewSize = imageView.image.size; - - if ([self isEmoji:url]) { - CGFloat scale = [UIScreen mainScreen].scale; - viewSize.width *= scale; - viewSize.height *= scale; - } else { - CGFloat ratio = viewSize.width / viewSize.height; - CGFloat width = _textContentView.frame.size.width; - CGFloat availableWidth = _textContentView.frame.size.width - (_textContentView.edgeInsets.left + _textContentView.edgeInsets.right); - - viewSize.width = availableWidth; - - if (imageView.isShowingPlaceholder) { - viewSize.height = roundf(width / imageView.placeholderRatio); - } else { - viewSize.height = roundf(width / ratio); - } - - viewSize.height += imageView.edgeInsets.top; // account for the top edge inset. - } - - // Widths should always match - if (viewSize.height != originalSize.height) { - frameChanged = YES; - } - - NSPredicate *pred = [NSPredicate predicateWithFormat:@"contentURL == %@", url]; - - // update all attachments that matchin this URL (possibly multiple images with same size) - for (DTTextAttachment *attachment in [self.textContentView.layoutFrame textAttachmentsWithPredicate:pred]) { - attachment.originalSize = originalSize; - attachment.displaySize = viewSize; - } - - return frameChanged; -} - - -- (BOOL)isEmoji:(NSURL *)url { - return ([[url absoluteString] rangeOfString:@"wp.com/wp-includes/images/smilies"].location != NSNotFound); -} - -- (UINavigationController *)detailNavigationController { - return [[[WordPressAppDelegate sharedWordPressApplicationDelegate] readerPostsViewController] navigationController]; -} - - -- (void)handleFollowButtonInteraction:(id)sender { - [self setNeedsLayout]; -} - - -- (void)handleFollowButtonTapped:(id)sender { - self.followButton.selected = ![self.post.isFollowing boolValue]; // to fake the call. - [self setNeedsLayout]; - [self.post toggleFollowingWithSuccess:^{ - self.followButton.selected = [self.post.isFollowing boolValue]; // for good measure! - [self setNeedsLayout]; - } failure:^(NSError *error) { - DDLogError(@"Error Following Blog : %@", [error localizedDescription]); - [_followButton setSelected:self.post.isFollowing]; - [self setNeedsLayout]; - - NSString *title; - NSString *description; - if (self.post.isFollowing) { - title = NSLocalizedString(@"Could Not Unfollow Blog", @"Title of prompt. Says a blog could not be unfollowed."); - description = NSLocalizedString(@"There was a problem unfollowing this blog.", @"Prompts the user that there was a problem unfollowing a blog."); - } else { - title = NSLocalizedString(@"Could Not Follow Blog", @"Title of prompt. Says a blog could not be followed."); - description = NSLocalizedString(@"There was a problem following this blog.", @"Prompts the user there was a problem following a blog."); - } - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:description - delegate:nil - cancelButtonTitle:NSLocalizedString(@"OK", @"") - otherButtonTitles:nil]; - [alertView show]; - - }]; - [_followButton setSelected:self.post.isFollowing]; - [self setNeedsLayout]; -} - - -- (void)handleAuthorViewTapped:(id)sender { - WPWebViewController *controller = [[WPWebViewController alloc] init]; - [controller setUrl:[NSURL URLWithString:self.post.permaLink]]; - [[self detailNavigationController] pushViewController:controller animated:YES]; -} - - -- (void)handleImageLinkTapped:(id)sender { - ReaderImageView *imageView = (ReaderImageView *)sender; - - if(imageView.linkURL) { - NSString *url = [imageView.linkURL absoluteString]; - - BOOL matched = NO; - NSArray *types = @[@".png", @".jpg", @".gif", @".jpeg"]; - for (NSString *type in types) { - if (NSNotFound != [url rangeOfString:type].location) { - matched = YES; - break; - } - } - - if (matched) { - [WPImageViewController presentAsModalWithImage:imageView.image andURL:((ReaderImageView *)sender).linkURL]; -// [WPImageViewController presentAsModalWithURL:((ReaderImageView *)sender).linkURL]; - } else { - WPWebViewController *controller = [[WPWebViewController alloc] init]; - [controller setUrl:((ReaderImageView *)sender).linkURL]; - [[self detailNavigationController] pushViewController:controller animated:YES]; - } - } else { - [WPImageViewController presentAsModalWithImage:imageView.image]; - } -} - - -- (void)handleLinkTapped:(id)sender { - WPWebViewController *controller = [[WPWebViewController alloc] init]; - [controller setUrl:((DTLinkButton *)sender).URL]; - [[self detailNavigationController] pushViewController:controller animated:YES]; -} - - -- (void)handleVideoTapped:(id)sender { - ReaderVideoView *videoView = (ReaderVideoView *)sender; - if(videoView.contentType == ReaderVideoContentTypeVideo) { - - MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:videoView.contentURL]; - // Remove the movie player view controller from the "playback did finish" notification observers - [[NSNotificationCenter defaultCenter] removeObserver:controller - name:MPMoviePlayerPlaybackDidFinishNotification - object:controller.moviePlayer]; - - // Register this class as an observer instead - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMoviePlaybackFinishedNotification:) - name:MPMoviePlayerPlaybackDidFinishNotification - object:controller.moviePlayer]; - - controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - controller.modalPresentationStyle = UIModalPresentationFormSheet; - [[[WordPressAppDelegate sharedWordPressApplicationDelegate].window rootViewController] presentViewController:controller animated:YES completion:nil]; - - } else { - // Should either be an iframe, or an object embed. In either case a src attribute should have been parsed for the contentURL. - // Assume this is content we can show and try to load it. - WPWebVideoViewController *controller = [WPWebVideoViewController presentAsModalWithURL:videoView.contentURL]; - controller.title = (videoView.title != nil) ? videoView.title : @"Video"; - } -} - - -- (void)handleMediaViewLoaded:(ReaderMediaView *)mediaView { - - BOOL frameChanged = [self updateMediaLayout:mediaView]; - - if (frameChanged) { - // need to reset the layouter because otherwise we get the old framesetter or cached layout frames - self.textContentView.layouter = nil; - - // layout might have changed due to image sizes - [self.textContentView relayoutText]; - - [self _updateLayout]; - } -} - - -- (void)handleMoviePlaybackFinishedNotification:(NSNotification *)notification { - // Obtain the reason why the movie playback finished - NSNumber *finishReason = [[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey]; - - // Dismiss the view controller ONLY when the reason is not "playback ended" - if ([finishReason intValue] != MPMovieFinishReasonPlaybackEnded) { - MPMoviePlayerController *moviePlayer = [notification object]; - - // Remove this class from the observers - [[NSNotificationCenter defaultCenter] removeObserver:self - name:MPMoviePlayerPlaybackDidFinishNotification - object:moviePlayer]; - - // Dismiss the view controller - [[[WordPressAppDelegate sharedWordPressApplicationDelegate].window rootViewController] dismissViewControllerAnimated:YES completion:nil]; - } -} - - -#pragma mark - DTCoreAttributedTextContentView Delegate Methods - -- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttributedString:(NSAttributedString *)string frame:(CGRect)frame { - NSDictionary *attributes = [string attributesAtIndex:0 effectiveRange:nil]; - - NSURL *URL = [attributes objectForKey:DTLinkAttribute]; - NSString *identifier = [attributes objectForKey:DTGUIDAttribute]; - - DTLinkButton *button = [[DTLinkButton alloc] initWithFrame:frame]; - button.URL = URL; - button.minimumHitSize = CGSizeMake(25, 25); // adjusts it's bounds so that button is always large enough - button.GUID = identifier; - - // get image with normal link text - UIImage *normalImage = [attributedTextContentView contentImageWithBounds:frame options:DTCoreTextLayoutFrameDrawingDefault]; - [button setImage:normalImage forState:UIControlStateNormal]; - - // get image for highlighted link text - UIImage *highlightImage = [attributedTextContentView contentImageWithBounds:frame options:DTCoreTextLayoutFrameDrawingDrawLinksHighlighted]; - [button setImage:highlightImage forState:UIControlStateHighlighted]; - - // use normal push action for opening URL - [button addTarget:self action:@selector(handleLinkTapped:) forControlEvents:UIControlEventTouchUpInside]; - - return button; -} - - -- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame { - - CGFloat width = _textContentView.frame.size.width; - CGFloat availableWidth = _textContentView.frame.size.width - (_textContentView.edgeInsets.left + _textContentView.edgeInsets.right); - - // The ReaderImageView view will conform to the width constraints of the _textContentView. We want the image itself to run out to the edges, - // so position it offset by the inverse of _textContentView's edgeInsets. Also add top padding so we don't bump into a line of text. - // Remeber to add an extra 10px to the frame to preserve aspect ratio. - UIEdgeInsets edgeInsets = _textContentView.edgeInsets; - edgeInsets.left = 0.0f - edgeInsets.left; - edgeInsets.top = 15.0f; - edgeInsets.right = 0.0f - edgeInsets.right; - edgeInsets.bottom = 0.0f; - - if ([attachment isKindOfClass:[DTImageTextAttachment class]]) { - if ([self isEmoji:attachment.contentURL]) { - // minimal frame to suppress drawing context errors with 0 height or width. - frame.size.width = MAX(frame.size.width, 1.0f); - frame.size.height = MAX(frame.size.height, 1.0f); - ReaderImageView *imageView = [[ReaderImageView alloc] initWithFrame:frame]; - [_mediaArray addObject:imageView]; - [self.mediaQueue enqueueMedia:imageView - withURL:attachment.contentURL - placeholderImage:nil - size:CGSizeMake(15.0f, 15.0f) - isPrivate:self.post.isPrivate - success:nil - failure:nil]; - return imageView; - } - - DTImageTextAttachment *imageAttachment = (DTImageTextAttachment *)attachment; - UIImage *image; - - if( [imageAttachment.image isKindOfClass:[UIImage class]] ) { - image = imageAttachment.image; - - CGFloat ratio = image.size.width / image.size.height; - frame.size.width = availableWidth; - frame.size.height = roundf(width / ratio); - } else { - image = [UIImage imageNamed:@"wp_img_placeholder.png"]; - - if (frame.size.width > 1.0f && frame.size.height > 1.0f) { - CGFloat ratio = frame.size.width / frame.size.height; - frame.size.width = availableWidth; - frame.size.height = roundf(width / ratio); - } else { - frame.size.width = availableWidth; - frame.size.height = roundf(width * 0.66f); - } - } - - // offset the top edge inset keeping the image from bumping the text above it. - frame.size.height += edgeInsets.top; - - ReaderImageView *imageView = [[ReaderImageView alloc] initWithFrame:frame]; - imageView.contentMode = UIViewContentModeScaleAspectFit; - imageView.edgeInsets = edgeInsets; - - [_mediaArray addObject:imageView]; - imageView.linkURL = attachment.hyperLinkURL; - [imageView addTarget:self action:@selector(handleImageLinkTapped:) forControlEvents:UIControlEventTouchUpInside]; - - if ([imageAttachment.image isKindOfClass:[UIImage class]]) { - [imageView setImage:image]; - } else { - imageView.contentMode = UIViewContentModeCenter; - imageView.backgroundColor = [UIColor colorWithRed:192.0f/255.0f green:192.0f/255.0f blue:192.0f/255.0f alpha:1.0]; - - [self.mediaQueue enqueueMedia:imageView - withURL:attachment.contentURL - placeholderImage:image - size:CGSizeMake(width, 0) - isPrivate:self.post.isPrivate - success:^(ReaderMediaView *readerMediaView) { - ReaderImageView *imageView = (ReaderImageView *)readerMediaView; - imageView.contentMode = UIViewContentModeScaleAspectFit; - imageView.backgroundColor = [UIColor clearColor]; - } - failure:nil]; - } - - return imageView; - - } else { - - ReaderVideoContentType videoType; - - if ([attachment isKindOfClass:[DTVideoTextAttachment class]]) { - videoType = ReaderVideoContentTypeVideo; - } else if ([attachment isKindOfClass:[DTIframeTextAttachment class]]) { - videoType = ReaderVideoContentTypeIFrame; - } else if ([attachment isKindOfClass:[DTObjectTextAttachment class]]) { - videoType = ReaderVideoContentTypeEmbed; - } else { - return nil; // Can't handle whatever this is :P - } - - // make sure we have a reasonable size. - if (frame.size.width > width) { - if (frame.size.height == 0) { - frame.size.height = roundf(frame.size.width * 0.66f); - } - CGFloat ratio = frame.size.width / frame.size.height; - frame.size.width = availableWidth; - frame.size.height = roundf(width / ratio); - } - - // offset the top edge inset keeping the image from bumping the text above it. - frame.size.height += edgeInsets.top; - - ReaderVideoView *videoView = [[ReaderVideoView alloc] initWithFrame:frame]; - videoView.contentMode = UIViewContentModeCenter; - videoView.backgroundColor = [UIColor colorWithRed:192.0f/255.0f green:192.0f/255.0f blue:192.0f/255.0f alpha:1.0]; - videoView.edgeInsets = edgeInsets; - - [_mediaArray addObject:videoView]; - [videoView setContentURL:attachment.contentURL ofType:videoType success:^(id readerVideoView) { - [(ReaderVideoView *)readerVideoView setContentMode:UIViewContentModeScaleAspectFit]; - [self handleMediaViewLoaded:readerVideoView]; - } failure:^(id readerVideoView, NSError *error) { - [self handleMediaViewLoaded:readerVideoView]; - - }]; - - [videoView addTarget:self action:@selector(handleVideoTapped:) forControlEvents:UIControlEventTouchUpInside]; - - return videoView; - } - -} - -#pragma mark ReaderMediaQueueDelegate methods - -- (void)readerMediaQueue:(ReaderMediaQueue *)mediaQueue didLoadBatch:(NSArray *)batch { - BOOL frameChanged = NO; - - for (NSInteger i = 0; i < [batch count]; i++) { - ReaderMediaView *mediaView = [batch objectAtIndex:i]; - if ([self updateMediaLayout:mediaView]) { - frameChanged = YES; - } - } - - if (frameChanged) { - // need to reset the layouter because otherwise we get the old framesetter or cached layout frames - self.textContentView.layouter = nil; - - // layout might have changed due to image sizes - [self.textContentView relayoutText]; - - [self _updateLayout]; - } -} - -@end diff --git a/WordPress/Classes/ReaderPostDetailViewController.h b/WordPress/Classes/ReaderPostDetailViewController.h index a8e788825f5c..72ca4fb82254 100644 --- a/WordPress/Classes/ReaderPostDetailViewController.h +++ b/WordPress/Classes/ReaderPostDetailViewController.h @@ -7,10 +7,15 @@ * Some rights reserved. See license.txt */ -@class ReaderPost; +#import +#import "ReaderPost.h" +#import "ReaderPostView.h" -@interface ReaderPostDetailViewController : UIViewController +@interface ReaderPostDetailViewController : UITableViewController +@property (nonatomic, strong) ReaderPost *post; +@property (nonatomic, assign) BOOL showInlineActionBar; -- (id)initWithPost:(ReaderPost *)apost; +- (id)initWithPost:(ReaderPost *)post featuredImage:(UIImage *)image; +- (void)updateFeaturedImage:(UIImage *)image; @end diff --git a/WordPress/Classes/ReaderPostDetailViewController.m b/WordPress/Classes/ReaderPostDetailViewController.m index 24a67d8479cb..5d2c9b00702e 100644 --- a/WordPress/Classes/ReaderPostDetailViewController.m +++ b/WordPress/Classes/ReaderPostDetailViewController.m @@ -8,29 +8,44 @@ */ #import "ReaderPostDetailViewController.h" +#import "ReaderPostsViewController.h" #import +#import +#import #import #import "WPActivityDefaults.h" #import "WordPressAppDelegate.h" #import "ReaderComment.h" #import "ReaderCommentTableViewCell.h" -#import "ReaderPostDetailView.h" #import "ReaderCommentFormView.h" #import "ReaderReblogFormView.h" #import "IOS7CorrectedTextView.h" +#import "ReaderImageView.h" +#import "ReaderVideoView.h" +#import "WPImageViewController.h" +#import "WPWebVideoViewController.h" +#import "WPWebViewController.h" #import "ContextManager.h" +#import "WPTableViewController.h" NSInteger const ReaderCommentsToSync = 100; NSTimeInterval const ReaderPostDetailViewControllerRefreshTimeout = 300; // 5 minutes -@interface ReaderPostDetailViewController () +typedef enum { + ReaderDetailContentSection = 0, + ReaderDetailCommentsSection, + ReaderDetailSectionCount +} ReaderDetailSection; -@property (nonatomic, strong) ReaderPost *post; -@property (nonatomic, strong) ReaderPostDetailView *headerView; + +@interface ReaderPostDetailViewController () { +} + +@property (nonatomic, strong) ReaderPostView *postView; @property (nonatomic, strong) ReaderCommentFormView *readerCommentFormView; @property (nonatomic, strong) ReaderReblogFormView *readerReblogFormView; +@property (nonatomic, strong) UIImage *featuredImage; @property (nonatomic) BOOL infiniteScrollEnabled; -@property (nonatomic, strong) UITableView *tableView; @property (nonatomic, strong) UIActivityIndicatorView *activityFooter; @property (nonatomic, strong) UIBarButtonItem *commentButton; @property (nonatomic, strong) UIBarButtonItem *likeButton; @@ -51,28 +66,38 @@ @interface ReaderPostDetailViewController () _savedScrollOffset.y) { + if (contentSize.height > _savedScrollOffset.y) { [self.tableView scrollRectToVisible:CGRectMake(_savedScrollOffset.x, _savedScrollOffset.y, 0.0f, 0.0f) animated:NO]; } else { [self.tableView scrollRectToVisible:CGRectMake(0.0f, contentSize.height, 0.0f, 0.0f) animated:NO]; @@ -105,20 +136,10 @@ - (void)viewWillAppear:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; UIToolbar *toolbar = self.navigationController.toolbar; - if (IS_IOS7) { - toolbar.barTintColor = [WPStyleGuide littleEddieGrey]; - toolbar.tintColor = [UIColor whiteColor]; - toolbar.translucent = NO; - } else { - [toolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionBottom barMetrics:UIBarMetricsDefault]; - [toolbar setTintColor:DTColorCreateWithHexString(@"F1F1F1")]; - } + toolbar.barTintColor = [WPStyleGuide littleEddieGrey]; + toolbar.tintColor = [UIColor whiteColor]; + toolbar.translucent = NO; - [self.navigationController setToolbarHidden:NO animated:animated]; - - [self.post addObserver:self forKeyPath:@"isReblogged" options:NSKeyValueObservingOptionNew context:@"reblogging"]; - - [_headerView updateLayout]; [self showStoredComment]; } @@ -127,14 +148,15 @@ - (void)viewDidAppear:(BOOL)animated { // Do not start auto-sync if connection is down WordPressAppDelegate *appDelegate = [WordPressAppDelegate sharedWordPressApplicationDelegate]; - if (appDelegate.connectionAvailable == NO) { + if (appDelegate.connectionAvailable == NO) return; - } NSDate *lastSynced = [self lastSyncDate]; if (lastSynced == nil || ABS([lastSynced timeIntervalSinceNow]) > ReaderPostDetailViewControllerRefreshTimeout) { [self syncWithUserInteraction:NO]; } + + [self.tableView reloadData]; } - (void)viewWillDisappear:(BOOL)animated { @@ -145,17 +167,12 @@ - (void)viewWillDisappear:(BOOL)animated { } [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self.post removeObserver:self forKeyPath:@"isReblogged" context:@"reblogging"]; - - self.navigationController.navigationBar.translucent = NO; - self.navigationController.toolbar.translucent = NO; - [self.navigationController setToolbarHidden:YES animated:animated]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - [_headerView updateLayout]; + [self.postView updateLayout]; // Make sure a selected comment is visible after rotating. if ([self.tableView indexPathForSelectedRow] != nil && self.isShowingCommentForm) { @@ -166,59 +183,46 @@ - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceO #pragma mark - View getters/builders -- (ReaderPostDetailView *)headerView { - if (_headerView) { - return _headerView; +- (void)updateFeaturedImage: (UIImage *)image { + self.featuredImage = image; + [self.postView setFeaturedImage:self.featuredImage]; +} + +- (void)buildHeader { + // The text view in postView needs an initial frame + CGFloat postHeight = [ReaderPostView heightForPost:self.post withWidth:self.view.frame.size.width]; + CGRect postFrame = CGRectMake(0.0f, 0.0f, self.tableView.frame.size.width, postHeight); + self.postView = [[ReaderPostView alloc] initWithFrame:postFrame showFullContent:YES]; + self.postView.delegate = self; + [self.postView configurePost:self.post]; + self.postView.backgroundColor = [UIColor whiteColor]; + if (self.featuredImage) { + [self.postView setFeaturedImage: self.featuredImage]; } - _headerView = [[ReaderPostDetailView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.frame.size.width, 190.0f) post:self.post delegate:self]; - _headerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _headerView.backgroundColor = [UIColor whiteColor]; - + UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDismissForm:)]; tgr.cancelsTouchesInView = NO; - [_headerView addGestureRecognizer:tgr]; - return _headerView; + [self.postView addGestureRecognizer:tgr]; } - (UIBarButtonItem *)shareButton { - if (_shareButton) { + if (_shareButton) return _shareButton; - } - // Top Navigation bar and Sharing. - if (IS_IOS7) { - UIImage *image = [UIImage imageNamed:@"icon-posts-share"]; - UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)]; - [button setImage:image forState:UIControlStateNormal]; - [button addTarget:self action:@selector(handleShareButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; - _shareButton = [[UIBarButtonItem alloc] initWithCustomView:button]; - } else { - UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; - - [btn setImage:[UIImage imageNamed:@"navbar_actions.png"] forState:UIControlStateNormal]; - [btn setImage:[UIImage imageNamed:@"navbar_actions.png"] forState:UIControlStateHighlighted]; - - UIImage *backgroundImage = [[UIImage imageNamed:@"navbar_button_bg"] stretchableImageWithLeftCapWidth:4 topCapHeight:0]; - [btn setBackgroundImage:backgroundImage forState:UIControlStateNormal]; - - backgroundImage = [[UIImage imageNamed:@"navbar_button_bg_active"] stretchableImageWithLeftCapWidth:4 topCapHeight:0]; - [btn setBackgroundImage:backgroundImage forState:UIControlStateHighlighted]; - btn.frame = CGRectMake(0.0f, 0.0f, 44.0f, 30.0f); - [btn addTarget:self action:@selector(handleShareButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; - - _shareButton = [[UIBarButtonItem alloc] initWithCustomView:btn]; - } - return _shareButton; + // Top Navigation bar and Sharing + UIImage *image = [UIImage imageNamed:@"icon-posts-share"]; + UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)]; + [button setImage:image forState:UIControlStateNormal]; + [button addTarget:self action:@selector(handleShareButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + _shareButton = [[UIBarButtonItem alloc] initWithCustomView:button]; + + return _shareButton; } -- (void)buildBottomToolbar { +- (void)buildActionBar { UIButton *commentBtn = [UIButton buttonWithType:UIButtonTypeCustom]; - if (IS_IOS7) { - [commentBtn setImage:[UIImage imageNamed:@"reader-postaction-comment"] forState:UIControlStateNormal]; - [commentBtn setImage:[UIImage imageNamed:@"reader-postaction-comment-active"] forState:UIControlStateHighlighted]; - } else { - [commentBtn setImage:[UIImage imageNamed:@"reader-postaction-comment-blue"] forState:UIControlStateNormal]; - } + [commentBtn setImage:[UIImage imageNamed:@"reader-postaction-comment"] forState:UIControlStateNormal]; + [commentBtn setImage:[UIImage imageNamed:@"reader-postaction-comment-active"] forState:UIControlStateHighlighted]; commentBtn.frame = CGRectMake(0.0f, 0.0f, 40.0f, 40.0f); [commentBtn addTarget:self action:@selector(handleCommentButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; self.commentButton = [[UIBarButtonItem alloc] initWithCustomView:commentBtn]; @@ -228,11 +232,7 @@ - (void)buildBottomToolbar { [likeBtn setTitleEdgeInsets:UIEdgeInsetsMake(0.0f, -5.0f, 0.0f, 0.0f)]; [likeBtn setTitleColor:[UIColor colorWithRed:84.0f/255.0f green:173.0f/255.0f blue:211.0f/255.0f alpha:1.0f] forState:UIControlStateNormal]; [likeBtn setTitleColor:[UIColor colorWithRed:221.0f/255.0f green:118.0f/255.0f blue:43.0f/255.0f alpha:1.0f] forState:UIControlStateSelected]; - if (IS_IOS7) { - [likeBtn setImage:[UIImage imageNamed:@"reader-postaction-like"] forState:UIControlStateNormal]; - } else { - [likeBtn setImage:[UIImage imageNamed:@"reader-postaction-like-blue"] forState:UIControlStateNormal]; - } + [likeBtn setImage:[UIImage imageNamed:@"reader-postaction-like"] forState:UIControlStateNormal]; [likeBtn setImage:[UIImage imageNamed:@"reader-postaction-like-active"] forState:UIControlStateSelected]; likeBtn.frame = CGRectMake(0.0f, 0.0f, 60.0f, 40.0f); likeBtn.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; @@ -240,23 +240,20 @@ - (void)buildBottomToolbar { self.likeButton = [[UIBarButtonItem alloc] initWithCustomView:likeBtn]; UIButton *reblogBtn = [UIButton buttonWithType:UIButtonTypeCustom]; - if (IS_IOS7) { - [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog"] forState:UIControlStateNormal]; - [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog-active"] forState:UIControlStateHighlighted]; - } else { - [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog-blue"] forState:UIControlStateNormal]; - } + [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog"] forState:UIControlStateNormal]; + [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog-active"] forState:UIControlStateHighlighted]; [reblogBtn setImage:[UIImage imageNamed:@"reader-postaction-reblog-done"] forState:UIControlStateSelected]; reblogBtn.frame = CGRectMake(0.0f, 0.0f, 40.0f, 40.0f); reblogBtn.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; [reblogBtn addTarget:self action:@selector(handleReblogButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; self.reblogButton = [[UIBarButtonItem alloc] initWithCustomView:reblogBtn]; - [self updateToolbar]; + [self updateActionBar]; } - (void)updateToolbar { - if (!self.post) return; + if (!self.post) + return; UIButton *btn = (UIButton *)_likeButton.customView; [btn setSelected:[self.post.isLiked boolValue]]; @@ -280,9 +277,7 @@ - (void)updateToolbar { } [items addObject:placeholder]; - [self setToolbarItems:items animated:YES]; - self.navigationController.toolbarHidden = NO; } @@ -311,15 +306,16 @@ - (void)buildForms { } - (UIActivityIndicatorView *)activityFooter { - if (_activityFooter) { + if (_activityFooter) return _activityFooter; - } + CGRect rect = CGRectMake(145.0f, 10.0f, 30.0f, 30.0f); _activityFooter = [[UIActivityIndicatorView alloc] initWithFrame:rect]; _activityFooter.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; _activityFooter.hidesWhenStopped = YES; _activityFooter.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [_activityFooter stopAnimating]; + return _activityFooter; } @@ -350,16 +346,13 @@ __block void(__unsafe_unretained ^flattenComments)(NSArray *) = ^void (NSArray * for (ReaderComment *comment in comments) { [_comments addObject:comment]; - if([comment.childComments count] > 0) { + if ([comment.childComments count] > 0) { flattenComments([comment.childComments allObjects]); } } }; flattenComments(self.resultsController.fetchedObjects); - if ([_comments count] > 0) { - self.tableView.backgroundColor = DTColorCreateWithHexString(@"EFEFEF"); - } // Cache attributed strings. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{ @@ -373,38 +366,35 @@ __block void(__unsafe_unretained ^flattenComments)(NSArray *) = ^void (NSArray * }); } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"isReblogged"]) { - [self updateToolbar]; - } -} - -- (void)handleCommentButtonTapped:(id)sender { - if (_readerCommentFormView.window != nil) { - [self hideCommentForm]; - return; +- (void)updateActionBar { + if (!self.post) + return; + + UIButton *btn = (UIButton *)_likeButton.customView; + [btn setSelected:[self.post.isLiked boolValue]]; + NSString *str = ([self.post.likeCount integerValue] > 0) ? [self.post.likeCount stringValue] : nil; + [btn setTitle:str forState:UIControlStateNormal]; + _likeButton.customView = btn; + + btn = (UIButton *)_reblogButton.customView; + [btn setSelected:[self.post.isReblogged boolValue]]; + btn.userInteractionEnabled = !btn.selected; + _reblogButton.customView = btn; + + UIBarButtonItem *placeholder = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; + NSMutableArray *items = [NSMutableArray arrayWithObject:placeholder]; + if ([self canComment]) { + [items addObjectsFromArray:@[_commentButton, placeholder]]; } - [self showCommentForm]; -} - - -- (void)handleLikeButtonTapped:(id)sender { - [self.post toggleLikedWithSuccess:nil failure:^(NSError *error) { - DDLogError(@"Error Liking Post : %@", [error localizedDescription]); - [self updateToolbar]; - }]; - [self updateToolbar]; -} - - -- (void)handleReblogButtonTapped:(id)sender { - if (_isShowingReblogForm) { - [self hideReblogForm]; - return; + if ([self.post isWPCom]) { + [items addObjectsFromArray:@[_likeButton, placeholder, _reblogButton]]; } - [self showReblogForm]; + [items addObject:placeholder]; + + // Could put these in the title bar instead. We're preserving this code for now pending design decisions. + //[self setToolbarItems:items animated:YES]; } - (void)handleShareButtonTapped:(id)sender { @@ -456,7 +446,7 @@ - (void)showStoredComment { NSUInteger idx = [_comments indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { ReaderComment *c = (ReaderComment *)obj; - if([c.commentID integerValue] == cid) { + if ([c.commentID integerValue] == cid) { return YES; } return NO; @@ -469,12 +459,9 @@ - (void)showStoredComment { - (void)showCommentForm { [self hideReblogForm]; - if (_readerCommentFormView.superview != nil) { + if (_readerCommentFormView.superview != nil) return; - } - - [self.navigationController setToolbarHidden:YES animated:NO]; - + NSIndexPath *path = [self.tableView indexPathForSelectedRow]; if (path) { _readerCommentFormView.comment = (ReaderComment *)[self.resultsController objectAtIndexPath:path]; @@ -487,15 +474,14 @@ - (void)showCommentForm { CGFloat y = tableFrame.origin.y + tableFrame.size.height; _readerCommentFormView.frame = CGRectMake(0.0f, y, self.view.bounds.size.width, formHeight); - [self.view addSubview:_readerCommentFormView]; + [self.view.superview addSubview:_readerCommentFormView]; self.isShowingCommentForm = YES; [_readerCommentFormView.textView becomeFirstResponder]; } - (void)hideCommentForm { - if(_readerCommentFormView.superview == nil) { + if (_readerCommentFormView.superview == nil) return; - } _readerCommentFormView.comment = nil; [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO]; @@ -507,17 +493,13 @@ - (void)hideCommentForm { [_readerCommentFormView removeFromSuperview]; self.isShowingCommentForm = NO; [self.view endEditing:YES]; - [self.navigationController setToolbarHidden:NO animated:YES]; } - (void)showReblogForm { [self hideCommentForm]; - - [self.navigationController setToolbarHidden:YES animated:NO]; - - if (_readerReblogFormView.superview != nil) { + + if (_readerReblogFormView.superview != nil) return; - } CGFloat reblogHeight = [ReaderReblogFormView desiredHeight]; CGRect tableFrame = self.tableView.frame; @@ -526,15 +508,14 @@ - (void)showReblogForm { CGFloat y = tableFrame.origin.y + tableFrame.size.height; _readerReblogFormView.frame = CGRectMake(0.0f, y, self.view.bounds.size.width, reblogHeight); - [self.view addSubview:_readerReblogFormView]; + [self.view.superview addSubview:_readerReblogFormView]; self.isShowingReblogForm = YES; [_readerReblogFormView.textView becomeFirstResponder]; } - (void)hideReblogForm { - if(_readerReblogFormView.superview == nil) { + if (_readerReblogFormView.superview == nil) return; - } [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO]; @@ -545,52 +526,215 @@ - (void)hideReblogForm { [_readerReblogFormView removeFromSuperview]; self.isShowingReblogForm = NO; [self.view endEditing:YES]; - - [self.navigationController setToolbarHidden:NO animated:YES]; +} + +- (CGSize)tabBarSize { + CGSize tabBarSize = CGSizeZero; + if ([self tabBarController]) { + tabBarSize = [[[self tabBarController] tabBar] bounds].size; + } + + return tabBarSize; } - (void)handleKeyboardDidShow:(NSNotification *)notification { - CGRect frame = self.view.frame; + UIView *view = self.view.superview; + CGRect frame = view.frame; CGRect startFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; CGRect endFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; // Figure out the difference between the bottom of this view, and the top of the keyboard. // This should account for any toolbars. - CGPoint point = [self.view.window convertPoint:startFrame.origin toView:self.view]; - _keyboardOffset = point.y - (frame.origin.y + frame.size.height); + CGPoint point = [view.window convertPoint:startFrame.origin toView:view]; + self.keyboardOffset = point.y - (frame.origin.y + frame.size.height); // if we're upside down, we need to adjust the origin. if (endFrame.origin.x == 0 && endFrame.origin.y == 0) { endFrame.origin.y = endFrame.origin.x += MIN(endFrame.size.height, endFrame.size.width); } - point = [self.view.window convertPoint:endFrame.origin toView:self.view]; - frame.size.height = point.y; + point = [view.window convertPoint:endFrame.origin toView:view]; + CGSize tabBarSize = [self tabBarSize]; + frame.size.height = point.y + tabBarSize.height; [UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.view.frame = frame; + view.frame = frame; } completion:^(BOOL finished) { // BUG: When dismissing a modal view, and the keyboard is showing again, the animation can get clobbered in some cases. // When this happens the view is set to the dimensions of its wrapper view, hiding content that should be visible // above the keyboard. // For now use a fallback animation. - if (!CGRectEqualToRect(self.view.frame, frame)) { + if (!CGRectEqualToRect(view.frame, frame)) { [UIView animateWithDuration:0.3 animations:^{ - self.view.frame = frame; + view.frame = frame; }]; } }]; } - (void)handleKeyboardWillHide:(NSNotification *)notification { - [self.navigationController setToolbarHidden:YES animated:NO]; - - CGRect frame = self.view.frame; + UIView *view = self.view.superview; + CGRect frame = view.frame; CGRect keyFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGPoint point = [self.view.window convertPoint:keyFrame.origin toView:self.view]; - frame.size.height = point.y - (frame.origin.y + _keyboardOffset); - self.view.frame = frame; + CGPoint point = [view.window convertPoint:keyFrame.origin toView:view]; + frame.size.height = point.y - (frame.origin.y + self.keyboardOffset); + view.frame = frame; +} + +- (void)moviePlaybackDidFinish:(NSNotification *)notification { + // Obtain the reason why the movie playback finished + NSNumber *finishReason = [[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey]; + + // Dismiss the view controller ONLY when the reason is not "playback ended" + if ([finishReason intValue] != MPMovieFinishReasonPlaybackEnded) { + MPMoviePlayerController *moviePlayer = [notification object]; + + // Remove this class from the observers + [[NSNotificationCenter defaultCenter] removeObserver:self + name:MPMoviePlayerPlaybackDidFinishNotification + object:moviePlayer]; + + // Dismiss the view controller + [[[WordPressAppDelegate sharedWordPressApplicationDelegate].window rootViewController] dismissViewControllerAnimated:YES completion:nil]; + } +} + + +#pragma mark - ReaderPostView delegate methods + +- (void)postView:(ReaderPostView *)postView didReceiveReblogAction:(id)sender { + if (_isShowingReblogForm) { + [self hideReblogForm]; + return; + } + + [self showReblogForm]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveLikeAction:(id)sender { + ReaderPost *post = postView.post; + [post toggleLikedWithSuccess:^{ + if ([post.isLiked boolValue]) { + [WPMobileStats trackEventForWPCom:StatsEventReaderLikedPost]; + } else { + [WPMobileStats trackEventForWPCom:StatsEventReaderUnlikedPost]; + } + } failure:^(NSError *error) { + DDLogError(@"Error Liking Post : %@", [error localizedDescription]); + [postView updateActionButtons]; + }]; + + [postView updateActionButtons]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveFollowAction:(id)sender { + UIButton *followButton = (UIButton *)sender; + ReaderPost *post = postView.post; + + if (![post isFollowable]) + return; + + followButton.selected = ![post.isFollowing boolValue]; // Set it optimistically + [post toggleFollowingWithSuccess:^{ + } failure:^(NSError *error) { + DDLogError(@"Error Following Blog : %@", [error localizedDescription]); + [followButton setSelected:[post.isFollowing boolValue]]; + }]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveCommentAction:(id)sender { + if (_readerCommentFormView.window != nil) { + [self hideCommentForm]; + return; + } + + [self showCommentForm]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveLinkAction:(id)sender { + WPWebViewController *controller = [[WPWebViewController alloc] init]; + [controller setUrl:((DTLinkButton *)sender).URL]; + [self.navigationController pushViewController:controller animated:YES]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveImageLinkAction:(id)sender { + ReaderImageView *imageView = (ReaderImageView *)sender; + UIViewController *controller; + + if (imageView.linkURL) { + NSString *url = [imageView.linkURL absoluteString]; + + BOOL matched = NO; + NSArray *types = @[@".png", @".jpg", @".gif", @".jpeg"]; + for (NSString *type in types) { + if (NSNotFound != [url rangeOfString:type].location) { + matched = YES; + break; + } + } + + if (matched) { + controller = [[WPImageViewController alloc] initWithImage:imageView.image andURL:imageView.linkURL]; + [self.navigationController presentViewController:controller animated:YES completion:nil]; + } else { + controller = [[WPWebViewController alloc] init]; + [(WPWebViewController *)controller setUrl:((ReaderImageView *)sender).linkURL]; + } + } else { + controller = [[WPImageViewController alloc] initWithImage:imageView.image]; + } + + controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + controller.modalPresentationStyle = UIModalPresentationFullScreen; + [self.navigationController pushViewController:controller animated:YES]; +} + +- (void)postView:(ReaderPostView *)postView didReceiveVideoLinkAction:(id)sender { + ReaderVideoView *videoView = (ReaderVideoView *)sender; + if (videoView.contentType == ReaderVideoContentTypeVideo) { + + MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:videoView.contentURL]; + // Remove the movie player view controller from the "playback did finish" notification observers + [[NSNotificationCenter defaultCenter] removeObserver:controller + name:MPMoviePlayerPlaybackDidFinishNotification + object:controller.moviePlayer]; + + // Register this class as an observer instead + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(moviePlaybackDidFinish:) + name:MPMoviePlayerPlaybackDidFinishNotification + object:controller.moviePlayer]; + + controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + controller.modalPresentationStyle = UIModalPresentationFormSheet; + [self.navigationController presentViewController:controller animated:YES completion:nil]; + + } else { + // Should either be an iframe, or an object embed. In either case a src attribute should have been parsed for the contentURL. + // Assume this is content we can show and try to load it. + UIViewController *controller = [[WPWebVideoViewController alloc] initWithURL:videoView.contentURL]; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; + navController.navigationBar.translucent = NO; + navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + navController.modalPresentationStyle = UIModalPresentationFullScreen; + navController.title = (videoView.title != nil) ? videoView.title : @"Video"; + [self.navigationController presentViewController:navController animated:YES completion:nil]; + } +} + +- (void)postView:(ReaderPostView *)postView didReceiveTagAction:(id)sender { + ReaderPost *post = postView.post; + + NSString *endpoint = [NSString stringWithFormat:@"read/tags/%@/posts", post.primaryTagSlug]; + NSDictionary *dict = @{@"endpoint" : endpoint, + @"title" : post.primaryTagName}; + + [[NSUserDefaults standardUserDefaults] setObject:dict forKey:ReaderCurrentTopicKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self.navigationController popViewControllerAnimated:YES]; + [[NSNotificationCenter defaultCenter] postNotificationName:ReaderTopicDidChangeNotification object:self]; } @@ -600,7 +744,6 @@ - (NSDate *)lastSyncDate { return self.post.dateCommentsSynced; } - - (void)syncWithUserInteraction:(BOOL)userInteraction { if ([self.post.postID integerValue] == 0 ) { // Weird that this should ever happen. self.post.dateCommentsSynced = [NSDate date]; @@ -615,14 +758,12 @@ - (void)syncWithUserInteraction:(BOOL)userInteraction { success:^(AFHTTPRequestOperation *operation, id responseObject) { [self onSyncSuccess:operation response:responseObject]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - [self onSyncFailure:operation error:error]; }]; } - (void)loadMoreWithSuccess:(void (^)())success failure:(void (^)(NSError *error))failure { - if ([self.resultsController.fetchedObjects count] == 0) { + if ([self.resultsController.fetchedObjects count] == 0) return; - } if (_loadingMore) return; _loadingMore = YES; @@ -636,7 +777,6 @@ - (void)loadMoreWithSuccess:(void (^)())success failure:(void (^)(NSError *error success:^(AFHTTPRequestOperation *operation, id responseObject) { [self onSyncSuccess:operation response:responseObject]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - [self onSyncFailure:operation error:error]; }]; } @@ -652,7 +792,7 @@ - (void)onSyncSuccess:(AFHTTPRequestOperation *)operation response:(id)responseO return; } - if([commentsArr count] < ([_comments count] + ReaderCommentsToSync)) { + if ([commentsArr count] < ([_comments count] + ReaderCommentsToSync)) { _hasMoreContent = NO; } @@ -663,11 +803,6 @@ - (void)onSyncSuccess:(AFHTTPRequestOperation *)operation response:(id)responseO [self prepareComments]; } -// TODO: Unhandled failure for user interaction -- (void)onSyncFailure:(AFHTTPRequestOperation *)operation error:(NSError *)error { - @throw ([NSException exceptionWithName:@"Method unimplemented" reason:@"onSyncFailure:error: not implemented in ReaderPostDetailViewController" userInfo:nil]); -} - #pragma mark - Infinite Scrolling @@ -700,27 +835,55 @@ - (void)disableInfiniteScrolling { #pragma mark - UITableView Delegate Methods +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (section == 0) + return IS_IPHONE ? 1 : WPTableViewTopMargin; + + return kSectionHeaderHight; +} + - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (![_comments count]) { + if (indexPath.section == ReaderDetailContentSection) { + return self.postView.frame.size.height; + } + + if ([_comments count] == 0) { return 0.0f; } + + CGFloat width = IS_IPAD ? WPTableViewFixedWidth : tableView.frame.size.width; ReaderComment *comment = [_comments objectAtIndex:indexPath.row]; return [ReaderCommentTableViewCell heightForComment:comment - width:tableView.frame.size.width + width:width tableStyle:tableView.style accessoryType:UITableViewCellAccessoryNone]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (section == ReaderDetailContentSection) + return 1; + return [_comments count]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; + return ReaderDetailSectionCount; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == ReaderDetailContentSection) { + UITableViewCell *postCell = [self.tableView dequeueReusableCellWithIdentifier:@"PostCell"]; + postCell.selectionStyle = UITableViewCellSelectionStyleNone; + [postCell.contentView addSubview:self.postView]; + + return postCell; + } + NSString *cellIdentifier = @"ReaderCommentCell"; ReaderCommentTableViewCell *cell = (ReaderCommentTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { @@ -758,6 +921,9 @@ - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NS } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == ReaderDetailContentSection) + return; + if (![self canComment]) { [self.tableView deselectRowAtIndexPath:indexPath animated:NO]; return; @@ -780,6 +946,9 @@ - (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPat } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == ReaderDetailContentSection) + return; + if (IS_IPAD) { cell.accessoryType = UITableViewCellAccessoryNone; } @@ -835,14 +1004,6 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { } } - -#pragma mark - ReaderPostDetailView Delegate Methods - -- (void)readerPostDetailViewLayoutChanged { - self.tableView.tableHeaderView = self.headerView; -} - - #pragma mark - ReaderTextForm Delegate Methods - (void)readerTextFormDidCancel:(ReaderTextFormView *)readerTextForm { @@ -916,6 +1077,8 @@ - (NSFetchedResultsController *)resultsController { managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil]; + + _resultsController.delegate = self; NSError *error = nil; if (![_resultsController performFetch:&error]) { @@ -926,6 +1089,20 @@ - (NSFetchedResultsController *)resultsController { return _resultsController; } +- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { +} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + [self.tableView reloadData]; +} + +- (void)controller:(NSFetchedResultsController *)controller + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath *)indexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath *)newIndexPath { +} + #pragma mark - MFMailComposeViewControllerDelegate diff --git a/WordPress/Classes/ReaderPostTableViewCell.h b/WordPress/Classes/ReaderPostTableViewCell.h index cb8a14a9de9c..9f2381beb836 100644 --- a/WordPress/Classes/ReaderPostTableViewCell.h +++ b/WordPress/Classes/ReaderPostTableViewCell.h @@ -8,28 +8,19 @@ #import #import "ReaderPost.h" -#import "ReaderTableViewCell.h" +#import "WPTableViewCell.h" -@interface ReaderPostTableViewCell : ReaderTableViewCell +@class ReaderPostView; + +@interface ReaderPostTableViewCell : WPTableViewCell @property (nonatomic, strong) UIImageView *avatarImageView; + (CGFloat)cellHeightForPost:(ReaderPost *)post withWidth:(CGFloat)width; + (ReaderPostTableViewCell *)cellForSubview:(UIView *)subview; - (void)configureCell:(ReaderPost *)post; -- (void)setFeaturedImage:(UIImage *)image; -- (void)setAvatar:(UIImage *)avatar; @property (nonatomic, strong) ReaderPost *post; -@property (nonatomic, strong) UIButton *followButton; -@property (nonatomic, strong) UIButton *tagButton; -@property (nonatomic, strong) UIButton *likeButton; -@property (nonatomic, strong) UIButton *reblogButton; -@property (nonatomic, strong) UIButton *commentButton; -@property (nonatomic, strong) UIButton *timeButton; - -extern CGFloat const RPTVCMaxImageHeightPercentage; - -- (void)updateControlBar; +@property (nonatomic, strong) ReaderPostView *postView; @end diff --git a/WordPress/Classes/ReaderPostTableViewCell.m b/WordPress/Classes/ReaderPostTableViewCell.m index d537195883da..faa42619c908 100644 --- a/WordPress/Classes/ReaderPostTableViewCell.m +++ b/WordPress/Classes/ReaderPostTableViewCell.m @@ -7,151 +7,31 @@ // #import "ReaderPostTableViewCell.h" -#import -#import -#import "UIImageView+Gravatar.h" #import "WordPressAppDelegate.h" -#import "WPWebViewController.h" -#import "UIImageView+AFNetworkingExtra.h" -#import "UILabel+SuggestSize.h" -#import "WPAvatarSource.h" -#import "ReaderButton.h" -#import "NSDate+StringFormatting.h" -#import "UIColor+Helpers.h" +#import "ReaderPost.h" +#import "ReaderPostView.h" -const CGFloat RPTVCAuthorPadding = 8.0f; -const CGFloat RPTVCHorizontalInnerPadding = 12.0f; const CGFloat RPTVCHorizontalOuterPadding = 8.0f; -const CGFloat RPTVCMetaViewHeight = 48.0f; -const CGFloat RPTVCAuthorViewHeight = 32.0f; -const CGFloat RPTVCVerticalPadding = 16.0f; -const CGFloat RPTVCAvatarSize = 32.0f; -const CGFloat RPTVCBorderHeight = 1.0f; -const CGFloat RPTVCSmallButtonLeftPadding = 2; // Follow, tag -const CGFloat RPTVCMaxImageHeightPercentage = 0.59f; -const CGFloat RPTVCMaxSummaryHeight = 88.0f; -const CGFloat RPTVCLineHeightMultiple = 1.15f; - -// Control buttons (Like, Reblog, ...) -const CGFloat RPTVCControlButtonHeight = 48.0f; -const CGFloat RPTVCControlButtonWidth = 48.0f; -const CGFloat RPTVCControlButtonSpacing = 12.0f; -const CGFloat RPTVCControlButtonBorderSize = 0.0f; +const CGFloat RPTVCVerticalOuterPadding = 16.0f; @interface ReaderPostTableViewCell() - -@property (nonatomic, strong) UIView *containerView; -@property (nonatomic, strong) UILabel *titleLabel; -@property (nonatomic, strong) CALayer *titleBorder; -@property (nonatomic, strong) UILabel *snippetLabel; - -@property (nonatomic, strong) UIView *metaView; -@property (nonatomic, strong) CALayer *metaBorder; -@property (nonatomic, strong) UIView *byView; -@property (nonatomic, strong) UILabel *bylineLabel; -@property (nonatomic, strong) UIView *controlView; - -@property (nonatomic, assign) BOOL showImage; - +@property (nonatomic, strong) UIView *sideBorderView; @end @implementation ReaderPostTableViewCell { - BOOL _avatarIsSet; - UIView *_sideBorderView; } + (CGFloat)cellHeightForPost:(ReaderPost *)post withWidth:(CGFloat)width { - CGFloat desiredHeight = 0.0f; - - // Margins - CGFloat contentWidth = width; - if (IS_IPAD) { - contentWidth = contentWidth * (1 - WPTableViewCellMarginPercentage * 2); - } - // iPhone has extra padding around each cell if (IS_IPHONE) { - contentWidth -= RPTVCHorizontalOuterPadding * 2; + width = width - 2 * RPTVCHorizontalOuterPadding; } - - desiredHeight += RPTVCAuthorPadding; - desiredHeight += RPTVCAuthorViewHeight; - desiredHeight += RPTVCAuthorPadding; - - // Are we showing an image? What size should it be? - if (post.featuredImageURL) { - CGFloat height = ceilf((contentWidth * RPTVCMaxImageHeightPercentage)); - desiredHeight += height; - } - // Everything but the image has inner padding - contentWidth -= RPTVCHorizontalInnerPadding * 2; - - // Title - desiredHeight += RPTVCVerticalPadding; - NSAttributedString *postTitle = [self titleAttributedStringForPost:post]; - desiredHeight += [postTitle boundingRectWithSize:CGSizeMake(contentWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size.height; - desiredHeight += RPTVCVerticalPadding; - - // Post summary - if ([post.summary length] > 0) { - NSAttributedString *postSummary = [self summaryAttributedStringForPost:post]; - desiredHeight += [postSummary boundingRectWithSize:CGSizeMake(contentWidth, RPTVCMaxSummaryHeight) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size.height; - desiredHeight += RPTVCVerticalPadding; - } - - // Tag - NSString *tagName = post.primaryTagName; - if ([tagName length] > 0) { - CGSize size = [tagName suggestedSizeWithFont:[self summaryFont] width:contentWidth]; - desiredHeight += size.height; - } - - // Padding above and below the line - desiredHeight += RPTVCVerticalPadding * 2; - - // Size of the meta view - desiredHeight += RPTVCMetaViewHeight; + CGFloat desiredHeight = [ReaderPostView heightForPost:post withWidth:width]; return ceil(desiredHeight); } -+ (NSAttributedString *)titleAttributedStringForPost:(ReaderPost *)post { - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - [style setLineHeightMultiple:RPTVCLineHeightMultiple]; - NSDictionary *attributes = @{NSParagraphStyleAttributeName : style, - NSFontAttributeName : [self titleFont]}; - NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithString:[post.postTitle trim] - attributes:attributes]; - - return titleString; -} - -+ (NSAttributedString *)summaryAttributedStringForPost:(ReaderPost *)post { - NSString *summary = [post.summary trim]; - NSInteger newline = [post.summary rangeOfString:@"\n"].location; - - if (newline != NSNotFound) - summary = [post.summary substringToIndex:newline]; - - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - [style setLineHeightMultiple:RPTVCLineHeightMultiple]; - NSDictionary *attributes = @{NSParagraphStyleAttributeName : style, - NSFontAttributeName : [self summaryFont]}; - NSMutableAttributedString *attributedSummary = [[NSMutableAttributedString alloc] initWithString:summary - attributes:attributes]; - - return attributedSummary; -} - -+ (UIFont *)titleFont { - return [UIFont fontWithName:@"Merriweather-Bold" size:21.0f]; -} - -+ (UIFont *)summaryFont { - return [UIFont fontWithName:@"OpenSans" size:14.0f]; -} - + (ReaderPostTableViewCell *)cellForSubview:(UIView *)subview { UIView *view = subview; while (![view isKindOfClass:self]) { @@ -174,23 +54,17 @@ - (void)dealloc { - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - if (self) { - self.backgroundColor = [UIColor colorWithWhite:0.9453125f alpha:1.f]; - self.contentView.backgroundColor = [WPStyleGuide itsEverywhereGrey]; - - _sideBorderView = [[UIView alloc] init]; - _sideBorderView.backgroundColor = [UIColor colorWithWhite:0.9f alpha:1.f]; - _sideBorderView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [self.contentView addSubview:_sideBorderView]; + if (self) { + self.sideBorderView = [[UIView alloc] init]; + self.sideBorderView.backgroundColor = [UIColor colorWithWhite:0.9f alpha:1.f]; + self.sideBorderView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.contentView addSubview:self.sideBorderView]; - self.containerView = [[UIView alloc] init]; - _containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _containerView.backgroundColor = [UIColor whiteColor]; - _containerView.opaque = YES; - [self.contentView addSubview:_containerView]; + self.postView = [[ReaderPostView alloc] initWithFrame:self.frame showFullContent:NO]; + self.postView.backgroundColor = [UIColor whiteColor]; + self.backgroundColor = [WPStyleGuide itsEverywhereGrey]; - [self buildPostContent]; - [self buildMetaContent]; + [self.contentView addSubview:self.postView]; } return self; @@ -201,7 +75,7 @@ - (void)setHighlightedEffect:(BOOL)highlighted animated:(BOOL)animated { delay:0 options:UIViewAnimationCurveEaseInOut animations:^{ - _sideBorderView.hidden = highlighted; + self.sideBorderView.hidden = highlighted; self.alpha = highlighted ? .7f : 1.f; if (highlighted) { CGFloat perspective = IS_IPAD ? -0.00005 : -0.0001; @@ -221,13 +95,9 @@ - (void)setHighlightedEffect:(BOOL)highlighted animated:(BOOL)animated { - (void)setPost:(ReaderPost *)post { if ([post isEqual:_post]) return; - - if (_post) { - [_post removeObserver:self forKeyPath:@"isReblogged" context:@"reblogging"]; - } - + + self.postView.post = post; _post = post; - [_post addObserver:self forKeyPath:@"isReblogged" options:NSKeyValueObservingOptionNew context:@"reblogging"]; } - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { @@ -253,322 +123,34 @@ - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [self setHighlightedEffect:selected animated:animated]; } -- (void)buildPostContent { - self.cellImageView.contentMode = UIViewContentModeScaleAspectFill; - [_containerView addSubview:self.cellImageView]; - self.titleLabel = [[UILabel alloc] init]; - _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _titleLabel.backgroundColor = [UIColor clearColor]; - _titleLabel.textColor = [UIColor colorWithHexString:@"333"]; - _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; - _titleLabel.numberOfLines = 0; - [_containerView addSubview:_titleLabel]; - - self.titleBorder = [[CALayer alloc] init]; - _titleBorder.backgroundColor = [[UIColor colorWithHexString:@"f1f1f1"] CGColor]; - [_containerView.layer addSublayer:_titleBorder]; - - self.snippetLabel = [[UILabel alloc] init]; - _snippetLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _snippetLabel.backgroundColor = [UIColor clearColor]; - _snippetLabel.textColor = [UIColor colorWithHexString:@"333"]; - _snippetLabel.lineBreakMode = NSLineBreakByTruncatingTail; - _snippetLabel.numberOfLines = 4; - [_containerView addSubview:_snippetLabel]; - - self.byView = [[UIView alloc] init]; - _byView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _byView.backgroundColor = [UIColor whiteColor]; - _byView.userInteractionEnabled = YES; - [_containerView addSubview:_byView]; - - CGRect avatarFrame = CGRectMake(RPTVCHorizontalInnerPadding, RPTVCAuthorPadding, RPTVCAvatarSize, RPTVCAvatarSize); - self.avatarImageView = [[UIImageView alloc] initWithFrame:avatarFrame]; - [_byView addSubview:_avatarImageView]; - - self.bylineLabel = [[UILabel alloc] init]; - _bylineLabel.backgroundColor = [UIColor clearColor]; - _bylineLabel.numberOfLines = 1; - _bylineLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _bylineLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; - _bylineLabel.adjustsFontSizeToFitWidth = NO; - _bylineLabel.textColor = [UIColor colorWithHexString:@"333"]; - [_byView addSubview:_bylineLabel]; - - self.followButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; - [_followButton setSelected:[self.post.isFollowing boolValue]]; - _followButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - _followButton.backgroundColor = [UIColor clearColor]; - _followButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; - NSString *followString = NSLocalizedString(@"Follow", @"Prompt to follow a blog."); - NSString *followedString = NSLocalizedString(@"Following", @"User is following the blog."); - [_followButton setTitle:followString forState:UIControlStateNormal]; - [_followButton setTitle:followedString forState:UIControlStateSelected]; - [_followButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPTVCSmallButtonLeftPadding, 0, 0)]; - [_followButton setImage:[UIImage imageNamed:@"reader-postaction-follow"] forState:UIControlStateNormal]; - [_followButton setImage:[UIImage imageNamed:@"reader-postaction-following"] forState:UIControlStateSelected]; - [_followButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; - [_byView addSubview:_followButton]; +- (void)prepareForReuse { + [super prepareForReuse]; - self.tagButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; - _tagButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - _tagButton.backgroundColor = [UIColor clearColor]; - _tagButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; - [_tagButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPTVCSmallButtonLeftPadding, 0, 0)]; - [_tagButton setImage:[UIImage imageNamed:@"reader-postaction-tag"] forState:UIControlStateNormal]; - [_tagButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; - [_containerView addSubview:_tagButton]; + [self.postView reset]; + [self setHighlightedEffect:NO animated:NO]; } -- (void)buildMetaContent { - self.metaView = [[UIView alloc] init]; - _metaView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _metaView.backgroundColor = [UIColor clearColor]; - [_containerView addSubview:_metaView]; - - self.metaBorder = [[CALayer alloc] init]; - _metaBorder.backgroundColor = [[UIColor colorWithHexString:@"f1f1f1"] CGColor]; - [_metaView.layer addSublayer:_metaBorder]; - - self.timeButton = [UIButton buttonWithType:UIButtonTypeCustom]; - _timeButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - _timeButton.backgroundColor = [UIColor clearColor]; - _timeButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; - [_timeButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPTVCSmallButtonLeftPadding, 0, 0)]; - [_timeButton setImage:[UIImage imageNamed:@"reader-postaction-time"] forState:UIControlStateNormal]; - [_timeButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; - [_metaView addSubview:_timeButton]; - self.likeButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; - _likeButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; - _likeButton.backgroundColor = [UIColor whiteColor]; - [_likeButton setImage:[UIImage imageNamed:@"reader-postaction-like-blue"] forState:UIControlStateNormal]; - [_likeButton setImage:[UIImage imageNamed:@"reader-postaction-like-active"] forState:UIControlStateSelected]; - [_metaView addSubview:_likeButton]; - - self.reblogButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; - _reblogButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; - _reblogButton.backgroundColor = [UIColor whiteColor]; - [_reblogButton setImage:[UIImage imageNamed:@"reader-postaction-reblog-blue"] forState:UIControlStateNormal]; - [_reblogButton setImage:[UIImage imageNamed:@"reader-postaction-reblog-done"] forState:UIControlStateSelected]; - [_metaView addSubview:_reblogButton]; - - self.commentButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; - _commentButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; - _commentButton.backgroundColor = [UIColor whiteColor]; - [_commentButton setImage:[UIImage imageNamed:@"reader-postaction-comment-blue"] forState:UIControlStateNormal]; - [_commentButton setImage:[UIImage imageNamed:@"reader-postaction-comment-active"] forState:UIControlStateSelected]; - [_metaView addSubview:_commentButton]; -} +#pragma mark - Instance Methods - (void)layoutSubviews { [super layoutSubviews]; CGFloat leftPadding = IS_IPHONE ? RPTVCHorizontalOuterPadding : 0; CGFloat contentWidth = self.frame.size.width - leftPadding * 2; - CGFloat innerContentWidth = contentWidth - RPTVCHorizontalInnerPadding * 2; - CGFloat nextY = RPTVCAuthorPadding; - CGFloat height = 0.0f; - - CGRect frame = CGRectMake(leftPadding, 0, contentWidth, self.frame.size.height - RPTVCVerticalPadding); - _containerView.frame = frame; - _byView.frame = CGRectMake(0, 0, contentWidth, RPTVCAuthorViewHeight + RPTVCAuthorPadding * 2); - CGFloat bylineX = RPTVCAvatarSize + RPTVCAuthorPadding + RPTVCHorizontalInnerPadding; - _bylineLabel.frame = CGRectMake(bylineX, RPTVCAuthorPadding - 2, contentWidth - bylineX, 18); - - if ([self.post isFollowable]) { - _followButton.hidden = NO; - CGFloat followX = bylineX - 4; // Fudge factor for image alignment - CGFloat followY = RPTVCAuthorPadding + _bylineLabel.frame.size.height - 2; - height = ceil([_followButton.titleLabel suggestedSizeForWidth:innerContentWidth].height); - _followButton.frame = CGRectMake(followX, followY, contentWidth - bylineX, height); - } else { - _followButton.hidden = YES; - } - - nextY += RPTVCAuthorViewHeight + RPTVCAuthorPadding; - - // Are we showing an image? What size should it be? - if (_showImage) { - _titleBorder.hidden = YES; - height = ceilf(contentWidth * RPTVCMaxImageHeightPercentage); - self.cellImageView.frame = CGRectMake(0, nextY, contentWidth, height); - nextY += height; - } else { - _titleBorder.hidden = NO; - _titleBorder.frame = CGRectMake(RPTVCHorizontalInnerPadding, nextY, contentWidth - RPTVCHorizontalInnerPadding * 2, RPTVCBorderHeight); - } - - // Position the title - nextY += RPTVCVerticalPadding; - height = ceil([_titleLabel suggestedSizeForWidth:innerContentWidth].height); - _titleLabel.frame = CGRectMake(RPTVCHorizontalInnerPadding, nextY, innerContentWidth, height); - nextY += height + RPTVCVerticalPadding; - - // Position the snippet - if ([self.post.summary length] > 0) { - height = ceil([_snippetLabel suggestedSizeForWidth:innerContentWidth].height); - height = MIN(height, RPTVCMaxSummaryHeight); - _snippetLabel.frame = CGRectMake(RPTVCHorizontalInnerPadding, nextY, innerContentWidth, height); - nextY += ceilf(height + RPTVCVerticalPadding); - } - - // Tag - if ([self.post.primaryTagName length] > 0) { - height = ceil([_tagButton.titleLabel suggestedSizeForWidth:innerContentWidth].height); - _tagButton.frame = CGRectMake(RPTVCHorizontalInnerPadding, nextY, innerContentWidth, height); - nextY += height + RPTVCVerticalPadding; - self.tagButton.hidden = NO; - } else { - self.tagButton.hidden = YES; - } - - // Position the meta view and its subviews - _metaView.frame = CGRectMake(0, nextY, contentWidth, RPTVCMetaViewHeight); - _metaBorder.frame = CGRectMake(RPTVCHorizontalInnerPadding, 0, contentWidth - RPTVCHorizontalInnerPadding * 2, RPTVCBorderHeight); - - BOOL commentsOpen = [[self.post commentsOpen] boolValue] && [self.post isWPCom]; - CGFloat buttonWidth = RPTVCControlButtonWidth; - CGFloat buttonX = _metaView.frame.size.width - RPTVCControlButtonWidth; - CGFloat buttonY = RPTVCBorderHeight; // Just below the line - - // Button order from right-to-left: Like, [Comment], Reblog, - _likeButton.frame = CGRectMake(buttonX, buttonY, buttonWidth, RPTVCControlButtonHeight); - buttonX -= buttonWidth + RPTVCControlButtonSpacing; - - if (commentsOpen) { - self.commentButton.hidden = NO; - self.commentButton.frame = CGRectMake(buttonX, buttonY, buttonWidth, RPTVCControlButtonHeight); - buttonX -= buttonWidth + RPTVCControlButtonSpacing; - } else { - self.commentButton.hidden = YES; - } - _reblogButton.frame = CGRectMake(buttonX, buttonY, buttonWidth - RPTVCControlButtonBorderSize, RPTVCControlButtonHeight); - - CGFloat timeWidth = contentWidth - _reblogButton.frame.origin.x; - _timeButton.frame = CGRectMake(RPTVCHorizontalInnerPadding, RPTVCBorderHeight, timeWidth, RPTVCControlButtonHeight); + CGRect frame = CGRectMake(leftPadding, 0, contentWidth, self.frame.size.height - RPTVCVerticalOuterPadding); + self.postView.frame = frame; CGFloat sideBorderX = RPTVCHorizontalOuterPadding - 1; // Just to the left of the container - CGFloat sideBorderHeight = self.frame.size.height - RPTVCVerticalPadding; // Just below it - _sideBorderView.frame = CGRectMake(sideBorderX, 1, self.frame.size.width - sideBorderX * 2, sideBorderHeight); -} - -- (void)prepareForReuse { - [super prepareForReuse]; - - _avatarIsSet = NO; - - _bylineLabel.text = nil; - _titleLabel.text = nil; - _snippetLabel.text = nil; - [_tagButton setTitle:nil forState:UIControlStateNormal]; - - [self setHighlightedEffect:NO animated:NO]; -} - - -#pragma mark - Instance Methods - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - [self updateControlBar]; + CGFloat sideBorderHeight = self.frame.size.height - RPTVCVerticalOuterPadding; // Just below it + self.sideBorderView.frame = CGRectMake(sideBorderX, 1, self.frame.size.width - sideBorderX * 2, sideBorderHeight); } - (void)configureCell:(ReaderPost *)post { self.post = post; - - // This will show the placeholder avatar. Do this here instead of prepareForReusue - // so avatars show up after a cell is created, and not dequeued. - [self setAvatar:nil]; - - _titleLabel.attributedText = [ReaderPostTableViewCell titleAttributedStringForPost:post]; - _snippetLabel.attributedText = [ReaderPostTableViewCell summaryAttributedStringForPost:post]; - - _bylineLabel.text = [post authorString]; - - [_timeButton setTitle:[post.dateCreated shortString] forState:UIControlStateNormal]; - - self.showImage = NO; - self.cellImageView.hidden = YES; - self.cellImageView.contentMode = UIViewContentModeCenter; - self.cellImageView.image = [UIImage imageNamed:@"wp_img_placeholder"]; - if (post.featuredImageURL) { - self.showImage = YES; - self.cellImageView.hidden = NO; - } - - if ([self.post.primaryTagName length] > 0) { - _tagButton.hidden = NO; - [_tagButton setTitle:self.post.primaryTagName forState:UIControlStateNormal]; - } else { - _tagButton.hidden = YES; - } - - if ([self.post isWPCom]) { - _likeButton.hidden = NO; - _reblogButton.hidden = NO; - _commentButton.hidden = NO; - } else { - _likeButton.hidden = YES; - _reblogButton.hidden = YES; - _commentButton.hidden = YES; - } - - _reblogButton.userInteractionEnabled = ![post.isReblogged boolValue]; - - [self updateControlBar]; -} - -- (void)setAvatar:(UIImage *)avatar { - if (_avatarIsSet) - return; - - static UIImage *wpcomBlavatar; - static UIImage *wporgBlavatar; - if (!wpcomBlavatar) { - wpcomBlavatar = [UIImage imageNamed:@"wpcom_blavatar"]; - } - - if (!wporgBlavatar) { - wporgBlavatar = [UIImage imageNamed:@"wporg_blavatar"]; - } - - if (avatar) { - self.avatarImageView.image = avatar; - _avatarIsSet = YES; - } else { - self.avatarImageView.image = [self.post isWPCom] ? wpcomBlavatar : wporgBlavatar; - } -} - -- (void)setFeaturedImage:(UIImage *)image { - self.cellImageView.contentMode = UIViewContentModeScaleAspectFill; - self.cellImageView.image = image; -} - -- (void)updateControlBar { - if (!_post) - return; - - _likeButton.selected = _post.isLiked.boolValue; - _reblogButton.selected = _post.isReblogged.boolValue; - _reblogButton.userInteractionEnabled = !_reblogButton.selected; -} - -- (void)likeAction:(id)sender { - [self.post toggleLikedWithSuccess:^{ - if ([self.post.isLiked boolValue]) { - [WPMobileStats trackEventForWPCom:StatsEventReaderLikedPost]; - } else { - [WPMobileStats trackEventForWPCom:StatsEventReaderUnlikedPost]; - } - } failure:^(NSError *error) { - DDLogError(@"Error Liking Post : %@", [error localizedDescription]); - [self updateControlBar]; - }]; - - [self updateControlBar]; + [self.postView configurePost:post]; } @end diff --git a/WordPress/Classes/ReaderPostView.h b/WordPress/Classes/ReaderPostView.h new file mode 100644 index 000000000000..93ac0f557a25 --- /dev/null +++ b/WordPress/Classes/ReaderPostView.h @@ -0,0 +1,46 @@ +// +// ReaderPostView.h +// WordPress +// +// Created by Michael Johnston on 11/19/13. +// Copyright (c) 2013 WordPress. All rights reserved. +// + +#import +#import "ReaderPost.h" +#import "DTAttributedTextContentView.h" +#import "ReaderMediaQueue.h" + +@class ReaderPostView; + +@protocol ReaderPostViewDelegate +@optional +- (void)postView:(ReaderPostView *)postView didReceiveFollowAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveTagAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveLikeAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveReblogAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveCommentAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveLinkAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveImageLinkAction:(id)sender; +- (void)postView:(ReaderPostView *)postView didReceiveVideoLinkAction:(id)sender; +@end + +@interface ReaderPostView : UIView { + +} + +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) ReaderPost *post; +@property (nonatomic, strong) UIImageView *cellImageView; +@property (nonatomic, strong) UIImageView *avatarImageView; + ++ (CGFloat)heightForPost:(ReaderPost *)post withWidth:(CGFloat)width; +- (id)initWithFrame:(CGRect)frame showFullContent:(BOOL)showFullContent; +- (void)setFeaturedImage:(UIImage *)image; +- (void)setAvatar:(UIImage *)avatar; +- (void)updateActionButtons; +- (void)reset; +- (void)configurePost:(ReaderPost *)post; +- (void)updateLayout; + +@end diff --git a/WordPress/Classes/ReaderPostView.m b/WordPress/Classes/ReaderPostView.m new file mode 100644 index 000000000000..511221fa5b93 --- /dev/null +++ b/WordPress/Classes/ReaderPostView.m @@ -0,0 +1,869 @@ +// +// ReaderPostView.m +// WordPress +// +// Created by Michael Johnston on 11/19/13. +// Copyright (c) 2013 WordPress. All rights reserved. +// + +#import "ReaderPostView.h" + +#import +#import +#import "UIImageView+Gravatar.h" +#import "WordPressAppDelegate.h" +#import "WPWebViewController.h" +#import "UIImageView+AFNetworkingExtra.h" +#import "UILabel+SuggestSize.h" +#import "WPAvatarSource.h" +#import "ReaderButton.h" +#import "NSDate+StringFormatting.h" +#import "UIColor+Helpers.h" +#import "WPTableViewCell.h" +#import "DTTiledLayerWithoutFade.h" +#import "ReaderMediaView.h" +#import "ReaderImageView.h" +#import "ReaderVideoView.h" + +const CGFloat RPVAuthorPadding = 8.0f; +const CGFloat RPVHorizontalInnerPadding = 12.0f; +const CGFloat RPVMetaViewHeight = 48.0f; +const CGFloat RPVAuthorViewHeight = 32.0f; +const CGFloat RPVVerticalPadding = 16.0f; +const CGFloat RPVAvatarSize = 32.0f; +const CGFloat RPVBorderHeight = 1.0f; +const CGFloat RPVSmallButtonLeftPadding = 2; // Follow, tag +const CGFloat RPVMaxImageHeightPercentage = 0.59f; +const CGFloat RPVMaxSummaryHeight = 88.0f; +const CGFloat RPVLineHeightMultiple = 1.15f; +const CGFloat RPVFollowButtonWidth = 100.0f; + +// Control buttons (Like, Reblog, ...) +const CGFloat RPVControlButtonHeight = 48.0f; +const CGFloat RPVControlButtonWidth = 48.0f; +const CGFloat RPVControlButtonSpacing = 12.0f; +const CGFloat RPVControlButtonBorderSize = 0.0f; + +@interface ReaderPostView() + +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) CALayer *titleBorder; +@property (nonatomic, strong) UILabel *snippetLabel; +@property (nonatomic, strong) UIButton *followButton; +@property (nonatomic, strong) UIButton *tagButton; +@property (nonatomic, strong) UIButton *likeButton; +@property (nonatomic, strong) UIButton *reblogButton; +@property (nonatomic, strong) UIButton *commentButton; +@property (nonatomic, strong) UIButton *timeButton; +@property (nonatomic, strong) DTAttributedTextContentView *textContentView; + +@property (nonatomic, strong) UIView *metaView; +@property (nonatomic, strong) CALayer *metaBorder; +@property (nonatomic, strong) UIView *byView; +@property (nonatomic, strong) UILabel *bylineLabel; +@property (nonatomic, strong) UIView *controlView; + +@property (nonatomic, assign) BOOL showImage; +@property (nonatomic, assign) BOOL showFullContent; +@property (nonatomic, strong) NSMutableArray *mediaArray; +@property (nonatomic, strong) ReaderMediaQueue *mediaQueue; + +@end + +@implementation ReaderPostView { + BOOL _avatarIsSet; +} + ++ (CGFloat)heightForPost:(ReaderPost *)post withWidth:(CGFloat)width { + CGFloat desiredHeight = 0.0f; + + // Margins + CGFloat contentWidth = width; + if (IS_IPAD) { + contentWidth = WPTableViewFixedWidth; + } + + desiredHeight += RPVAuthorPadding; + desiredHeight += RPVAuthorViewHeight; + desiredHeight += RPVAuthorPadding; + + // Are we showing an image? What size should it be? + if (post.featuredImageURL) { + CGFloat height = ceilf((contentWidth * RPVMaxImageHeightPercentage)); + desiredHeight += height; + } + + // Everything but the image has inner padding + contentWidth -= RPVHorizontalInnerPadding * 2; + + // Title + desiredHeight += RPVVerticalPadding; + NSAttributedString *postTitle = [self titleAttributedStringForPost:post]; + desiredHeight += [postTitle boundingRectWithSize:CGSizeMake(contentWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size.height; + desiredHeight += RPVVerticalPadding; + + // Post summary + if ([post.summary length] > 0) { + NSAttributedString *postSummary = [self summaryAttributedStringForPost:post]; + desiredHeight += [postSummary boundingRectWithSize:CGSizeMake(contentWidth, RPVMaxSummaryHeight) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size.height; + desiredHeight += RPVVerticalPadding; + } + + // Tag + NSString *tagName = post.primaryTagName; + if ([tagName length] > 0) { + CGRect tagRect = [tagName boundingRectWithSize:CGSizeMake(contentWidth, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading + attributes:@{NSFontAttributeName : [self summaryFont]} + context:nil]; + desiredHeight += tagRect.size.height; + } + + // Padding above and below the line + desiredHeight += RPVVerticalPadding * 2; + + // Size of the meta view + desiredHeight += RPVMetaViewHeight; + + return ceil(desiredHeight); +} + ++ (NSAttributedString *)titleAttributedStringForPost:(ReaderPost *)post { + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + [style setLineHeightMultiple:RPVLineHeightMultiple]; + NSDictionary *attributes = @{NSParagraphStyleAttributeName : style, + NSFontAttributeName : [self titleFont]}; + NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithString:[post.postTitle trim] + attributes:attributes]; + + return titleString; +} + ++ (NSAttributedString *)summaryAttributedStringForPost:(ReaderPost *)post { + NSString *summary = [post.summary trim]; + NSInteger newline = [post.summary rangeOfString:@"\n"].location; + + if (newline != NSNotFound) + summary = [post.summary substringToIndex:newline]; + + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + [style setLineHeightMultiple:RPVLineHeightMultiple]; + NSDictionary *attributes = @{NSParagraphStyleAttributeName : style, + NSFontAttributeName : [self summaryFont]}; + NSMutableAttributedString *attributedSummary = [[NSMutableAttributedString alloc] initWithString:summary + attributes:attributes]; + + return attributedSummary; +} + ++ (UIFont *)titleFont { + return [UIFont fontWithName:@"Merriweather-Bold" size:21.0f]; +} + ++ (UIFont *)summaryFont { + return [UIFont fontWithName:@"OpenSans" size:14.0f]; +} + + +#pragma mark - Lifecycle Methods + +- (id)initWithFrame:(CGRect)frame showFullContent:(BOOL)showFullContent { + self = [super initWithFrame:frame]; + if (self) { + self.mediaArray = [NSMutableArray array]; + self.mediaQueue = [[ReaderMediaQueue alloc] initWithDelegate:self]; + + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.opaque = YES; + self.showFullContent = showFullContent; + + self.cellImageView = [[UIImageView alloc] init]; + _cellImageView.backgroundColor = [WPStyleGuide readGrey]; + _cellImageView.contentMode = UIViewContentModeScaleAspectFill; + _cellImageView.clipsToBounds = YES; + + [self buildPostContent]; + [self buildMetaContent]; + } + return self; +} + +- (void)dealloc { + self.post = nil; + self.delegate = nil; + _textContentView.delegate = nil; + _mediaQueue.delegate = nil; + [_mediaQueue discardQueuedItems]; +} + +- (void)configurePost:(ReaderPost *)post { + self.post = post; + + // This will show the placeholder avatar. Do this here instead of prepareForReusue + // so avatars show up after a cell is created, and not dequeued. + [self setAvatar:nil]; + + _titleLabel.attributedText = [ReaderPostView titleAttributedStringForPost:post]; + + if (self.showFullContent) { + NSString *contentString = [NSString stringWithFormat:@"

%@", self.post.content]; + NSData *data = [contentString dataUsingEncoding:NSUTF8StringEncoding]; + _textContentView.attributedString = [[NSAttributedString alloc] initWithHTMLData:data + options:[WPStyleGuide defaultDTCoreTextOptions] + documentAttributes:nil]; + [_textContentView relayoutText]; + } else { + _snippetLabel.attributedText = [ReaderPostView summaryAttributedStringForPost:post]; + } + + _bylineLabel.text = [post authorString]; + + [_timeButton setTitle:[post.dateCreated shortString] forState:UIControlStateNormal]; + + self.showImage = NO; + self.cellImageView.hidden = YES; + if (post.featuredImageURL) { + self.showImage = YES; + self.cellImageView.hidden = NO; + } + + if ([self.post.primaryTagName length] > 0) { + _tagButton.hidden = NO; + [_tagButton setTitle:self.post.primaryTagName forState:UIControlStateNormal]; + } else { + _tagButton.hidden = YES; + } + + if ([self.post isWPCom]) { + _likeButton.hidden = NO; + _reblogButton.hidden = NO; + _commentButton.hidden = NO; + } else { + _likeButton.hidden = YES; + _reblogButton.hidden = YES; + _commentButton.hidden = YES; + } + + [_followButton setSelected:[self.post.isFollowing boolValue]]; + _reblogButton.userInteractionEnabled = ![post.isReblogged boolValue]; + + [self updateActionButtons]; +} + +- (void)setPost:(ReaderPost *)post { + if ([post isEqual:_post]) + return; + + _post = post; +} + +- (UIView *)buildContentView { + UIView *contentView; + + if (self.showFullContent) { + [DTAttributedTextContentView setLayerClass:[DTTiledLayerWithoutFade class]]; + + // Needs an initial frame + self.textContentView = [[DTAttributedTextContentView alloc] initWithFrame:self.frame]; + _textContentView.delegate = self; + _textContentView.backgroundColor = [UIColor whiteColor]; + _textContentView.edgeInsets = UIEdgeInsetsMake(0.0f, RPVHorizontalInnerPadding, 0.0f, RPVHorizontalInnerPadding); + _textContentView.shouldDrawImages = NO; + _textContentView.shouldDrawLinks = NO; + contentView = _textContentView; + } else { + self.snippetLabel = [[UILabel alloc] init]; + _snippetLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _snippetLabel.backgroundColor = [UIColor clearColor]; + _snippetLabel.textColor = [UIColor colorWithHexString:@"333"]; + _snippetLabel.lineBreakMode = NSLineBreakByTruncatingTail; + _snippetLabel.numberOfLines = 4; + contentView = _snippetLabel; + } + + return contentView; +} + +- (void)buildPostContent { + self.cellImageView.contentMode = UIViewContentModeScaleAspectFill; + [self addSubview:self.cellImageView]; + + self.titleLabel = [[UILabel alloc] init]; + _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _titleLabel.backgroundColor = [UIColor clearColor]; + _titleLabel.textColor = [UIColor colorWithHexString:@"333"]; + _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + _titleLabel.numberOfLines = 0; + [self addSubview:_titleLabel]; + + self.titleBorder = [[CALayer alloc] init]; + _titleBorder.backgroundColor = [[UIColor colorWithHexString:@"f1f1f1"] CGColor]; + [self.layer addSublayer:_titleBorder]; + + [self addSubview:[self buildContentView]]; + + self.byView = [[UIView alloc] init]; + _byView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _byView.backgroundColor = [UIColor clearColor]; + _byView.userInteractionEnabled = YES; + [self addSubview:_byView]; + + CGRect avatarFrame = CGRectMake(RPVHorizontalInnerPadding, RPVAuthorPadding, RPVAvatarSize, RPVAvatarSize); + self.avatarImageView = [[UIImageView alloc] initWithFrame:avatarFrame]; + [_byView addSubview:_avatarImageView]; + + self.bylineLabel = [[UILabel alloc] init]; + _bylineLabel.backgroundColor = [UIColor clearColor]; + _bylineLabel.numberOfLines = 1; + _bylineLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _bylineLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; + _bylineLabel.adjustsFontSizeToFitWidth = NO; + _bylineLabel.textColor = [UIColor colorWithHexString:@"333"]; + [_byView addSubview:_bylineLabel]; + + self.followButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; + _followButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + _followButton.backgroundColor = [UIColor clearColor]; + _followButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; + NSString *followString = NSLocalizedString(@"Follow", @"Prompt to follow a blog."); + NSString *followedString = NSLocalizedString(@"Following", @"User is following the blog."); + [_followButton setTitle:followString forState:UIControlStateNormal]; + [_followButton setTitle:followedString forState:UIControlStateSelected]; + [_followButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPVSmallButtonLeftPadding, 0, 0)]; + [_followButton setImage:[UIImage imageNamed:@"reader-postaction-follow"] forState:UIControlStateNormal]; + [_followButton setImage:[UIImage imageNamed:@"reader-postaction-following"] forState:UIControlStateSelected]; + [_followButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; + [_followButton addTarget:self action:@selector(followAction:) forControlEvents:UIControlEventTouchUpInside]; + [_byView addSubview:_followButton]; + + self.tagButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; + _tagButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + _tagButton.backgroundColor = [UIColor clearColor]; + _tagButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; + [_tagButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPVSmallButtonLeftPadding, 0, 0)]; + [_tagButton setImage:[UIImage imageNamed:@"reader-postaction-tag"] forState:UIControlStateNormal]; + [_tagButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; + [_tagButton addTarget:self action:@selector(tagAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_tagButton]; +} + +- (void)buildMetaContent { + self.metaView = [[UIView alloc] init]; + _metaView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _metaView.backgroundColor = [UIColor clearColor]; + [self addSubview:_metaView]; + + self.metaBorder = [[CALayer alloc] init]; + _metaBorder.backgroundColor = [[UIColor colorWithHexString:@"f1f1f1"] CGColor]; + [_metaView.layer addSublayer:_metaBorder]; + + self.timeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _timeButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + _timeButton.backgroundColor = [UIColor clearColor]; + _timeButton.titleLabel.font = [UIFont fontWithName:@"OpenSans" size:12.0f]; + [_timeButton setTitleEdgeInsets: UIEdgeInsetsMake(0, RPVSmallButtonLeftPadding, 0, 0)]; + [_timeButton setImage:[UIImage imageNamed:@"reader-postaction-time"] forState:UIControlStateNormal]; + [_timeButton setTitleColor:[UIColor colorWithHexString:@"aaa"] forState:UIControlStateNormal]; + [_metaView addSubview:_timeButton]; + + self.likeButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; + _likeButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; + _likeButton.backgroundColor = [UIColor whiteColor]; + [_likeButton setImage:[UIImage imageNamed:@"reader-postaction-like-blue"] forState:UIControlStateNormal]; + [_likeButton setImage:[UIImage imageNamed:@"reader-postaction-like-active"] forState:UIControlStateSelected]; + [_likeButton addTarget:self action:@selector(likeAction:) forControlEvents:UIControlEventTouchUpInside]; + [_metaView addSubview:_likeButton]; + + self.reblogButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; + _reblogButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; + _reblogButton.backgroundColor = [UIColor whiteColor]; + [_reblogButton setImage:[UIImage imageNamed:@"reader-postaction-reblog-blue"] forState:UIControlStateNormal]; + [_reblogButton setImage:[UIImage imageNamed:@"reader-postaction-reblog-done"] forState:UIControlStateSelected]; + [_reblogButton addTarget:self action:@selector(reblogAction:) forControlEvents:UIControlEventTouchUpInside]; + [_metaView addSubview:_reblogButton]; + + self.commentButton = [ReaderButton buttonWithType:UIButtonTypeCustom]; + _commentButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; + _commentButton.backgroundColor = [UIColor whiteColor]; + [_commentButton setImage:[UIImage imageNamed:@"reader-postaction-comment-blue"] forState:UIControlStateNormal]; + [_commentButton setImage:[UIImage imageNamed:@"reader-postaction-comment-active"] forState:UIControlStateSelected]; + [_commentButton addTarget:self action:@selector(commentAction:) forControlEvents:UIControlEventTouchUpInside]; + [_metaView addSubview:_commentButton]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGFloat contentWidth; + + // On iPad, get the width from the cell instead in order to account for margins + if (IS_IPHONE) { + contentWidth = self.frame.size.width; + } else { + contentWidth = self.superview.frame.size.width; + } + + CGFloat innerContentWidth = contentWidth - RPVHorizontalInnerPadding * 2; + CGFloat nextY = RPVAuthorPadding; + CGFloat height = 0.0f; + + _byView.frame = CGRectMake(0, 0, contentWidth, RPVAuthorViewHeight + RPVAuthorPadding * 2); + CGFloat bylineX = RPVAvatarSize + RPVAuthorPadding + RPVHorizontalInnerPadding; + _bylineLabel.frame = CGRectMake(bylineX, RPVAuthorPadding - 2, contentWidth - bylineX, 18); + + if ([self.post isFollowable]) { + _followButton.hidden = NO; + CGFloat followX = bylineX - 4; // Fudge factor for image alignment + CGFloat followY = RPVAuthorPadding + _bylineLabel.frame.size.height - 2; + height = ceil([_followButton.titleLabel suggestedSizeForWidth:innerContentWidth].height); + _followButton.frame = CGRectMake(followX, followY, RPVFollowButtonWidth, height); + } else { + _followButton.hidden = YES; + } + + nextY += RPVAuthorViewHeight + RPVAuthorPadding; + + // Are we showing an image? What size should it be? + if (_showImage) { + _titleBorder.hidden = YES; + height = ceilf(contentWidth * RPVMaxImageHeightPercentage); + self.cellImageView.frame = CGRectMake(0, nextY, contentWidth, height); + nextY += height; + } else { + _titleBorder.hidden = NO; + _titleBorder.frame = CGRectMake(RPVHorizontalInnerPadding, nextY, contentWidth - RPVHorizontalInnerPadding * 2, RPVBorderHeight); + } + + // Position the title + nextY += RPVVerticalPadding; + height = ceil([_titleLabel suggestedSizeForWidth:innerContentWidth].height); + _titleLabel.frame = CGRectMake(RPVHorizontalInnerPadding, nextY, innerContentWidth, height); + nextY += height + RPVVerticalPadding; + + // Position the snippet / content + if ([self.post.summary length] > 0) { + if (self.showFullContent) { + [self.textContentView relayoutText]; + height = [self.textContentView suggestedFrameSizeToFitEntireStringConstraintedToWidth:contentWidth].height; + CGRect textContainerFrame = _textContentView.frame; + textContainerFrame.size.width = contentWidth; + textContainerFrame.size.height = height; + textContainerFrame.origin.y = nextY; + self.textContentView.frame = textContainerFrame; + nextY += textContainerFrame.size.height + RPVVerticalPadding; + } else { + height = ceil([_snippetLabel suggestedSizeForWidth:innerContentWidth].height); + height = MIN(height, RPVMaxSummaryHeight); + _snippetLabel.frame = CGRectMake(RPVHorizontalInnerPadding, nextY, innerContentWidth, height); + nextY += ceilf(height + RPVVerticalPadding); + } + } + + // Tag + if ([self.post.primaryTagName length] > 0) { + height = ceil([_tagButton.titleLabel suggestedSizeForWidth:innerContentWidth].height); + _tagButton.frame = CGRectMake(RPVHorizontalInnerPadding, nextY, innerContentWidth, height); + nextY += height + RPVVerticalPadding; + self.tagButton.hidden = NO; + } else { + self.tagButton.hidden = YES; + } + + // Position the meta view and its subviews + _metaView.frame = CGRectMake(0, nextY, contentWidth, RPVMetaViewHeight); + _metaBorder.frame = CGRectMake(RPVHorizontalInnerPadding, 0, contentWidth - RPVHorizontalInnerPadding * 2, RPVBorderHeight); + + BOOL commentsOpen = [[self.post commentsOpen] boolValue] && [self.post isWPCom]; + CGFloat buttonWidth = RPVControlButtonWidth; + CGFloat buttonX = _metaView.frame.size.width - RPVControlButtonWidth; + CGFloat buttonY = RPVBorderHeight; // Just below the line + + // Button order from right-to-left: Like, [Comment], Reblog, + _likeButton.frame = CGRectMake(buttonX, buttonY, buttonWidth, RPVControlButtonHeight); + buttonX -= buttonWidth + RPVControlButtonSpacing; + + if (commentsOpen) { + self.commentButton.hidden = NO; + self.commentButton.frame = CGRectMake(buttonX, buttonY, buttonWidth, RPVControlButtonHeight); + buttonX -= buttonWidth + RPVControlButtonSpacing; + } else { + self.commentButton.hidden = YES; + } + _reblogButton.frame = CGRectMake(buttonX, buttonY, buttonWidth - RPVControlButtonBorderSize, RPVControlButtonHeight); + + CGFloat timeWidth = contentWidth - _reblogButton.frame.origin.x; + _timeButton.frame = CGRectMake(RPVHorizontalInnerPadding, RPVBorderHeight, timeWidth, RPVControlButtonHeight); + + // Update own frame + CGRect ownFrame = self.frame; + + ownFrame.size.height = nextY + RPVMetaViewHeight + 1; + self.frame = ownFrame; +} + +- (void)reset { + self.post = nil; + _avatarIsSet = NO; + + _bylineLabel.text = nil; + _titleLabel.text = nil; + _snippetLabel.text = nil; + [_tagButton setTitle:nil forState:UIControlStateNormal]; + [_followButton setSelected:NO]; + + [_cellImageView cancelImageRequestOperation]; + _cellImageView.image = nil; +} + + +#pragma mark - Actions + +// Forward the actions to the delegate; do it this way instead of exposing buttons as properties +// because the view can have dynamically generated buttons (e.g. links) +- (void)followAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveFollowAction:)]) { + [self.delegate postView:self didReceiveFollowAction:sender]; + } +} + +- (void)tagAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveTagAction:)]) { + [self.delegate postView:self didReceiveTagAction:sender]; + } +} + +- (void)reblogAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveReblogAction:)]) { + [self.delegate postView:self didReceiveReblogAction:sender]; + } +} + +- (void)commentAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveCommentAction:)]) { + [self.delegate postView:self didReceiveCommentAction:sender]; + } +} + +- (void)likeAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveLikeAction:)]) { + [self.delegate postView:self didReceiveLikeAction:sender]; + } +} + +- (void)linkAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveLinkAction:)]) { + [self.delegate postView:self didReceiveLinkAction:sender]; + } +} + +- (void)imageLinkAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveImageLinkAction:)]) { + [self.delegate postView:self didReceiveImageLinkAction:sender]; + } +} + +- (void)videoLinkAction:(id)sender { + if ([self.delegate respondsToSelector:@selector(postView:didReceiveVideoLinkAction:)]) { + [self.delegate postView:self didReceiveVideoLinkAction:sender]; + } +} + + +#pragma mark - Instance Methods + +- (void)setAvatar:(UIImage *)avatar { + if (_avatarIsSet) + return; + + static UIImage *wpcomBlavatar; + static UIImage *wporgBlavatar; + if (!wpcomBlavatar) { + wpcomBlavatar = [UIImage imageNamed:@"wpcom_blavatar"]; + } + + if (!wporgBlavatar) { + wporgBlavatar = [UIImage imageNamed:@"wporg_blavatar"]; + } + + if (avatar) { + self.avatarImageView.image = avatar; + _avatarIsSet = YES; + } else { + self.avatarImageView.image = [self.post isWPCom] ? wpcomBlavatar : wporgBlavatar; + } +} + +- (void)setFeaturedImage:(UIImage *)image { + self.cellImageView.contentMode = UIViewContentModeScaleAspectFill; + self.cellImageView.image = image; +} + +- (void)updateActionButtons { + if (!_post) + return; + + _likeButton.selected = _post.isLiked.boolValue; + _reblogButton.selected = _post.isReblogged.boolValue; + _reblogButton.userInteractionEnabled = !_reblogButton.selected; +} + +- (BOOL)isEmoji:(NSURL *)url { + return ([[url absoluteString] rangeOfString:@"wp.com/wp-includes/images/smilies"].location != NSNotFound); +} + +- (void)handleMediaViewLoaded:(ReaderMediaView *)mediaView { + + BOOL frameChanged = [self updateMediaLayout:mediaView]; + + if (frameChanged) { + // need to reset the layouter because otherwise we get the old framesetter or cached layout frames + self.textContentView.layouter = nil; + + // layout might have changed due to image sizes + [self.textContentView relayoutText]; + + [self updateLayout]; + } +} + +- (void)updateLayout { + // Size the textContentView + CGRect frame = _textContentView.frame; + CGFloat height = [_textContentView suggestedFrameSizeToFitEntireStringConstraintedToWidth:frame.size.width].height; + frame.size.height = height; + _textContentView.frame = frame; + + frame = self.frame; + frame.size.height = height + _textContentView.frame.origin.y + 10.0f; // + bottom padding + self.frame = frame; +} + + +- (BOOL)updateMediaLayout:(ReaderMediaView *)imageView { + BOOL frameChanged = NO; + NSURL *url = imageView.contentURL; + + CGSize originalSize = imageView.frame.size; + CGSize viewSize = imageView.image.size; + + if ([self isEmoji:url]) { + CGFloat scale = [UIScreen mainScreen].scale; + viewSize.width *= scale; + viewSize.height *= scale; + } else { + CGFloat ratio = viewSize.width / viewSize.height; + CGFloat width = _textContentView.frame.size.width; + CGFloat availableWidth = _textContentView.frame.size.width - (_textContentView.edgeInsets.left + _textContentView.edgeInsets.right); + + viewSize.width = availableWidth; + + if (imageView.isShowingPlaceholder) { + viewSize.height = roundf(width / imageView.placeholderRatio); + } else { + viewSize.height = roundf(width / ratio); + } + + viewSize.height += imageView.edgeInsets.top; // account for the top edge inset. + } + + // Widths should always match + if (viewSize.height != originalSize.height) { + frameChanged = YES; + } + + NSPredicate *pred = [NSPredicate predicateWithFormat:@"contentURL == %@", url]; + + // update all attachments that matchin this URL (possibly multiple images with same size) + for (DTTextAttachment *attachment in [self.textContentView.layoutFrame textAttachmentsWithPredicate:pred]) { + attachment.originalSize = originalSize; + attachment.displaySize = viewSize; + } + + return frameChanged; +} + + +#pragma mark ReaderMediaQueueDelegate methods + +- (void)readerMediaQueue:(ReaderMediaQueue *)mediaQueue didLoadBatch:(NSArray *)batch { + BOOL frameChanged = NO; + + for (NSInteger i = 0; i < [batch count]; i++) { + ReaderMediaView *mediaView = [batch objectAtIndex:i]; + if ([self updateMediaLayout:mediaView]) { + frameChanged = YES; + } + } + + if (frameChanged) { + // need to reset the layouter because otherwise we get the old framesetter or cached layout frames + self.textContentView.layouter = nil; + + // layout might have changed due to image sizes + [self.textContentView relayoutText]; + [self setNeedsLayout]; + } +} + +#pragma mark - DTCoreAttributedTextContentView Delegate Methods + +- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttributedString:(NSAttributedString *)string frame:(CGRect)frame { + NSDictionary *attributes = [string attributesAtIndex:0 effectiveRange:nil]; + + NSURL *URL = [attributes objectForKey:DTLinkAttribute]; + NSString *identifier = [attributes objectForKey:DTGUIDAttribute]; + + DTLinkButton *button = [[DTLinkButton alloc] initWithFrame:frame]; + button.URL = URL; + button.minimumHitSize = CGSizeMake(25, 25); // adjusts it's bounds so that button is always large enough + button.GUID = identifier; + + // get image with normal link text + UIImage *normalImage = [attributedTextContentView contentImageWithBounds:frame options:DTCoreTextLayoutFrameDrawingDefault]; + [button setImage:normalImage forState:UIControlStateNormal]; + + // get image for highlighted link text + UIImage *highlightImage = [attributedTextContentView contentImageWithBounds:frame options:DTCoreTextLayoutFrameDrawingDrawLinksHighlighted]; + [button setImage:highlightImage forState:UIControlStateHighlighted]; + + // use normal push action for opening URL + [button addTarget:self action:@selector(linkAction:) forControlEvents:UIControlEventTouchUpInside]; + + return button; +} + + +- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame { + + if (!attachment.contentURL) + return nil; + + CGFloat width = _textContentView.frame.size.width; + CGFloat availableWidth = _textContentView.frame.size.width - (_textContentView.edgeInsets.left + _textContentView.edgeInsets.right); + + // The ReaderImageView view will conform to the width constraints of the _textContentView. We want the image itself to run out to the edges, + // so position it offset by the inverse of _textContentView's edgeInsets. Also add top padding so we don't bump into a line of text. + // Remeber to add an extra 10px to the frame to preserve aspect ratio. + UIEdgeInsets edgeInsets = _textContentView.edgeInsets; + edgeInsets.left = 0.0f - edgeInsets.left; + edgeInsets.top = 15.0f; + edgeInsets.right = 0.0f - edgeInsets.right; + edgeInsets.bottom = 0.0f; + + if ([attachment isKindOfClass:[DTImageTextAttachment class]]) { + if ([self isEmoji:attachment.contentURL]) { + // minimal frame to suppress drawing context errors with 0 height or width. + frame.size.width = MAX(frame.size.width, 1.0f); + frame.size.height = MAX(frame.size.height, 1.0f); + ReaderImageView *imageView = [[ReaderImageView alloc] initWithFrame:frame]; + [_mediaArray addObject:imageView]; + [self.mediaQueue enqueueMedia:imageView + withURL:attachment.contentURL + placeholderImage:nil + size:CGSizeMake(15.0f, 15.0f) + isPrivate:self.post.isPrivate + success:nil + failure:nil]; + return imageView; + } + + DTImageTextAttachment *imageAttachment = (DTImageTextAttachment *)attachment; + UIImage *image; + + if ([imageAttachment.image isKindOfClass:[UIImage class]]) { + image = imageAttachment.image; + + CGFloat ratio = image.size.width / image.size.height; + frame.size.width = availableWidth; + frame.size.height = roundf(width / ratio); + } else { + if (frame.size.width > 1.0f && frame.size.height > 1.0f) { + CGFloat ratio = frame.size.width / frame.size.height; + frame.size.width = availableWidth; + frame.size.height = roundf(width / ratio); + } else { + frame.size.width = availableWidth; + frame.size.height = roundf(width * RPVMaxImageHeightPercentage); + } + } + + // offset the top edge inset keeping the image from bumping the text above it. + frame.size.height += edgeInsets.top; + + ReaderImageView *imageView = [[ReaderImageView alloc] initWithFrame:frame]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + imageView.edgeInsets = edgeInsets; + + [_mediaArray addObject:imageView]; + imageView.linkURL = attachment.hyperLinkURL; + [imageView addTarget:self action:@selector(imageLinkAction:) forControlEvents:UIControlEventTouchUpInside]; + + if ([imageAttachment.image isKindOfClass:[UIImage class]]) { + [imageView setImage:image]; + } else { + imageView.backgroundColor = [UIColor colorWithRed:192.0f/255.0f green:192.0f/255.0f blue:192.0f/255.0f alpha:1.0]; + + [self.mediaQueue enqueueMedia:imageView + withURL:attachment.contentURL + placeholderImage:image + size:CGSizeMake(width, 0) + isPrivate:self.post.isPrivate + success:^(ReaderMediaView *readerMediaView) { + ReaderImageView *imageView = (ReaderImageView *)readerMediaView; + imageView.contentMode = UIViewContentModeScaleAspectFit; + imageView.backgroundColor = [UIColor clearColor]; + } + failure:nil]; + } + + return imageView; + + } else { + + ReaderVideoContentType videoType; + + if ([attachment isKindOfClass:[DTVideoTextAttachment class]]) { + videoType = ReaderVideoContentTypeVideo; + } else if ([attachment isKindOfClass:[DTIframeTextAttachment class]]) { + videoType = ReaderVideoContentTypeIFrame; + } else if ([attachment isKindOfClass:[DTObjectTextAttachment class]]) { + videoType = ReaderVideoContentTypeEmbed; + } else { + return nil; // Can't handle whatever this is :P + } + + // make sure we have a reasonable size. + if (frame.size.width > width) { + if (frame.size.height == 0) { + frame.size.height = roundf(frame.size.width * 0.66f); + } + CGFloat ratio = frame.size.width / frame.size.height; + frame.size.width = availableWidth; + frame.size.height = roundf(width / ratio); + } + + // offset the top edge inset keeping the image from bumping the text above it. + frame.size.height += edgeInsets.top; + + ReaderVideoView *videoView = [[ReaderVideoView alloc] initWithFrame:frame]; + videoView.contentMode = UIViewContentModeCenter; + videoView.backgroundColor = [UIColor colorWithRed:192.0f/255.0f green:192.0f/255.0f blue:192.0f/255.0f alpha:1.0]; + videoView.edgeInsets = edgeInsets; + + [_mediaArray addObject:videoView]; + [videoView setContentURL:attachment.contentURL ofType:videoType success:^(id readerVideoView) { + [(ReaderVideoView *)readerVideoView setContentMode:UIViewContentModeScaleAspectFit]; + [self handleMediaViewLoaded:readerVideoView]; + } failure:^(id readerVideoView, NSError *error) { + [self handleMediaViewLoaded:readerVideoView]; + + }]; + + [videoView addTarget:self action:@selector(videoLinkAction:) forControlEvents:UIControlEventTouchUpInside]; + + return videoView; + } +} + + +@end diff --git a/WordPress/Classes/ReaderPostsViewController.h b/WordPress/Classes/ReaderPostsViewController.h index ff3826863860..a9e327c7a84c 100644 --- a/WordPress/Classes/ReaderPostsViewController.h +++ b/WordPress/Classes/ReaderPostsViewController.h @@ -8,7 +8,10 @@ #import #import "WPTableViewController.h" +#import "ReaderPostView.h" -@interface ReaderPostsViewController : WPTableViewController +extern NSString * const ReaderTopicDidChangeNotification; + +@interface ReaderPostsViewController : WPTableViewController @end diff --git a/WordPress/Classes/ReaderPostsViewController.m b/WordPress/Classes/ReaderPostsViewController.m index f6f7d0802ecc..e6f5081f6066 100644 --- a/WordPress/Classes/ReaderPostsViewController.m +++ b/WordPress/Classes/ReaderPostsViewController.m @@ -29,20 +29,24 @@ static CGFloat const RPVCScrollingFastVelocityThreshold = 30.f; static CGFloat const RPVCHeaderHeightPhone = 10.f; -NSString *const RPVCDisplayedNativeFriendFinder = @"DisplayedNativeFriendFinder"; +static CGFloat const RPVCMaxImageHeightPercentage = 0.58f; +static CGFloat const RPVCExtraTableViewHeightPercentage = 2.0f; -@interface ReaderPostsViewController () { +NSString * const ReaderTopicDidChangeNotification = @"ReaderTopicDidChangeNotification"; +NSString * const RPVCDisplayedNativeFriendFinder = @"DisplayedNativeFriendFinder"; + +@interface ReaderPostsViewController () { BOOL _hasMoreContent; BOOL _loadingMore; WPTableImageSource *_featuredImageSource; CGFloat keyboardOffset; - BOOL _isScrollingFast; CGFloat _lastOffset; UIPopoverController *_popover; WPAnimatedBox *_animatedBox; } @property (nonatomic, strong) ReaderReblogFormView *readerReblogFormView; +@property (nonatomic, strong) ReaderPostDetailViewController *detailController; @property (nonatomic, strong) UINavigationBar *navBar; @property (nonatomic) BOOL isShowingReblogForm; @@ -76,6 +80,10 @@ - (id)init { _hasMoreContent = YES; self.infiniteScrollEnabled = YES; self.incrementalLoadingSupported = YES; + + [[NSNotificationCenter defaultCenter] addObserverForName:ReaderTopicDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [self readerTopicDidChange]; + }]; } return self; } @@ -88,27 +96,29 @@ - (void)viewDidLoad { CGFloat maxWidth = self.tableView.bounds.size.width; if (IS_IPHONE) { maxWidth = MAX(self.tableView.bounds.size.width, self.tableView.bounds.size.height); + } else { + maxWidth = WPTableViewFixedWidth; } - maxWidth -= 20.f; // Container frame - CGFloat maxHeight = maxWidth * RPTVCMaxImageHeightPercentage; + + CGFloat maxHeight = maxWidth * RPVCMaxImageHeightPercentage; _featuredImageSource = [[WPTableImageSource alloc] initWithMaxSize:CGSizeMake(maxWidth, maxHeight)]; _featuredImageSource.delegate = self; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - + // Topics button + UIBarButtonItem *button = nil; UIButton *topicsButton = [UIButton buttonWithType:UIButtonTypeCustom]; [topicsButton setImage:[UIImage imageNamed:@"icon-reader-topics"] forState:UIControlStateNormal]; [topicsButton setImage:[UIImage imageNamed:@"icon-reader-topics-active"] forState:UIControlStateHighlighted]; CGSize imageSize = [UIImage imageNamed:@"icon-reader-topics"].size; topicsButton.frame = CGRectMake(0.0, 0.0, imageSize.width, imageSize.height); + topicsButton.contentEdgeInsets = UIEdgeInsetsMake(0, 16, 0, -16); [topicsButton addTarget:self action:@selector(topicsAction:) forControlEvents:UIControlEventTouchUpInside]; - UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithCustomView:topicsButton]; - + button = [[UIBarButtonItem alloc] initWithCustomView:topicsButton]; [button setAccessibilityLabel:NSLocalizedString(@"Topics", @"")]; - [WPStyleGuide setRightBarButtonItemWithCorrectSpacing:button forNavigationItem:self.navigationItem]; - + self.navigationItem.rightBarButtonItem = button; CGRect frame = CGRectMake(0.0f, self.view.bounds.size.height, self.view.bounds.size.width, [ReaderReblogFormView desiredHeight]); self.readerReblogFormView = [[ReaderReblogFormView alloc] initWithFrame:frame]; @@ -142,7 +152,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.title = [[[ReaderPost currentTopic] objectForKey:@"title"] capitalizedString]; - [self loadImagesForVisibleRows]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardDidShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; @@ -164,6 +173,8 @@ - (void)viewDidAppear:(BOOL)animated { [self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES]; } + [self resizeTableViewForImagePreloading]; + // Delay box animation after the view appears double delayInSeconds = 0.3; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); @@ -182,14 +193,32 @@ - (void)viewWillDisappear:(BOOL)animated { - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - // After rotation, visible images might be scaled up/down - // Force them to reload so they're pixel perfect - [self loadImagesForVisibleRows]; + [self resizeTableViewForImagePreloading]; } #pragma mark - Instance Methods +- (void)resizeTableViewForImagePreloading { + // Use a little trick to preload more images by making the table view longer + CGRect rect = self.tableView.frame; + CGFloat navigationHeight = self.navigationController.view.frame.size.height; + CGFloat extraHeight = navigationHeight * RPVCExtraTableViewHeightPercentage; + rect.size.height = navigationHeight + extraHeight; + self.tableView.frame = rect; + + // Move insets up to compensate + UIEdgeInsets insets = self.tableView.contentInset; + insets.bottom = extraHeight + [self tabBarSize].height; + self.tableView.contentInset = insets; + + // Adjust the scroll insets as well + UIEdgeInsets scrollInsets = self.tableView.scrollIndicatorInsets; + scrollInsets.bottom = insets.bottom; + self.tableView.scrollIndicatorInsets = scrollInsets; + [self.tableView layoutIfNeeded]; +} + - (void)setTitle:(NSString *)title { [super setTitle:title]; @@ -260,7 +289,10 @@ - (void)showReblogForm { CGFloat reblogHeight = [ReaderReblogFormView desiredHeight]; CGRect tableFrame = self.tableView.frame; - tableFrame.size.height = self.tableView.frame.size.height - reblogHeight; + CGRect superviewFrame = self.view.superview.frame; + + // The table's frame is artifically tall due to resizeTableViewForImagePreloading, so effectively undo that + tableFrame.size.height = superviewFrame.size.height - tableFrame.origin.y - reblogHeight - [self tabBarSize].height; self.tableView.frame = tableFrame; CGFloat y = tableFrame.origin.y + tableFrame.size.height; @@ -280,49 +312,17 @@ - (void)hideReblogForm { tableFrame.size.height = self.tableView.frame.size.height + _readerReblogFormView.frame.size.height; self.tableView.frame = tableFrame; + [self resizeTableViewForImagePreloading]; [_readerReblogFormView removeFromSuperview]; self.isShowingReblogForm = NO; [self.view endEditing:YES]; } -- (void)loadImagesForVisibleRows { - NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows]; - for (NSIndexPath *indexPath in visiblePaths) { - ReaderPost *post = (ReaderPost *)[self.resultsController objectAtIndexPath:indexPath]; - - ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; - - UIImage *image = [post cachedAvatarWithSize:cell.avatarImageView.bounds.size]; - CGSize imageSize = cell.avatarImageView.bounds.size; - if (image) { - [cell setAvatar:image]; - } else { - __weak UITableView *tableView = self.tableView; - [post fetchAvatarWithSize:imageSize success:^(UIImage *image) { - if (cell == [tableView cellForRowAtIndexPath:indexPath]) { - [cell setAvatar:image]; - } - }]; - } - - if (post.featuredImageURL) { - NSURL *imageURL = post.featuredImageURL; - imageSize = cell.cellImageView.frame.size; - image = [_featuredImageSource imageForURL:imageURL withSize:imageSize]; - if (image) { - [cell setFeaturedImage:image]; - } else { - [_featuredImageSource fetchImageForURL:imageURL withSize:imageSize indexPath:indexPath isPrivate:post.isPrivate]; - } - } - } -} - -#pragma mark - Actions +#pragma mark - ReaderPostView delegate methods -- (void)reblogAction:(id)sender { - NSIndexPath *selectedPath = [self.tableView indexPathForSelectedRow]; +- (void)postView:(ReaderPostView *)postView didReceiveReblogAction:(id)sender { + NSIndexPath *selectedPath = [self.tableView indexPathForSelectedRow]; UITableViewCell *cell = [ReaderPostTableViewCell cellForSubview:sender]; NSIndexPath *path = [self.tableView indexPathForCell:cell]; @@ -341,9 +341,8 @@ - (void)reblogAction:(id)sender { } } -- (void)likeAction:(id)sender { - ReaderPostTableViewCell *cell = [ReaderPostTableViewCell cellForSubview:sender]; - ReaderPost *post = cell.post; +- (void)postView:(ReaderPostView *)postView didReceiveLikeAction:(id)sender { + ReaderPost *post = postView.post; [post toggleLikedWithSuccess:^{ if ([post.isLiked boolValue]) { [WPMobileStats trackEventForWPCom:StatsEventReaderLikedPost]; @@ -352,42 +351,16 @@ - (void)likeAction:(id)sender { } } failure:^(NSError *error) { DDLogError(@"Error Liking Post : %@", [error localizedDescription]); - [cell updateControlBar]; + [postView updateActionButtons]; }]; - [cell updateControlBar]; + [postView updateActionButtons]; } -- (void)topicsAction:(id)sender { - ReaderTopicsViewController *controller = [[ReaderTopicsViewController alloc] initWithStyle:UITableViewStyleGrouped]; - controller.delegate = self; - if (IS_IPAD) { - if (_popover) { - [self dismissPopover]; - return; - } - - _popover = [[UIPopoverController alloc] initWithContentViewController:controller]; - - UIBarButtonItem *shareButton; - if (IS_IOS7) { - // For iOS7 there is an added spacing element inserted before the share button to adjust the position of the button. - shareButton = [self.navigationItem.rightBarButtonItems objectAtIndex:1]; - } else { - shareButton = self.navigationItem.rightBarButtonItem; - } - [_popover presentPopoverFromBarButtonItem:shareButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; - } else { - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; - navController.navigationBar.translucent = NO; - [self presentViewController:navController animated:YES completion:nil]; - } -} - -- (void)followAction:(id)sender { +- (void)postView:(ReaderPostView *)postView didReceiveFollowAction:(id)sender { UIButton *followButton = (UIButton *)sender; ReaderPostTableViewCell *cell = [ReaderPostTableViewCell cellForSubview:sender]; - ReaderPost *post = cell.post; + ReaderPost *post = postView.post; if (![post isFollowable]) return; @@ -402,13 +375,12 @@ - (void)followAction:(id)sender { }]; } -- (void)commentAction:(id)sender { +- (void)postView:(ReaderPostView *)postView didReceiveCommentAction:(id)sender { // TODO: allow commenting } -- (void)tagAction:(id)sender { - ReaderPostTableViewCell *cell = [ReaderPostTableViewCell cellForSubview:sender]; - ReaderPost *post = cell.post; +- (void)postView:(ReaderPostView *)postView didReceiveTagAction:(id)sender { + ReaderPost *post = postView.post; NSString *endpoint = [NSString stringWithFormat:@"read/tags/%@/posts", post.primaryTagSlug]; NSDictionary *dict = @{@"endpoint" : endpoint, @@ -416,7 +388,29 @@ - (void)tagAction:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:dict forKey:ReaderCurrentTopicKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - [self readerTopicChanged]; + [self readerTopicDidChange]; +} + + +#pragma mark - Actions + +- (void)topicsAction:(id)sender { + ReaderTopicsViewController *controller = [[ReaderTopicsViewController alloc] initWithStyle:UITableViewStyleGrouped]; + if (IS_IPAD) { + if (_popover) { + [self dismissPopover]; + return; + } + + _popover = [[UIPopoverController alloc] initWithContentViewController:controller]; + + UIBarButtonItem *shareButton = self.navigationItem.rightBarButtonItem; + [_popover presentPopoverFromBarButtonItem:shareButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } else { + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; + navController.navigationBar.translucent = NO; + [self presentViewController:navController animated:YES completion:nil]; + } } #pragma mark - ReaderTextForm Delegate Methods @@ -433,22 +427,8 @@ - (void)readerTextFormDidCancel:(ReaderTextFormView *)readerTextForm { #pragma mark - UIScrollView Delegate Methods -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - CGFloat offset = self.tableView.contentOffset.y; - // We just take a diff from the last known offset, as the approximation is good enough - CGFloat velocity = fabsf(offset - _lastOffset); - if (velocity > RPVCScrollingFastVelocityThreshold && self.isScrolling) { - _isScrollingFast = YES; - } else { - _isScrollingFast = NO; - } - _lastOffset = offset; -} - - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [super scrollViewDidEndDecelerating:scrollView]; - _isScrollingFast = NO; - [self loadImagesForVisibleRows]; NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; if (!selectedIndexPath) @@ -473,7 +453,7 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { #pragma mark - WPTableViewSublass methods - (NSString *)noResultsTitleText { - NSString *prompt; + NSString *prompt; NSString *endpoint = [ReaderPost currentEndpoint]; NSArray *endpoints = [ReaderPost readerEndpoints]; NSInteger idx = [endpoints indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { @@ -551,12 +531,8 @@ - (UITableViewCell *)newCell { ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { cell = [[ReaderPostTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; - [cell.reblogButton addTarget:self action:@selector(reblogAction:) forControlEvents:UIControlEventTouchUpInside]; - [cell.likeButton addTarget:self action:@selector(likeAction:) forControlEvents:UIControlEventTouchUpInside]; - [cell.followButton addTarget:self action:@selector(followAction:) forControlEvents:UIControlEventTouchUpInside]; - [cell.commentButton addTarget:self action:@selector(commentAction:) forControlEvents:UIControlEventTouchUpInside]; - [cell.tagButton addTarget:self action:@selector(tagAction:) forControlEvents:UIControlEventTouchUpInside]; } + return cell; } @@ -569,36 +545,53 @@ - (void)configureCell:(UITableViewCell *)aCell atIndexPath:(NSIndexPath *)indexP cell.accessoryType = UITableViewCellAccessoryNone; ReaderPost *post = (ReaderPost *)[self.resultsController objectAtIndexPath:indexPath]; + [cell configureCell:post]; [self setImageForPost:post forCell:cell indexPath:indexPath]; + + cell.postView.delegate = self; - CGSize imageSize = cell.avatarImageView.bounds.size; + CGSize imageSize = cell.postView.avatarImageView.bounds.size; UIImage *image = [post cachedAvatarWithSize:imageSize]; if (image) { - [cell setAvatar:image]; + [cell.postView setAvatar:image]; } else if (!self.tableView.isDragging && !self.tableView.isDecelerating) { [post fetchAvatarWithSize:imageSize success:^(UIImage *image) { if (cell == [self.tableView cellForRowAtIndexPath:indexPath]) { - [cell setAvatar:image]; + [cell.postView setAvatar:image]; } }]; } } +- (UIImage *)imageForURL:(NSURL *)imageURL size:(CGSize)imageSize { + if (!imageURL) + return nil; + + if (CGSizeEqualToSize(imageSize, CGSizeZero)) { + imageSize.width = self.tableView.bounds.size.width; + imageSize.height = round(imageSize.width * RPVCMaxImageHeightPercentage); + } + return [_featuredImageSource imageForURL:imageURL withSize:imageSize]; +} + - (void)setImageForPost:(ReaderPost *)post forCell:(ReaderPostTableViewCell *)cell indexPath:(NSIndexPath *)indexPath { NSURL *imageURL = post.featuredImageURL; + if (!imageURL) return; - - CGSize imageSize = cell.cellImageView.bounds.size; - if (CGSizeEqualToSize(imageSize, CGSizeZero)) { - imageSize.width = self.tableView.bounds.size.width; - imageSize.height = round(imageSize.width * RPTVCMaxImageHeightPercentage); + + // We know the width, but not the height; let the image loader figure that out + CGFloat imageWidth = self.tableView.frame.size.width; + if (IS_IPAD) { + imageWidth = WPTableViewFixedWidth; } - UIImage *image = [_featuredImageSource imageForURL:imageURL withSize:imageSize]; + CGSize imageSize = CGSizeMake(imageWidth, 0); + UIImage *image = [self imageForURL:imageURL size:imageSize]; + if (image) { - [cell setFeaturedImage:image]; - } else if (!_isScrollingFast) { + [cell.postView setFeaturedImage:image]; + } else { [_featuredImageSource fetchImageForURL:imageURL withSize:imageSize indexPath:indexPath isPrivate:post.isPrivate]; } } @@ -737,10 +730,18 @@ - (void)onSyncSuccess:(AFHTTPRequestOperation *)operation response:(id)responseO #pragma mark - #pragma mark TableView Methods +- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { + return [ReaderPostTableViewCell cellHeightForPost:[self.resultsController objectAtIndexPath:indexPath] withWidth:self.tableView.bounds.size.width]; +} + - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [ReaderPostTableViewCell cellHeightForPost:[self.resultsController objectAtIndexPath:indexPath] withWidth:self.tableView.bounds.size.width]; } +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return [[UIView alloc] initWithFrame:CGRectZero]; +} + - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { if (IS_IPHONE) return RPVCHeaderHeightPhone; @@ -770,27 +771,44 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [tableView deselectRowAtIndexPath:indexPath animated:YES]; } + // Pass the image forward ReaderPost *post = [self.resultsController.fetchedObjects objectAtIndex:indexPath.row]; - - ReaderPostDetailViewController *controller = [[ReaderPostDetailViewController alloc] initWithPost:post]; - [self.navigationController pushViewController:controller animated:YES]; + ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; + CGSize imageSize = cell.postView.cellImageView.image.size; + UIImage *image = [_featuredImageSource imageForURL:post.featuredImageURL withSize:imageSize]; + + self.detailController = [[ReaderPostDetailViewController alloc] initWithPost:post featuredImage:image]; + + [self.navigationController pushViewController:self.detailController animated:YES]; [WPMobileStats trackEventForWPCom:StatsEventReaderOpenedArticleDetails]; [WPMobileStats pingWPComStatsEndpoint:@"details_page"]; } -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)aCell forRowAtIndexPath:(NSIndexPath *)indexPath { - [super tableView:tableView willDisplayCell:aCell forRowAtIndexPath:indexPath]; - ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)aCell; - ReaderPost *post = (ReaderPost *)[self.resultsController objectAtIndexPath:indexPath]; - [self setImageForPost:post forCell:cell indexPath:indexPath]; +#pragma mark - NSFetchedResultsController overrides + +- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { + // Do nothing (prevent superclass from adjusting table view) +} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + [self.tableView reloadData]; + [self.noResultsView removeFromSuperview]; +} + +- (void)controller:(NSFetchedResultsController *)controller + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath *)indexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath *)newIndexPath { + // Do nothing (prevent superclass from adjusting table view) } #pragma mark - ReaderTopicsDelegate Methods -- (void)readerTopicChanged { +- (void)readerTopicDidChange { if (IS_IPAD){ [self dismissPopover]; } @@ -922,9 +940,14 @@ - (CGSize)tabBarSize { #pragma mark - WPTableImageSourceDelegate - (void)tableImageSource:(WPTableImageSource *)tableImageSource imageReady:(UIImage *)image forIndexPath:(NSIndexPath *)indexPath { - if (!_isScrollingFast) { - ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; - [cell setFeaturedImage:image]; + ReaderPostTableViewCell *cell = (ReaderPostTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; + [cell.postView setFeaturedImage:image]; + + ReaderPost *post = [self.resultsController objectAtIndexPath:indexPath]; + + // Update the detail view if it's open and applicable + if (post == self.detailController.post) { + [self.detailController updateFeaturedImage:image]; } } diff --git a/WordPress/Classes/ReaderReblogFormView.m b/WordPress/Classes/ReaderReblogFormView.m index 2e09fb62eb36..5d4eea09fab1 100644 --- a/WordPress/Classes/ReaderReblogFormView.m +++ b/WordPress/Classes/ReaderReblogFormView.m @@ -218,8 +218,18 @@ - (void)handleSendButtonTapped:(id)sender { } -- (void)handleBlogButtonTapped:(id)sender { - [ReaderUsersBlogsViewController presentAsModalWithDelegate:self]; +- (void)handleBlogButtonTapped:(id)sender { + ReaderUsersBlogsViewController *controller = [[ReaderUsersBlogsViewController alloc] init]; + controller.delegate = self; + + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; + navController.navigationBar.translucent = NO; + navController.modalPresentationStyle = UIModalPresentationFormSheet; + if (!IS_IPAD) { + // Avoid a weird issue on the iPad with cross dissolves when the keyboard is visible. + navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + } + [[[WordPressAppDelegate sharedWordPressApplicationDelegate].window rootViewController] presentViewController:navController animated:YES completion:nil]; } diff --git a/WordPress/Classes/ReaderTopicsViewController.h b/WordPress/Classes/ReaderTopicsViewController.h index 5de04d555ba1..960f14360db8 100644 --- a/WordPress/Classes/ReaderTopicsViewController.h +++ b/WordPress/Classes/ReaderTopicsViewController.h @@ -8,14 +8,6 @@ #import -@protocol ReaderTopicsDelegate - -- (void)readerTopicChanged; - -@end - @interface ReaderTopicsViewController : UITableViewController -@property (nonatomic, strong) iddelegate; - @end diff --git a/WordPress/Classes/ReaderTopicsViewController.m b/WordPress/Classes/ReaderTopicsViewController.m index 12a1fdf17cae..cc62f6d542f2 100644 --- a/WordPress/Classes/ReaderTopicsViewController.m +++ b/WordPress/Classes/ReaderTopicsViewController.m @@ -6,6 +6,7 @@ // Copyright (c) 2013 WordPress. All rights reserved. // +#import "ReaderPostsViewController.h" #import "ReaderTopicsViewController.h" #import "WordPressComApi.h" #import "ReaderPost.h" @@ -27,7 +28,6 @@ - (void)handleFriendFinderButtonTapped:(id)sender; @implementation ReaderTopicsViewController -@synthesize delegate; #pragma mark - LifeCycle Methods @@ -255,9 +255,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [[NSUserDefaults standardUserDefaults] synchronize]; if(![[dict objectForKey:@"endpoint"] isEqualToString:[_currentTopic objectForKey:@"endpoint"]]) { - if(self.delegate) { - [delegate readerTopicChanged]; - } + [[NSNotificationCenter defaultCenter] postNotificationName:ReaderTopicDidChangeNotification object:self]; } [self dismissViewControllerAnimated:YES completion:nil]; diff --git a/WordPress/Classes/ReaderUsersBlogsViewController.h b/WordPress/Classes/ReaderUsersBlogsViewController.h index 20d3c1c8c282..5f72d13fc01d 100644 --- a/WordPress/Classes/ReaderUsersBlogsViewController.h +++ b/WordPress/Classes/ReaderUsersBlogsViewController.h @@ -14,8 +14,6 @@ @property (nonatomic, weak) iddelegate; -+ (id)presentAsModalWithDelegate:(id)delegate; - @end @protocol ReaderUsersBlogsDelegate diff --git a/WordPress/Classes/ReaderUsersBlogsViewController.m b/WordPress/Classes/ReaderUsersBlogsViewController.m index 266817af6913..cf52b2994730 100644 --- a/WordPress/Classes/ReaderUsersBlogsViewController.m +++ b/WordPress/Classes/ReaderUsersBlogsViewController.m @@ -25,23 +25,6 @@ - (void)handleCloseButtonTapped:(id)sender; @implementation ReaderUsersBlogsViewController -+ (id)presentAsModalWithDelegate:(id)delegate { - ReaderUsersBlogsViewController *controller = [[ReaderUsersBlogsViewController alloc] init]; - controller.delegate = delegate; - controller.title = NSLocalizedString(@"My Blogs", @"Title of the list of the user's blogs as shown in the reader."); - - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; - navController.navigationBar.translucent = NO; - navController.modalPresentationStyle = UIModalPresentationFormSheet; - if (!IS_IPAD) { - // Avoid a weird issue on the iPad with cross dissolves when the keyboard is visible. - navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - } - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:navController animated:YES completion:nil]; - - return controller; -} - #pragma mark - Lifecycle Methods - (id)init { @@ -54,7 +37,8 @@ - (id)init { } - (void)viewDidLoad { - + self.title = NSLocalizedString(@"My Blogs", @"Title of the list of the user's blogs as shown in the reader."); + self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui-texture"]]; self.view.backgroundColor = [WPNUXUtility backgroundColor]; diff --git a/WordPress/Classes/WPImageViewController.h b/WordPress/Classes/WPImageViewController.h index ffcc134a8fc9..7e98db8bbbf3 100644 --- a/WordPress/Classes/WPImageViewController.h +++ b/WordPress/Classes/WPImageViewController.h @@ -10,10 +10,6 @@ @interface WPImageViewController : UIViewController -+ (id)presentAsModalWithImage:(UIImage *)image; -+ (id)presentAsModalWithURL:(NSURL *)url; -+ (id)presentAsModalWithImage:(UIImage *)image andURL:(NSURL *)url; - - (id)initWithImage:(UIImage *)image; - (id)initWithURL:(NSURL *)url; - (id)initWithImage:(UIImage *)image andURL:(NSURL *)url; diff --git a/WordPress/Classes/WPImageViewController.m b/WordPress/Classes/WPImageViewController.m index 932cc294b634..d7099a53844f 100644 --- a/WordPress/Classes/WPImageViewController.m +++ b/WordPress/Classes/WPImageViewController.m @@ -24,33 +24,6 @@ - (void)handleImageDoubleTapped:(UITapGestureRecognizer *)tgr; @implementation WPImageViewController -+ (id)presentAsModalWithImage:(UIImage *)image { - UIViewController *controller = [[self alloc] initWithImage:image]; - controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - controller.modalPresentationStyle = UIModalPresentationFullScreen; - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:controller animated:YES completion:nil]; - return controller; -} - - -+ (id)presentAsModalWithURL:(NSURL *)url { - UIViewController *controller = [[self alloc] initWithURL:url]; - controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - controller.modalPresentationStyle = UIModalPresentationFullScreen; - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:controller animated:YES completion:nil]; - return controller; -} - - -+ (id)presentAsModalWithImage:(UIImage *)image andURL:(NSURL *)url { - UIViewController *controller = [[self alloc] initWithImage:image andURL:url]; - controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - controller.modalPresentationStyle = UIModalPresentationFullScreen; - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:controller animated:YES completion:nil]; - return controller; -} - - #pragma mark - LifeCycle Methods - (id)initWithImage:(UIImage *)image { diff --git a/WordPress/Classes/WPTableImageSource.m b/WordPress/Classes/WPTableImageSource.m index b57885449b0b..144d69eac7bf 100644 --- a/WordPress/Classes/WPTableImageSource.m +++ b/WordPress/Classes/WPTableImageSource.m @@ -36,6 +36,10 @@ - (id)initWithMaxSize:(CGSize)size - (UIImage *)imageForURL:(NSURL *)url withSize:(CGSize)size { + // Force rounding and only cache based on width + size.width = ceilf(size.width); + size.height = 0; + UIImage *image = [self cachedImageForURL:url withSize:size]; if (image) { return image; @@ -180,11 +184,18 @@ - (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)size - (void)setCachedImage:(UIImage *)image forURL:(NSURL *)url withSize:(CGSize)size { + // Force rounding and only cache based on width + size.width = ceilf(size.width); + size.height = 0; + [_imageCache setObject:image forKey:[self cacheKeyForURL:url withSize:size]]; } - (UIImage *)cachedImageForURL:(NSURL *)url withSize:(CGSize)size { + size.width = ceilf(size.width); + size.height = 0; + return [_imageCache objectForKey:[self cacheKeyForURL:url withSize:size]]; } diff --git a/WordPress/Classes/WPTableViewCell.h b/WordPress/Classes/WPTableViewCell.h index 2a739331307b..a8c8195155d1 100644 --- a/WordPress/Classes/WPTableViewCell.h +++ b/WordPress/Classes/WPTableViewCell.h @@ -8,7 +8,7 @@ #import -extern CGFloat const WPTableViewCellMarginPercentage; +extern CGFloat const WPTableViewFixedWidth; @interface WPTableViewCell : UITableViewCell diff --git a/WordPress/Classes/WPTableViewCell.m b/WordPress/Classes/WPTableViewCell.m index e2ac1b14331d..43cb39c419dc 100644 --- a/WordPress/Classes/WPTableViewCell.m +++ b/WordPress/Classes/WPTableViewCell.m @@ -8,18 +8,28 @@ #import "WPTableViewCell.h" -CGFloat const WPTableViewCellMarginPercentage = 0.2; +CGFloat const WPTableViewFixedWidth = 500; @implementation WPTableViewCell - (void)setFrame:(CGRect)frame { // On iPad, add a margin around tables if (IS_IPAD) { - CGFloat inset = ceilf(self.superview.frame.size.width * WPTableViewCellMarginPercentage); - frame.origin.x = inset; - frame.size.width = self.superview.frame.size.width - 2 * inset; + frame.origin.x = (self.superview.frame.size.width - WPTableViewFixedWidth) / 2; + frame.size.width = WPTableViewFixedWidth; } [super setFrame:frame]; } +- (void)layoutSubviews { + [super layoutSubviews]; + + // Need to set the origin again on iPad (for margins) + if (IS_IPAD) { + CGRect frame = self.frame; + frame.origin.x = (self.superview.frame.size.width - WPTableViewFixedWidth) / 2; + self.frame = frame; + } +} + @end diff --git a/WordPress/Classes/WPWebVideoViewController.h b/WordPress/Classes/WPWebVideoViewController.h index 81c3aaf1eecd..da03fd8831ce 100644 --- a/WordPress/Classes/WPWebVideoViewController.h +++ b/WordPress/Classes/WPWebVideoViewController.h @@ -10,9 +10,6 @@ @interface WPWebVideoViewController : UIViewController -+ (id)presentAsModalWithURL:(NSURL *)url; -+ (id)presentAsModalWithHTML:(NSString *)html; - - (id)initWithURL:(NSURL *)url; - (id)initWithHTML:(NSString *)html; diff --git a/WordPress/Classes/WPWebVideoViewController.m b/WordPress/Classes/WPWebVideoViewController.m index 93a5971480b6..5d5a8b6d4c27 100644 --- a/WordPress/Classes/WPWebVideoViewController.m +++ b/WordPress/Classes/WPWebVideoViewController.m @@ -23,27 +23,6 @@ - (void)handleCloseTapped:(id)sender; @implementation WPWebVideoViewController -+ (id)presentAsModalWithURL:(NSURL *)url { - UIViewController *controller = [[self alloc] initWithURL:url]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; - navController.navigationBar.translucent = NO; - navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - navController.modalPresentationStyle = UIModalPresentationFullScreen; - - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:navController animated:YES completion:nil]; - return controller; -} - - -+ (id)presentAsModalWithHTML:(NSString *)html { - UIViewController *controller = [[self alloc] initWithHTML:html]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; - navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - navController.modalPresentationStyle = UIModalPresentationFullScreen; - [[[WordPressAppDelegate sharedWordPressApplicationDelegate] navigationController] presentViewController:navController animated:YES completion:nil]; - return controller; -} - #pragma mark - LifeCycle Methods diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index f2df8c38a455..39d8433b0c69 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -296,6 +296,7 @@ 46F8714F1838C41600BC149B /* NSDate+StringFormatting.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F8714E1838C41600BC149B /* NSDate+StringFormatting.m */; }; 46F871521839528C00BC149B /* reader-postaction-time.png in Resources */ = {isa = PBXBuildFile; fileRef = 46F871501839528C00BC149B /* reader-postaction-time.png */; }; 46F871531839528C00BC149B /* reader-postaction-time@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 46F871511839528C00BC149B /* reader-postaction-time@2x.png */; }; + 46F8D7FB183C293200E10A38 /* ReaderPostView.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F8D7FA183C293200E10A38 /* ReaderPostView.m */; }; 46FE8276184FD8A200535844 /* WordPressComOAuthClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E1634518183B733B005E967F /* WordPressComOAuthClient.m */; }; 5D0077A7182AE9DF00F865DB /* ReaderMediaQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D0077A6182AE9DF00F865DB /* ReaderMediaQueue.m */; }; 5D119DA3176FBE040073D83A /* UIImageView+AFNetworkingExtra.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D119DA2176FBE040073D83A /* UIImageView+AFNetworkingExtra.m */; }; @@ -361,7 +362,6 @@ 5D42A3F7175E75EE005CFF05 /* ReaderCommentTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3E4175E75EE005CFF05 /* ReaderCommentTableViewCell.m */; }; 5D42A3F8175E75EE005CFF05 /* ReaderImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3E6175E75EE005CFF05 /* ReaderImageView.m */; }; 5D42A3F9175E75EE005CFF05 /* ReaderMediaView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3E8175E75EE005CFF05 /* ReaderMediaView.m */; }; - 5D42A3FA175E75EE005CFF05 /* ReaderPostDetailView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3EA175E75EE005CFF05 /* ReaderPostDetailView.m */; }; 5D42A3FB175E75EE005CFF05 /* ReaderPostDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3EC175E75EE005CFF05 /* ReaderPostDetailViewController.m */; }; 5D42A3FC175E75EE005CFF05 /* ReaderPostsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3EE175E75EE005CFF05 /* ReaderPostsViewController.m */; }; 5D42A3FD175E75EE005CFF05 /* ReaderPostTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3F0175E75EE005CFF05 /* ReaderPostTableViewCell.m */; }; @@ -1299,6 +1299,9 @@ 46F8714E1838C41600BC149B /* NSDate+StringFormatting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+StringFormatting.m"; sourceTree = ""; }; 46F871501839528C00BC149B /* reader-postaction-time.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "reader-postaction-time.png"; path = "Resources/Images/reader-postaction-time.png"; sourceTree = ""; }; 46F871511839528C00BC149B /* reader-postaction-time@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "reader-postaction-time@2x.png"; path = "Resources/Images/reader-postaction-time@2x.png"; sourceTree = ""; }; + 46F871551839802100BC149B /* WordPress 13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 13.xcdatamodel"; sourceTree = ""; }; + 46F8D7F9183C293200E10A38 /* ReaderPostView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderPostView.h; sourceTree = ""; }; + 46F8D7FA183C293200E10A38 /* ReaderPostView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderPostView.m; sourceTree = ""; }; 5D0077A5182AE9DF00F865DB /* ReaderMediaQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderMediaQueue.h; sourceTree = ""; }; 5D0077A6182AE9DF00F865DB /* ReaderMediaQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderMediaQueue.m; sourceTree = ""; }; 5D119DA1176FBE040073D83A /* UIImageView+AFNetworkingExtra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImageView+AFNetworkingExtra.h"; sourceTree = ""; }; @@ -1380,8 +1383,6 @@ 5D42A3E6175E75EE005CFF05 /* ReaderImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderImageView.m; sourceTree = ""; }; 5D42A3E7175E75EE005CFF05 /* ReaderMediaView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderMediaView.h; sourceTree = ""; }; 5D42A3E8175E75EE005CFF05 /* ReaderMediaView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderMediaView.m; sourceTree = ""; }; - 5D42A3E9175E75EE005CFF05 /* ReaderPostDetailView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderPostDetailView.h; sourceTree = ""; }; - 5D42A3EA175E75EE005CFF05 /* ReaderPostDetailView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderPostDetailView.m; sourceTree = ""; }; 5D42A3EB175E75EE005CFF05 /* ReaderPostDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderPostDetailViewController.h; sourceTree = ""; }; 5D42A3EC175E75EE005CFF05 /* ReaderPostDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderPostDetailViewController.m; sourceTree = ""; }; 5D42A3ED175E75EE005CFF05 /* ReaderPostsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderPostsViewController.h; sourceTree = ""; }; @@ -3756,10 +3757,10 @@ 5D42A3F4175E75EE005CFF05 /* ReaderTopicsViewController.m */, 5D42A3ED175E75EE005CFF05 /* ReaderPostsViewController.h */, 5D42A3EE175E75EE005CFF05 /* ReaderPostsViewController.m */, - 5D42A3E9175E75EE005CFF05 /* ReaderPostDetailView.h */, - 5D42A3EA175E75EE005CFF05 /* ReaderPostDetailView.m */, 5D42A3EB175E75EE005CFF05 /* ReaderPostDetailViewController.h */, 5D42A3EC175E75EE005CFF05 /* ReaderPostDetailViewController.m */, + 46F8D7F9183C293200E10A38 /* ReaderPostView.h */, + 46F8D7FA183C293200E10A38 /* ReaderPostView.m */, 5D42A3F1175E75EE005CFF05 /* ReaderTableViewCell.h */, 5D42A3F2175E75EE005CFF05 /* ReaderTableViewCell.m */, 5D42A3EF175E75EE005CFF05 /* ReaderPostTableViewCell.h */, @@ -5116,7 +5117,7 @@ 5D0077A7182AE9DF00F865DB /* ReaderMediaQueue.m in Sources */, 462F4E0B18369F0B0028D2F8 /* BlogListViewController.m in Sources */, 5D42A3F9175E75EE005CFF05 /* ReaderMediaView.m in Sources */, - 5D42A3FA175E75EE005CFF05 /* ReaderPostDetailView.m in Sources */, + 46F8D7FB183C293200E10A38 /* ReaderPostView.m in Sources */, 5D42A3FB175E75EE005CFF05 /* ReaderPostDetailViewController.m in Sources */, 5D42A3FC175E75EE005CFF05 /* ReaderPostsViewController.m in Sources */, 857F55B517CEA01A00E154E1 /* WPKeyboardToolbarBase.m in Sources */,