2010-06-21 17:17:26 -04:00
//
// FeedDetailViewController . m
// NewsBlur
//
// Created by Samuel Clay on 6 / 20 / 10.
// Copyright 2010 NewsBlur . All rights reserved .
//
2011-10-26 08:40:31 -07:00
# import < QuartzCore / QuartzCore . h >
2010-06-21 17:17:26 -04:00
# import "FeedDetailViewController.h"
# import "NewsBlurAppDelegate.h"
2012-07-25 17:29:29 -07:00
# import "NBContainerViewController.h"
2010-07-15 00:44:38 -04:00
# import "FeedDetailTableCell.h"
2011-07-24 22:23:38 -07:00
# import "ASIFormDataRequest.h"
2012-07-12 22:05:23 -07:00
# import "UserProfileViewController.h"
2012-08-02 18:00:48 -07:00
# import "StoryDetailViewController.h"
2012-11-06 17:26:08 -08:00
# import "StoryPageControl.h"
2011-08-08 09:58:15 -07:00
# import "NSString+HTML.h"
2011-10-17 09:28:15 -07:00
# import "MBProgressHUD.h"
2011-09-05 22:06:31 -07:00
# import "Base64.h"
2010-06-24 00:22:26 -04:00
# import "JSON.h"
2011-10-26 08:40:31 -07:00
# import "StringHelper.h"
2011-10-17 09:37:16 -07:00
# import "Utilities.h"
2014-11-04 12:15:49 -08:00
# import "WYPopoverController.h"
2013-02-27 17:22:49 -08:00
# import "UIBarButtonItem+Image.h"
2013-04-22 17:15:50 -07:00
# import "FeedDetailMenuViewController.h"
2013-06-07 02:47:43 -04:00
# import "NBNotifier.h"
2013-06-12 19:21:56 -07:00
# import "NBLoadingCell.h"
2013-06-14 19:21:30 -07:00
# import "FMDatabase.h"
2013-09-24 17:18:20 -07:00
# import "NBBarButtonItem.h"
2014-02-12 15:48:16 -08:00
# import "UIImage+Resize.h"
# import "TMCache.h"
# import "AFImageRequestOperation.h"
# import "DashboardViewController.h"
2014-02-12 20:09:37 -08:00
# import "StoriesCollection.h"
2012-07-25 17:29:29 -07:00
2014-10-01 14:54:06 -07:00
# define kTableViewRowHeight 46 ;
# define kTableViewRiverRowHeight 68 ;
# define kTableViewShortRowDifference 17 ;
2011-11-05 16:25:04 -07:00
# define kMarkReadActionSheet 1 ;
# define kSettingsActionSheet 2 ;
2010-06-21 17:17:26 -04:00
2012-08-08 19:31:33 -07:00
@ interface FeedDetailViewController ( )
@ property ( nonatomic ) UIActionSheet * actionSheet_ ; // add this line
@ end
2010-06-21 17:17:26 -04:00
@ implementation FeedDetailViewController
2012-07-12 22:05:23 -07:00
@ synthesize popoverController ;
2013-02-27 17:22:49 -08:00
@ synthesize storyTitlesTable , feedMarkReadButton ;
@ synthesize settingsBarButton ;
2013-02-27 17:39:54 -08:00
@ synthesize separatorBarButton ;
2013-04-15 10:29:39 -07:00
@ synthesize titleImageBarButton ;
2013-10-11 17:46:09 -07:00
@ synthesize spacerBarButton , spacer2BarButton ;
2010-06-21 17:17:26 -04:00
@ synthesize appDelegate ;
2011-07-20 22:21:11 -07:00
@ synthesize pageFetching ;
2011-07-24 16:52:24 -07:00
@ synthesize pageFinished ;
2012-08-08 19:31:33 -07:00
@ synthesize actionSheet_ ;
2013-02-21 17:57:32 -08:00
@ synthesize finishedAnimatingIn ;
2013-06-10 00:29:03 -07:00
@ synthesize notifier ;
2014-11-17 16:07:58 -08:00
@ synthesize searchBar ;
2014-02-19 18:59:14 -08:00
@ synthesize isOnline ;
@ synthesize isShowingFetching ;
2014-02-10 19:21:53 -08:00
@ synthesize isDashboardModule ;
2014-02-12 20:09:37 -08:00
@ synthesize storiesCollection ;
2014-02-13 17:18:29 -08:00
@ synthesize showContentPreview ;
@ synthesize showImagePreview ;
2014-10-01 14:23:57 -07:00
@ synthesize invalidateFontCache ;
2014-02-12 20:09:37 -08:00
2010-06-21 17:17:26 -04:00
- ( id ) initWithNibName : ( NSString * ) nibNameOrNil bundle : ( NSBundle * ) nibBundleOrNil {
2011-03-09 18:23:55 -05:00
if ( ( self = [ super initWithNibName : nibNameOrNil bundle : nibBundleOrNil ] ) ) {
2010-06-21 17:17:26 -04:00
}
return self ;
}
2012-07-18 17:23:57 -07:00
2011-10-05 10:07:26 -07:00
- ( void ) viewDidLoad {
2012-08-10 18:10:07 -07:00
[ super viewDidLoad ] ;
2014-02-10 19:21:53 -08:00
2013-10-17 18:56:14 -07:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( preferredContentSizeChanged : )
name : UIContentSizeCategoryDidChangeNotification
object : nil ] ;
2014-11-04 12:15:49 -08:00
popoverClass = [ WYPopoverController class ] ;
2012-08-10 18:10:07 -07:00
self . storyTitlesTable . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
2013-03-04 17:15:50 -08:00
self . storyTitlesTable . separatorColor = UIColorFromRGB ( 0 xE9E8E4 ) ;
2013-02-27 17:22:49 -08:00
spacerBarButton = [ [ UIBarButtonItem alloc ]
initWithBarButtonSystemItem : UIBarButtonSystemItemFixedSpace target : nil action : nil ] ;
2013-10-11 17:46:09 -07:00
spacerBarButton . width = 0 ;
2013-02-27 17:39:54 -08:00
spacer2BarButton = [ [ UIBarButtonItem alloc ]
2013-10-11 17:46:09 -07:00
initWithBarButtonSystemItem : UIBarButtonSystemItemFixedSpace target : nil action : nil ] ;
spacer2BarButton . width = 0 ;
2013-02-27 17:22:49 -08:00
2014-11-17 16:07:58 -08:00
self . searchBar = [ [ UISearchBar alloc ]
initWithFrame : CGRectMake ( 0 , 0 , CGRectGetWidth ( self . storyTitlesTable . frame ) , 44. ) ] ;
self . searchBar . delegate = self ;
[ self . searchBar setReturnKeyType : UIReturnKeySearch ] ;
[ self . searchBar setBackgroundColor : UIColorFromRGB ( 0 xDCDFD6 ) ] ;
[ self . searchBar setTintColor : [ UIColor whiteColor ] ] ;
[ self . searchBar setSearchBarStyle : UISearchBarStyleMinimal ] ;
[ self . searchBar setAutocapitalizationType : UITextAutocapitalizationTypeNone ] ;
self . storyTitlesTable . tableHeaderView = self . searchBar ;
2014-11-17 16:54:54 -08:00
self . storyTitlesTable . keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag ;
2013-02-27 17:22:49 -08:00
UIImage * separatorImage = [ UIImage imageNamed : @ "bar-separator.png" ] ;
separatorBarButton = [ UIBarButtonItem barItemWithImage : separatorImage target : nil action : nil ] ;
[ separatorBarButton setEnabled : NO ] ;
UIImage * settingsImage = [ UIImage imageNamed : @ "nav_icn_settings.png" ] ;
settingsBarButton = [ UIBarButtonItem barItemWithImage : settingsImage target : self action : @ selector ( doOpenSettingsActionSheet : ) ] ;
2013-10-11 17:46:09 -07:00
2013-02-27 17:22:49 -08:00
UIImage * markreadImage = [ UIImage imageNamed : @ "markread.png" ] ;
feedMarkReadButton = [ UIBarButtonItem barItemWithImage : markreadImage target : self action : @ selector ( doOpenMarkReadActionSheet : ) ] ;
2013-04-15 10:29:39 -07:00
titleImageBarButton = [ UIBarButtonItem alloc ] ;
2013-06-13 17:56:58 -07:00
2013-10-09 14:54:49 -07:00
UILongPressGestureRecognizer * longpress = [ [ UILongPressGestureRecognizer alloc ]
initWithTarget : self action : @ selector ( handleLongPress : ) ] ;
longpress . minimumPressDuration = 1.0 ;
longpress . delegate = self ;
[ self . storyTitlesTable addGestureRecognizer : longpress ] ;
2014-03-03 12:52:26 -08:00
UITapGestureRecognizer * doubleTapGesture = [ [ UITapGestureRecognizer alloc ]
initWithTarget : self action : nil ] ;
doubleTapGesture . numberOfTapsRequired = 2 ;
[ self . storyTitlesTable addGestureRecognizer : doubleTapGesture ] ;
doubleTapGesture . delegate = self ;
2013-06-13 17:56:58 -07:00
self . notifier = [ [ NBNotifier alloc ] initWithTitle : @ "Fetching stories..." inView : self . view ] ;
2013-06-19 20:26:04 -07:00
[ self . view addSubview : self . notifier ] ;
2011-08-18 09:56:52 -07:00
}
2014-03-03 12:52:26 -08:00
- ( BOOL ) gestureRecognizer : ( UIGestureRecognizer * ) gestureRecognizer shouldReceiveTouch : ( UITouch * ) touch {
// NSLog ( @ "Gesture double tap: %ld - %ld" , touch . tapCount , gestureRecognizer . state ) ;
inDoubleTap = ( touch . tapCount = = 2 ) ;
return YES ;
}
- ( BOOL ) gestureRecognizer : ( UIGestureRecognizer * ) gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer : ( UIGestureRecognizer * ) otherGestureRecognizer {
// NSLog ( @ "Gesture should multiple? %ld (%ld) - %d" , gestureRecognizer . state , UIGestureRecognizerStateEnded , inDoubleTap ) ;
if ( gestureRecognizer . state = = UIGestureRecognizerStateEnded && inDoubleTap ) {
CGPoint p = [ gestureRecognizer locationInView : self . storyTitlesTable ] ;
NSIndexPath * indexPath = [ self . storyTitlesTable indexPathForRowAtPoint : p ] ;
NSDictionary * story = [ self getStoryAtRow : indexPath . row ] ;
2014-03-04 10:21:48 -08:00
if ( ! story ) return YES ;
2014-03-03 15:47:24 -08:00
NSUserDefaults * preferences = [ NSUserDefaults standardUserDefaults ] ;
BOOL openOriginal = NO ;
BOOL showText = NO ;
2014-03-03 16:21:26 -08:00
BOOL markUnread = NO ;
BOOL saveStory = NO ;
2014-03-03 15:47:24 -08:00
if ( gestureRecognizer . numberOfTouches = = 2 ) {
NSString * twoFingerTap = [ preferences stringForKey : @ "two_finger_double_tap" ] ;
if ( [ twoFingerTap isEqualToString : @ "open_original_story" ] ) {
openOriginal = YES ;
} else if ( [ twoFingerTap isEqualToString : @ "show_original_text" ] ) {
showText = YES ;
2014-03-03 16:21:26 -08:00
} else if ( [ twoFingerTap isEqualToString : @ "mark_unread" ] ) {
markUnread = YES ;
} else if ( [ twoFingerTap isEqualToString : @ "save_story" ] ) {
saveStory = YES ;
2014-03-03 15:47:24 -08:00
}
} else {
NSString * doubleTap = [ preferences stringForKey : @ "double_tap_story" ] ;
if ( [ doubleTap isEqualToString : @ "open_original_story" ] ) {
openOriginal = YES ;
} else if ( [ doubleTap isEqualToString : @ "show_original_text" ] ) {
showText = YES ;
2014-03-03 16:21:26 -08:00
} else if ( [ doubleTap isEqualToString : @ "mark_unread" ] ) {
markUnread = YES ;
} else if ( [ doubleTap isEqualToString : @ "save_story" ] ) {
saveStory = YES ;
2014-03-03 15:47:24 -08:00
}
}
if ( openOriginal ) {
[ appDelegate
showOriginalStory : [ NSURL URLWithString : [ story objectForKey : @ "story_permalink" ] ] ] ;
} else if ( showText ) {
[ appDelegate . storyDetailViewController fetchTextView ] ;
2014-03-03 16:21:26 -08:00
} else if ( markUnread ) {
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStoryUnread : story ] ;
2014-03-05 15:20:31 -08:00
[ self reloadData ] ;
2014-03-03 16:21:26 -08:00
} else if ( saveStory ) {
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStorySaved : story ] ;
2014-03-05 15:20:31 -08:00
[ self reloadData ] ;
2014-03-03 15:47:24 -08:00
}
2014-03-03 12:52:26 -08:00
inDoubleTap = NO ;
}
return YES ;
}
2014-11-17 16:07:58 -08:00
- ( void ) searchBarTextDidBeginEditing : ( UISearchBar * ) searchBar {
[ self . searchBar setShowsCancelButton : YES animated : YES ] ;
}
- ( void ) searchBarTextDidEndEditing : ( UISearchBar * ) searchBar {
if ( [ self . searchBar . text length ] ) {
[ self . searchBar setShowsCancelButton : YES animated : YES ] ;
} else {
[ self . searchBar setShowsCancelButton : NO animated : YES ] ;
}
2014-12-02 18:36:38 -08:00
[ self . searchBar resignFirstResponder ] ;
2014-11-17 16:07:58 -08:00
}
- ( void ) searchBarCancelButtonClicked : ( UISearchBar * ) searchBar {
[ self . searchBar setText : @ "" ] ;
[ self . searchBar resignFirstResponder ] ;
2014-12-02 18:36:38 -08:00
storiesCollection . inSearch = NO ;
storiesCollection . searchQuery = nil ;
2014-11-17 16:07:58 -08:00
[ self reloadStories ] ;
}
- ( void ) searchBarSearchButtonClicked : ( UISearchBar * ) theSearchBar {
[ self . searchBar resignFirstResponder ] ;
}
2014-12-02 18:36:38 -08:00
- ( BOOL ) disablesAutomaticKeyboardDismissal {
return NO ;
}
2014-11-17 16:07:58 -08:00
- ( void ) searchBar : ( UISearchBar * ) searchBar textDidChange : ( NSString * ) searchText {
if ( [ searchText length ] ) {
2014-12-02 18:36:38 -08:00
storiesCollection . inSearch = YES ;
storiesCollection . searchQuery = searchText ;
2014-11-17 16:07:58 -08:00
[ self reloadStories ] ;
} else {
2014-12-02 18:36:38 -08:00
storiesCollection . inSearch = NO ;
storiesCollection . searchQuery = nil ;
2014-11-17 16:07:58 -08:00
[ self reloadStories ] ;
}
}
2013-10-17 18:56:14 -07:00
- ( void ) preferredContentSizeChanged : ( NSNotification * ) aNotification {
2014-10-01 14:23:57 -07:00
appDelegate . fontDescriptorTitleSize = nil ;
2013-10-17 18:56:14 -07:00
[ self . storyTitlesTable reloadData ] ;
}
2014-02-27 17:51:56 -08:00
- ( void ) reloadData {
2014-03-10 12:51:05 -07:00
NSUserDefaults * userPreferences = [ NSUserDefaults standardUserDefaults ] ;
self . showContentPreview = [ userPreferences boolForKey : @ "story_list_preview_description" ] ;
self . showImagePreview = [ userPreferences boolForKey : @ "story_list_preview_images" ] ;
2014-10-01 14:23:57 -07:00
appDelegate . fontDescriptorTitleSize = nil ;
2014-02-27 17:51:56 -08:00
[ self . storyTitlesTable reloadData ] ;
}
2012-06-08 10:37:51 -07:00
- ( BOOL ) shouldAutorotateToInterfaceOrientation : ( UIInterfaceOrientation ) interfaceOrientation {
2012-06-08 19:21:10 -07:00
return YES ;
2012-06-08 10:37:51 -07:00
}
2012-06-29 23:25:56 -07:00
2012-07-09 21:26:53 -07:00
- ( void ) willAnimateRotationToInterfaceOrientation : ( UIInterfaceOrientation ) toInterfaceOrientation
duration : ( NSTimeInterval ) duration {
2012-08-02 18:00:48 -07:00
[ self setUserAvatarLayout : toInterfaceOrientation ] ;
2014-02-20 15:37:21 -08:00
[ self . notifier setNeedsLayout ] ;
2012-06-29 23:25:56 -07:00
}
2012-07-18 17:23:57 -07:00
- ( void ) didRotateFromInterfaceOrientation : ( UIInterfaceOrientation ) fromInterfaceOrientation {
[ self checkScroll ] ;
2014-02-13 17:18:29 -08:00
NSLog ( @ "Feed detail did re-orient." ) ;
2012-07-18 17:23:57 -07:00
}
2012-07-15 18:23:08 -07:00
- ( void ) viewWillAppear : ( BOOL ) animated {
2014-09-18 11:25:51 -07:00
[ super viewWillAppear : animated ] ;
2014-02-19 18:59:14 -08:00
self . appDelegate = ( NewsBlurAppDelegate * ) [ [ UIApplication sharedApplication ] delegate ] ;
2014-09-26 18:35:40 -07:00
2012-08-02 18:00:48 -07:00
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
[ self setUserAvatarLayout : orientation ] ;
2013-02-21 17:57:32 -08:00
self . finishedAnimatingIn = NO ;
2013-10-11 17:46:09 -07:00
[ MBProgressHUD hideHUDForView : self . view animated : NO ] ;
2013-06-11 23:00:00 -07:00
2014-02-13 17:18:29 -08:00
NSUserDefaults * userPreferences = [ NSUserDefaults standardUserDefaults ] ;
self . showContentPreview = [ userPreferences boolForKey : @ "story_list_preview_description" ] ;
self . showImagePreview = [ userPreferences boolForKey : @ "story_list_preview_images" ] ;
2012-07-12 22:05:23 -07:00
// set right avatar title image
2013-10-11 17:46:09 -07:00
spacerBarButton . width = 0 ;
spacer2BarButton . width = 0 ;
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
spacerBarButton . width = -6 ;
spacer2BarButton . width = 10 ;
}
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isSocialView ) {
2013-10-11 17:46:09 -07:00
spacerBarButton . width = -6 ;
2014-02-12 20:09:37 -08:00
NSString * feedIdStr = [ NSString stringWithFormat : @ "%@" , [ storiesCollection . activeFeed objectForKey : @ "id" ] ] ;
2014-02-24 18:56:51 -08:00
UIImage * titleImage = [ appDelegate getFavicon : feedIdStr isSocial : YES ] ;
2013-10-11 17:46:09 -07:00
titleImage = [ Utilities roundCorneredImage : titleImage radius : 6 ] ;
[ ( ( UIButton * ) titleImageBarButton . customView ) . imageView removeFromSuperview ] ;
titleImageBarButton = [ UIBarButtonItem barItemWithImage : titleImage
target : self
action : @ selector ( showUserProfile ) ] ;
titleImageBarButton . customView . frame = CGRectMake ( 0 , 0 , 32 , 32 ) ;
self . navigationItem . rightBarButtonItems = [ NSArray arrayWithObjects :
spacerBarButton ,
titleImageBarButton ,
spacer2BarButton ,
separatorBarButton ,
feedMarkReadButton , nil ] ;
2012-07-12 22:05:23 -07:00
} else {
2013-10-11 17:46:09 -07:00
self . navigationItem . rightBarButtonItems = [ NSArray arrayWithObjects :
spacerBarButton ,
settingsBarButton ,
spacer2BarButton ,
separatorBarButton ,
feedMarkReadButton ,
nil ] ;
2012-07-12 22:05:23 -07:00
}
2011-07-24 21:47:58 -07:00
2013-10-11 17:46:09 -07:00
// set center title
2013-10-11 18:07:30 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPhone &&
! self . navigationItem . titleView ) {
2014-02-12 20:09:37 -08:00
self . navigationItem . titleView = [ appDelegate makeFeedTitle : storiesCollection . activeFeed ] ;
2013-10-11 17:46:09 -07:00
}
2013-10-03 13:54:15 -07:00
2014-02-20 14:52:27 -08:00
if ( [ storiesCollection . activeFeedStories count ] ) {
2013-09-26 19:26:10 -07:00
[ self . storyTitlesTable reloadData ] ;
2011-07-24 21:47:58 -07:00
}
2011-10-30 18:53:10 -07:00
2014-03-21 16:23:51 -07:00
appDelegate . originalStoryCount = ( int ) [ appDelegate unreadCount ] ;
2013-04-26 16:41:25 -07:00
2014-02-12 20:09:37 -08:00
if ( ( storiesCollection . isSocialRiverView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isSocialView ) ) {
2013-02-27 17:22:49 -08:00
settingsBarButton . enabled = NO ;
2011-12-05 10:42:25 -08:00
} else {
2013-02-27 17:22:49 -08:00
settingsBarButton . enabled = YES ;
2012-07-16 19:45:14 -07:00
}
2012-08-08 19:31:33 -07:00
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isSocialRiverView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isSavedView ||
storiesCollection . isReadView ) {
2012-08-08 19:31:33 -07:00
feedMarkReadButton . enabled = NO ;
} else {
feedMarkReadButton . enabled = YES ;
}
2012-08-07 09:57:21 -07:00
2012-08-06 15:46:05 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPhone ) {
2014-02-20 14:52:27 -08:00
[ self fadeSelectedCell ] ;
2012-08-06 15:46:05 -07:00
}
2013-06-19 20:26:04 -07:00
[ self . notifier setNeedsLayout ] ;
2013-09-23 15:07:25 -07:00
[ appDelegate hideShareView : YES ] ;
2013-12-11 18:22:31 -08:00
2014-02-10 19:21:53 -08:00
if ( ! isDashboardModule &&
UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad &&
2014-02-27 17:58:02 -08:00
( appDelegate . masterContainerViewController . storyTitlesOnLeft ||
! UIInterfaceOrientationIsPortrait ( orientation ) ) &&
2014-01-16 17:15:29 -08:00
! self . isMovingFromParentViewController &&
! appDelegate . masterContainerViewController . interactiveOriginalTransition ) {
2013-12-11 18:22:31 -08:00
[ appDelegate . masterContainerViewController transitionToFeedDetail : NO ] ;
}
2014-11-17 16:33:36 -08:00
2014-12-02 18:36:38 -08:00
if ( ! storiesCollection . inSearch && storiesCollection . feedPage = = 1 ) {
2014-11-17 16:07:58 -08:00
[ self . storyTitlesTable setContentOffset : CGPointMake ( 0 , CGRectGetHeight ( self . searchBar . frame ) ) ] ;
}
2014-12-02 18:36:38 -08:00
if ( storiesCollection . inSearch && storiesCollection . searchQuery ) {
[ self . searchBar setText : storiesCollection . searchQuery ] ;
} else {
[ self . searchBar setText : @ "" ] ;
}
2014-11-17 16:54:54 -08:00
if ( [ self . searchBar . text length ] ) {
[ self . searchBar setShowsCancelButton : YES animated : YES ] ;
} else {
[ self . searchBar setShowsCancelButton : NO animated : YES ] ;
}
2014-02-24 18:35:35 -08:00
2015-03-10 18:58:23 -07:00
appDelegate . inTextView = [ appDelegate . storiesCollection . activeStoryView isEqualToString : @ "text" ] ;
2014-12-15 15:24:47 -08:00
// [ self testForTryFeed ] ;
2010-06-21 17:17:26 -04:00
}
2012-08-07 09:57:21 -07:00
- ( void ) viewDidAppear : ( BOOL ) animated {
2013-03-01 15:48:18 -08:00
[ super viewDidAppear : animated ] ;
2012-11-06 17:26:08 -08:00
if ( appDelegate . inStoryDetail && UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPhone ) {
2012-08-07 09:57:21 -07:00
appDelegate . inStoryDetail = NO ;
2015-03-10 18:58:23 -07:00
// [ appDelegate . storyPageControl resetPages ] ;
2012-08-13 23:35:11 -07:00
[ self checkScroll ] ;
2012-08-07 09:57:21 -07:00
}
2013-02-21 17:57:32 -08:00
2014-10-01 14:23:57 -07:00
if ( invalidateFontCache ) {
invalidateFontCache = NO ;
[ self reloadData ] ;
}
2013-02-21 17:57:32 -08:00
self . finishedAnimatingIn = YES ;
2014-03-04 10:21:48 -08:00
if ( [ storiesCollection . activeFeedStories count ] ||
self . isDashboardModule ) {
2014-02-19 18:59:14 -08:00
[ self . storyTitlesTable reloadData ] ;
}
2014-09-22 12:35:38 -07:00
2014-02-21 16:18:40 -08:00
[ self . notifier setNeedsLayout ] ;
2012-08-07 09:57:21 -07:00
}
2014-11-17 16:07:58 -08:00
- ( void ) viewWillDisappear : ( BOOL ) animated {
[ super viewWillDisappear : animated ] ;
[ self . searchBar resignFirstResponder ] ;
}
2013-09-25 10:47:35 -07:00
- ( void ) viewDidDisappear : ( BOOL ) animated {
2014-09-18 11:25:51 -07:00
[ super viewDidDisappear : animated ] ;
2014-11-17 16:20:29 -08:00
[ self . searchBar resignFirstResponder ] ;
2012-07-12 22:05:23 -07:00
[ self . popoverController dismissPopoverAnimated : YES ] ;
2012-10-12 13:58:26 -04:00
self . popoverController = nil ;
2014-02-27 17:58:02 -08:00
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
2014-12-15 15:24:47 -08:00
if ( self . isMovingToParentViewController ) {
appDelegate . inFindingStoryMode = NO ;
appDelegate . tryFeedStoryId = nil ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
}
2013-12-10 18:14:32 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad &&
2013-12-12 15:35:22 -08:00
self . isMovingToParentViewController &&
2014-02-27 17:58:02 -08:00
( appDelegate . masterContainerViewController . storyTitlesOnLeft ||
! UIInterfaceOrientationIsPortrait ( orientation ) ) ) {
2013-12-11 18:22:31 -08:00
[ appDelegate . masterContainerViewController transitionFromFeedDetail : NO ] ;
2013-12-10 18:14:32 -08:00
}
2012-07-12 22:05:23 -07:00
}
2012-08-06 15:46:05 -07:00
- ( void ) fadeSelectedCell {
2014-02-20 14:52:27 -08:00
[ self fadeSelectedCell : YES ] ;
}
- ( void ) fadeSelectedCell : ( BOOL ) deselect {
[ self . storyTitlesTable reloadData ] ;
2014-02-12 20:09:37 -08:00
NSInteger location = storiesCollection . locationOfActiveStory ;
2012-08-06 15:46:05 -07:00
NSIndexPath * indexPath = [ NSIndexPath indexPathForRow : location inSection : 0 ] ;
2014-02-25 12:37:22 -08:00
2014-02-20 14:52:27 -08:00
if ( indexPath && location >= 0 ) {
[ self . storyTitlesTable selectRowAtIndexPath : indexPath
animated : NO
scrollPosition : UITableViewScrollPositionMiddle ] ;
if ( deselect ) {
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , 0.4 * NSEC_PER _SEC ) ,
dispatch_get _main _queue ( ) , ^ ( void ) {
[ self . storyTitlesTable deselectRowAtIndexPath : indexPath
animated : YES ] ;
} ) ;
}
}
2014-02-25 12:37:22 -08:00
if ( deselect ) {
appDelegate . activeStory = nil ;
}
2010-06-21 17:17:26 -04:00
}
2012-08-02 18:00:48 -07:00
- ( void ) setUserAvatarLayout : ( UIInterfaceOrientation ) orientation {
2014-02-12 20:09:37 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPhone && storiesCollection . isSocialView ) {
2012-08-02 18:00:48 -07:00
if ( UIInterfaceOrientationIsPortrait ( orientation ) ) {
2013-09-24 17:18:20 -07:00
NBBarButtonItem * avatar = ( NBBarButtonItem * ) titleImageBarButton . customView ;
2012-08-02 18:00:48 -07:00
CGRect buttonFrame = avatar . frame ;
buttonFrame . size = CGSizeMake ( 32 , 32 ) ;
avatar . frame = buttonFrame ;
} else {
2013-09-24 17:18:20 -07:00
NBBarButtonItem * avatar = ( NBBarButtonItem * ) titleImageBarButton . customView ;
2012-08-02 18:00:48 -07:00
CGRect buttonFrame = avatar . frame ;
buttonFrame . size = CGSizeMake ( 28 , 28 ) ;
avatar . frame = buttonFrame ;
}
}
}
2010-06-21 17:17:26 -04:00
# pragma mark -
2010-06-24 00:22:26 -04:00
# pragma mark Initialization
2011-09-06 17:51:02 -07:00
- ( void ) resetFeedDetail {
2013-07-17 19:22:41 -07:00
appDelegate . hasLoadedFeedDetail = NO ;
2013-10-11 18:07:30 -07:00
self . navigationItem . titleView = nil ;
2011-09-06 17:51:02 -07:00
self . pageFetching = NO ;
self . pageFinished = NO ;
2014-02-19 18:59:14 -08:00
self . isOnline = YES ;
self . isShowingFetching = NO ;
2014-02-27 14:49:33 -08:00
// self . feedPage = 1 ;
2012-12-11 12:01:49 -08:00
appDelegate . activeStory = nil ;
2014-11-17 16:07:58 -08:00
[ storiesCollection setStories : nil ] ;
[ storiesCollection setFeedUserProfiles : nil ] ;
2014-11-17 16:20:29 -08:00
storiesCollection . storyCount = 0 ;
2014-03-04 10:21:48 -08:00
if ( ! self . isDashboardModule ) {
[ appDelegate . storyPageControl resetPages ] ;
}
2014-12-02 18:36:38 -08:00
storiesCollection . inSearch = NO ;
storiesCollection . searchQuery = nil ;
2014-11-17 16:07:58 -08:00
[ self . searchBar setText : @ "" ] ;
2013-06-13 17:56:58 -07:00
[ self . notifier hideIn : 0 ] ;
2013-07-17 19:22:41 -07:00
[ self cancelRequests ] ;
2013-06-13 17:56:58 -07:00
[ self beginOfflineTimer ] ;
2014-02-12 15:48:16 -08:00
[ appDelegate . cacheImagesOperationQueue cancelAllOperations ] ;
2011-09-06 17:51:02 -07:00
}
2014-11-17 16:07:58 -08:00
- ( void ) reloadStories {
2014-11-17 16:20:29 -08:00
appDelegate . hasLoadedFeedDetail = NO ;
2014-11-17 16:07:58 -08:00
appDelegate . activeStory = nil ;
2014-02-12 20:09:37 -08:00
[ storiesCollection setStories : nil ] ;
2014-11-17 16:07:58 -08:00
[ storiesCollection setFeedUserProfiles : nil ] ;
2014-02-12 20:09:37 -08:00
storiesCollection . storyCount = 0 ;
storiesCollection . activeClassifiers = [ NSMutableDictionary dictionary ] ;
storiesCollection . activePopularAuthors = [ NSArray array ] ;
storiesCollection . activePopularTags = [ NSArray array ] ;
2014-11-17 16:07:58 -08:00
self . pageFetching = NO ;
self . pageFinished = NO ;
self . isOnline = YES ;
self . isShowingFetching = NO ;
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ) {
2012-10-30 17:05:39 -07:00
[ self fetchRiverPage : 1 withCallback : nil ] ;
} else {
[ self fetchFeedDetail : 1 withCallback : nil ] ;
}
2013-08-06 18:08:55 -07:00
[ self . storyTitlesTable reloadData ] ;
[ storyTitlesTable scrollRectToVisible : CGRectMake ( 0 , 0 , 1 , 1 ) animated : YES ] ;
2012-10-30 17:05:39 -07:00
}
2013-06-13 17:56:58 -07:00
- ( void ) beginOfflineTimer {
2014-02-27 14:49:33 -08:00
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( self . isDashboardModule ? 3 : 1 ) * NSEC_PER _SEC ) , dispatch_get _main _queue ( ) , ^ {
2014-02-12 20:09:37 -08:00
if ( ! storiesCollection . storyLocationsCount && ! self . pageFinished &&
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage = = 1 && self . isOnline ) {
2014-02-19 18:59:14 -08:00
self . isShowingFetching = YES ;
self . isOnline = NO ;
2013-07-31 18:42:18 -07:00
[ self showLoadingNotifier ] ;
2013-06-13 17:56:58 -07:00
[ self loadOfflineStories ] ;
}
} ) ;
}
2014-02-12 15:48:16 -08:00
- ( void ) cacheStoryImages : ( NSArray * ) storyImageUrls {
2014-02-13 17:18:29 -08:00
NSBlockOperation * cacheImagesOperation = [ NSBlockOperation blockOperationWithBlock : ^ {
for ( NSString * storyImageUrl in storyImageUrls ) {
2014-02-12 15:48:16 -08:00
// NSLog ( @ "Fetching image: %@" , storyImageUrl ) ;
2014-02-13 17:18:29 -08:00
NSMutableURLRequest * request = [ NSMutableURLRequest
requestWithURL : [ NSURL URLWithString : storyImageUrl ] ] ;
[ request addValue : @ "image/*" forHTTPHeaderField : @ "Accept" ] ;
[ request setTimeoutInterval : 5.0 ] ;
AFImageRequestOperation * requestOperation = [ [ AFImageRequestOperation alloc ]
initWithRequest : request ] ;
[ requestOperation start ] ;
[ requestOperation waitUntilFinished ] ;
UIImage * image = requestOperation . responseImage ;
if ( ! image || image . size . height < 50 || image . size . width < 50 ) {
2014-02-24 18:56:51 -08:00
[ appDelegate . cachedStoryImages setObject : [ NSNull null ]
forKey : storyImageUrl ] ;
2014-02-13 17:18:29 -08:00
continue ;
2014-02-12 15:48:16 -08:00
}
2014-02-13 17:18:29 -08:00
CGSize maxImageSize = CGSizeMake ( 300 , 300 ) ;
image = [ image imageByScalingAndCroppingForSize : maxImageSize ] ;
2014-02-24 18:56:51 -08:00
[ appDelegate . cachedStoryImages setObject : image
forKey : storyImageUrl ] ;
2014-02-13 17:18:29 -08:00
if ( self . isDashboardModule ) {
[ appDelegate . dashboardViewController . storiesModule
showStoryImage : storyImageUrl ] ;
} else {
[ appDelegate . feedDetailViewController
showStoryImage : storyImageUrl ] ;
}
}
} ] ;
2015-09-16 15:55:55 -07:00
[ cacheImagesOperation setQualityOfService : NSQualityOfServiceBackground ] ;
2014-02-13 17:18:29 -08:00
[ cacheImagesOperation setQueuePriority : NSOperationQueuePriorityVeryLow ] ;
[ appDelegate . cacheImagesOperationQueue addOperation : cacheImagesOperation ] ;
2014-02-12 15:48:16 -08:00
}
2014-02-11 11:52:32 -08:00
- ( void ) showStoryImage : ( NSString * ) imageUrl {
2014-02-11 13:23:45 -08:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2014-02-12 20:09:37 -08:00
if ( self . isDashboardModule &&
appDelegate . navigationController . visibleViewController = = appDelegate . feedDetailViewController ) {
return ;
}
2014-02-11 13:23:45 -08:00
for ( FeedDetailTableCell * cell in [ self . storyTitlesTable visibleCells ] ) {
if ( ! [ cell isKindOfClass : [ FeedDetailTableCell class ] ] ) return ;
if ( [ cell . storyImageUrl isEqualToString : imageUrl ] ) {
NSIndexPath * indexPath = [ self . storyTitlesTable indexPathForCell : cell ] ;
2014-02-13 17:18:29 -08:00
// NSLog ( @ "Reloading cell (dashboard? %d): %@ (%ld)" , self . isDashboardModule , cell . storyTitle , ( long ) indexPath . row ) ;
2014-02-11 13:23:45 -08:00
[ self . storyTitlesTable beginUpdates ] ;
2014-02-11 11:52:32 -08:00
[ self . storyTitlesTable reloadRowsAtIndexPaths : @ [ indexPath ]
2014-02-11 16:00:31 -08:00
withRowAnimation : UITableViewRowAnimationNone ] ;
2014-02-11 13:23:45 -08:00
[ self . storyTitlesTable endUpdates ] ;
2014-02-12 15:48:16 -08:00
break ;
2014-02-11 13:23:45 -08:00
}
2014-02-11 11:52:32 -08:00
}
2014-02-11 13:23:45 -08:00
} ) ;
2014-02-11 11:52:32 -08:00
}
2012-06-29 10:20:06 -07:00
# pragma mark -
# pragma mark Regular and Social Feeds
2011-09-05 22:06:31 -07:00
- ( void ) fetchNextPage : ( void ( ^ ) ( ) ) callback {
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ) {
2014-02-27 14:49:33 -08:00
[ self fetchRiverPage : storiesCollection . feedPage + 1 withCallback : callback ] ;
2012-10-17 18:09:20 -07:00
} else {
2014-02-27 14:49:33 -08:00
[ self fetchFeedDetail : storiesCollection . feedPage + 1 withCallback : callback ] ;
2012-10-17 18:09:20 -07:00
}
2011-09-05 22:06:31 -07:00
}
2012-10-17 18:09:20 -07:00
- ( void ) fetchFeedDetail : ( int ) page withCallback : ( void ( ^ ) ( ) ) callback {
2012-06-25 15:02:20 -07:00
NSString * theFeedDetailURL ;
2014-02-12 20:09:37 -08:00
if ( ! storiesCollection . activeFeed ) return ;
2012-12-20 12:08:35 -08:00
2014-02-12 20:46:59 -08:00
if ( ! callback && ( self . pageFetching || self . pageFinished ) ) return ;
2012-07-16 19:45:14 -07:00
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage = page ;
2014-02-12 20:46:59 -08:00
self . pageFetching = YES ;
NSInteger storyCount = storiesCollection . storyCount ;
if ( storyCount = = 0 ) {
[ self . storyTitlesTable reloadData ] ;
[ storyTitlesTable scrollRectToVisible : CGRectMake ( 0 , 0 , 1 , 1 ) animated : YES ] ;
}
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2014-02-12 20:46:59 -08:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT ,
( unsigned long ) NULL ) , ^ ( void ) {
[ appDelegate . database inDatabase : ^ ( FMDatabase * db ) {
[ appDelegate prepareActiveCachedImages : db ] ;
} ] ;
} ) ;
}
2014-12-02 18:36:38 -08:00
if ( ! storiesCollection . inSearch && storiesCollection . feedPage = = 1 ) {
2014-11-17 16:33:36 -08:00
[ self . storyTitlesTable setContentOffset : CGPointMake ( 0 , CGRectGetHeight ( self . searchBar . frame ) ) ] ;
}
2014-02-12 20:46:59 -08:00
2014-02-19 18:59:14 -08:00
if ( ! self . isOnline ) {
2014-02-12 20:46:59 -08:00
[ self loadOfflineStories ] ;
2014-02-19 18:59:14 -08:00
if ( ! self . isShowingFetching ) {
2014-02-12 20:46:59 -08:00
[ self showOfflineNotifier ] ;
2013-06-26 16:22:44 -07:00
}
2014-02-12 20:46:59 -08:00
return ;
2014-02-27 14:49:33 -08:00
} else {
[ self . notifier hide ] ;
2014-02-12 20:46:59 -08:00
}
if ( storiesCollection . isSocialView ) {
theFeedDetailURL = [ NSString stringWithFormat : @ "%@/social/stories/%@/?page=%d" ,
NEWSBLUR_URL ,
[ storiesCollection . activeFeed objectForKey : @ "user_id" ] ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2014-05-20 15:29:16 -07:00
} else if ( storiesCollection . isSavedView ) {
theFeedDetailURL = [ NSString stringWithFormat :
@ "%@/reader/starred_stories/?page=%d&v=2&tag=%@" ,
NEWSBLUR_URL ,
storiesCollection . feedPage ,
2014-05-20 15:43:45 -07:00
[ storiesCollection . activeSavedStoryTag urlEncode ] ] ;
2014-10-22 17:03:48 -07:00
} else if ( storiesCollection . isReadView ) {
theFeedDetailURL = [ NSString stringWithFormat :
@ "%@/reader/read_stories/?page=%d&v=2" ,
NEWSBLUR_URL ,
storiesCollection . feedPage ] ;
2014-02-12 20:46:59 -08:00
} else {
2015-01-28 13:38:52 -08:00
theFeedDetailURL = [ NSString stringWithFormat : @ "%@/reader/feed/%@/?include_hidden=true&page=%d" ,
2014-02-12 20:46:59 -08:00
NEWSBLUR_URL ,
[ storiesCollection . activeFeed objectForKey : @ "id" ] ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2014-02-12 20:46:59 -08:00
}
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&order=%@" ,
theFeedDetailURL ,
[ storiesCollection activeOrder ] ] ;
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&read_filter=%@" ,
theFeedDetailURL ,
[ storiesCollection activeReadFilter ] ] ;
2014-12-02 18:36:38 -08:00
if ( storiesCollection . inSearch && storiesCollection . searchQuery ) {
2014-11-17 16:07:58 -08:00
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&query=%@" ,
theFeedDetailURL ,
2014-12-02 18:36:38 -08:00
[ storiesCollection . searchQuery stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ] ;
2014-11-17 16:07:58 -08:00
}
2014-02-12 20:46:59 -08:00
[ self cancelRequests ] ;
__weak ASIHTTPRequest * request = [ self requestWithURL : theFeedDetailURL ] ;
[ request setDelegate : self ] ;
[ request setResponseEncoding : NSUTF8StringEncoding ] ;
[ request setDefaultResponseEncoding : NSUTF8StringEncoding ] ;
2014-02-27 14:49:33 -08:00
[ request setUserInfo : @ { @ "feedPage" : [ NSNumber numberWithInt : storiesCollection . feedPage ] } ] ;
2014-02-12 20:46:59 -08:00
[ request setFailedBlock : ^ ( void ) {
NSLog ( @ "in failed block %@" , request ) ;
if ( request . isCancelled ) {
NSLog ( @ "Cancelled" ) ;
2013-07-31 18:42:18 -07:00
return ;
2012-06-25 15:02:20 -07:00
} else {
2014-02-19 18:59:14 -08:00
self . isOnline = NO ;
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage = 1 ;
2014-02-12 20:46:59 -08:00
[ self loadOfflineStories ] ;
[ self showOfflineNotifier ] ;
2012-06-25 15:02:20 -07:00
}
2014-02-12 20:46:59 -08:00
[ self . storyTitlesTable reloadData ] ;
} ] ;
[ request setCompletionBlock : ^ ( void ) {
if ( ! storiesCollection . activeFeed ) return ;
[ self finishedLoadingFeed : request ] ;
if ( callback ) {
callback ( ) ;
}
} ] ;
[ request setTimeOutSeconds : 30 ] ;
[ request setTag : [ [ [ storiesCollection activeFeed ] objectForKey : @ "id" ] intValue ] ] ;
[ request startAsynchronous ] ;
[ requests addObject : request ] ;
2010-07-15 23:32:37 -04:00
}
2013-06-13 17:56:58 -07:00
- ( void ) loadOfflineStories {
2013-07-31 18:42:18 -07:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT ,
( unsigned long ) NULL ) , ^ ( void ) {
2013-06-14 19:21:30 -07:00
[ appDelegate . database inDatabase : ^ ( FMDatabase * db ) {
2013-06-14 19:44:57 -07:00
NSArray * feedIds ;
2013-09-25 17:43:00 -07:00
NSInteger limit = 12 ;
2014-02-27 14:49:33 -08:00
NSInteger offset = ( storiesCollection . feedPage - 1 ) * limit ;
2013-06-20 21:46:09 -07:00
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ) {
feedIds = storiesCollection . activeFolderFeeds ;
} else if ( storiesCollection . activeFeed ) {
feedIds = @ [ [ storiesCollection . activeFeed objectForKey : @ "id" ] ] ;
2013-06-20 20:38:40 -07:00
} else {
return ;
2013-06-14 19:44:57 -07:00
}
2013-06-15 08:56:43 -07:00
2013-06-21 18:35:06 -07:00
NSString * orderSql ;
2014-02-12 20:09:37 -08:00
if ( [ storiesCollection . activeOrder isEqualToString : @ "oldest" ] ) {
2013-06-21 18:35:06 -07:00
orderSql = @ "ASC" ;
2013-06-15 08:56:43 -07:00
} else {
2013-06-21 18:35:06 -07:00
orderSql = @ "DESC" ;
2013-06-15 08:56:43 -07:00
}
2013-06-21 18:35:06 -07:00
NSString * readFilterSql ;
2014-02-12 20:09:37 -08:00
if ( [ storiesCollection . activeReadFilter isEqualToString : @ "unread" ] ) {
2013-06-21 18:35:06 -07:00
readFilterSql = @ "INNER JOIN unread_hashes uh ON s.story_hash = uh.story_hash" ;
2013-06-15 08:56:43 -07:00
} else {
2013-06-21 18:35:06 -07:00
readFilterSql = @ "" ;
2013-06-15 08:56:43 -07:00
}
2013-09-25 17:43:00 -07:00
NSString * sql = [ NSString stringWithFormat : @ "SELECT * FROM stories s %@ WHERE s.story_feed_id IN (%@) ORDER BY s.story_timestamp %@ LIMIT %ld OFFSET %ld" ,
2013-06-21 18:35:06 -07:00
readFilterSql ,
2013-06-15 08:56:43 -07:00
[ feedIds componentsJoinedByString : @ "," ] ,
2013-07-31 18:42:18 -07:00
orderSql ,
2013-09-25 17:43:00 -07:00
( long ) limit , ( long ) offset ] ;
2013-06-14 19:44:57 -07:00
FMResultSet * cursor = [ db executeQuery : sql ] ;
2013-06-14 19:21:30 -07:00
NSMutableArray * offlineStories = [ NSMutableArray array ] ;
while ( [ cursor next ] ) {
NSDictionary * story = [ cursor resultDictionary ] ;
[ offlineStories addObject : [ NSJSONSerialization
JSONObjectWithData : [ [ story objectForKey : @ "story_json" ]
dataUsingEncoding : NSUTF8StringEncoding ]
options : nil error : nil ] ] ;
}
2013-10-03 18:07:39 -07:00
[ cursor close ] ;
2013-06-14 19:21:30 -07:00
2014-02-12 20:09:37 -08:00
if ( [ storiesCollection . activeReadFilter isEqualToString : @ "all" ] ) {
2013-06-20 21:46:09 -07:00
NSString * unreadHashSql = [ NSString stringWithFormat : @ "SELECT s.story_hash FROM stories s INNER JOIN unread_hashes uh ON s.story_hash = uh.story_hash WHERE s.story_feed_id IN (%@)" ,
[ feedIds componentsJoinedByString : @ "," ] ] ;
FMResultSet * unreadHashCursor = [ db executeQuery : unreadHashSql ] ;
2013-07-31 18:42:18 -07:00
NSMutableDictionary * unreadStoryHashes ;
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2013-07-31 18:42:18 -07:00
unreadStoryHashes = [ NSMutableDictionary dictionary ] ;
} else {
2013-09-05 12:38:06 -07:00
unreadStoryHashes = appDelegate . unreadStoryHashes ;
2013-07-31 18:42:18 -07:00
}
2013-06-20 21:46:09 -07:00
while ( [ unreadHashCursor next ] ) {
[ unreadStoryHashes setObject : [ NSNumber numberWithBool : YES ] forKey : [ unreadHashCursor objectForColumnName : @ "story_hash" ] ] ;
}
2013-09-05 12:38:06 -07:00
appDelegate . unreadStoryHashes = unreadStoryHashes ;
2013-10-03 18:07:39 -07:00
[ unreadHashCursor close ] ;
2013-06-20 21:46:09 -07:00
}
2013-10-17 17:23:52 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2014-02-19 18:59:14 -08:00
if ( self . isOnline ) {
2013-07-31 18:42:18 -07:00
NSLog ( @ "Online before offline rendered. Tossing offline stories." ) ;
return ;
}
if ( ! [ offlineStories count ] ) {
self . pageFinished = YES ;
2013-08-06 15:43:37 -07:00
[ self . storyTitlesTable reloadData ] ;
2013-07-31 18:42:18 -07:00
} else {
[ self renderStories : offlineStories ] ;
}
2014-02-19 18:59:14 -08:00
if ( ! self . isShowingFetching ) {
2013-08-12 11:59:07 -07:00
[ self showOfflineNotifier ] ;
}
2013-07-31 18:42:18 -07:00
} ) ;
2013-06-14 19:21:30 -07:00
} ] ;
2013-07-31 18:42:18 -07:00
} ) ;
2013-06-13 17:56:58 -07:00
}
- ( void ) showOfflineNotifier {
2013-07-31 18:42:18 -07:00
// [ self . notifier hide ] ;
2013-06-13 17:56:58 -07:00
self . notifier . style = NBOfflineStyle ;
self . notifier . title = @ "Offline" ;
[ self . notifier show ] ;
}
- ( void ) showLoadingNotifier {
[ self . notifier hide ] ;
self . notifier . style = NBLoadingStyle ;
2013-06-13 21:56:13 -07:00
self . notifier . title = @ "Fetching recent stories..." ;
2013-06-13 17:56:58 -07:00
[ self . notifier show ] ;
}
2012-06-29 10:20:06 -07:00
# pragma mark -
# pragma mark River of News
2014-02-13 17:18:29 -08:00
- ( void ) fetchRiver {
2014-02-27 14:49:33 -08:00
[ self fetchRiverPage : storiesCollection . feedPage withCallback : nil ] ;
2014-02-13 17:18:29 -08:00
}
2014-02-12 20:09:37 -08:00
- ( void ) fetchRiverPage : ( int ) page withCallback : ( void ( ^ ) ( ) ) callback {
2014-02-12 20:46:59 -08:00
if ( self . pageFetching || self . pageFinished ) return ;
2014-02-21 16:06:49 -08:00
// NSLog ( @ "Fetching River in storiesCollection (pg. %ld): %@" , ( long ) page , storiesCollection ) ;
2014-02-19 18:59:14 -08:00
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage = page ;
2014-02-12 20:46:59 -08:00
self . pageFetching = YES ;
NSInteger storyCount = storiesCollection . storyCount ;
if ( storyCount = = 0 ) {
[ self . storyTitlesTable reloadData ] ;
[ storyTitlesTable scrollRectToVisible : CGRectMake ( 0 , 0 , 1 , 1 ) animated : YES ] ;
2013-06-11 23:00:00 -07:00
// [ self . notifier initWithTitle : @ "Loading more..." inView : self . view ] ;
2013-06-10 00:29:03 -07:00
2014-02-12 20:46:59 -08:00
}
2014-12-02 18:36:38 -08:00
if ( ! storiesCollection . inSearch && storiesCollection . feedPage = = 1 ) {
2014-11-17 16:33:36 -08:00
[ self . storyTitlesTable setContentOffset : CGPointMake ( 0 , CGRectGetHeight ( self . searchBar . frame ) ) ] ;
}
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2014-02-12 20:46:59 -08:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT ,
( unsigned long ) NULL ) , ^ ( void ) {
[ appDelegate . database inDatabase : ^ ( FMDatabase * db ) {
[ appDelegate prepareActiveCachedImages : db ] ;
} ] ;
} ) ;
}
2014-02-19 18:59:14 -08:00
if ( ! self . isOnline ) {
2014-02-12 20:46:59 -08:00
[ self loadOfflineStories ] ;
return ;
2014-02-27 14:49:33 -08:00
} else {
[ self . notifier hide ] ;
2014-02-12 20:46:59 -08:00
}
NSString * theFeedDetailURL ;
if ( storiesCollection . isSocialRiverView ) {
if ( [ storiesCollection . activeFolder isEqualToString : @ "river_global" ] ) {
2012-10-17 15:07:53 -07:00
theFeedDetailURL = [ NSString stringWithFormat :
2014-02-12 20:46:59 -08:00
@ "%@/social/river_stories/?global_feed=true&page=%d" ,
2012-10-17 15:07:53 -07:00
NEWSBLUR_URL ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2014-02-12 20:46:59 -08:00
2012-08-08 10:57:38 -07:00
} else {
theFeedDetailURL = [ NSString stringWithFormat :
2014-02-12 20:46:59 -08:00
@ "%@/social/river_stories/?page=%d" ,
2012-08-08 10:57:38 -07:00
NEWSBLUR_URL ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2012-08-08 10:57:38 -07:00
}
2014-05-20 15:29:16 -07:00
} else if ( storiesCollection . isSavedView ) {
2014-02-12 20:46:59 -08:00
theFeedDetailURL = [ NSString stringWithFormat :
2014-05-20 15:29:16 -07:00
@ "%@/reader/starred_stories/?page=%d&v=2" ,
2014-02-12 20:46:59 -08:00
NEWSBLUR_URL ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2014-10-22 17:03:48 -07:00
} else if ( storiesCollection . isReadView ) {
theFeedDetailURL = [ NSString stringWithFormat :
@ "%@/reader/read_stories/?page=%d&v=2" ,
NEWSBLUR_URL ,
storiesCollection . feedPage ] ;
2014-02-12 20:46:59 -08:00
} else {
theFeedDetailURL = [ NSString stringWithFormat :
2015-01-28 13:38:52 -08:00
@ "%@/reader/river_stories/?include_hidden=true&f=%@&page=%d" ,
2014-02-12 20:46:59 -08:00
NEWSBLUR_URL ,
[ storiesCollection . activeFolderFeeds componentsJoinedByString : @ "&f=" ] ,
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage ] ;
2012-06-29 10:20:06 -07:00
}
2014-02-12 20:46:59 -08:00
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&order=%@" ,
theFeedDetailURL ,
[ storiesCollection activeOrder ] ] ;
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&read_filter=%@" ,
theFeedDetailURL ,
[ storiesCollection activeReadFilter ] ] ;
2014-12-02 18:36:38 -08:00
if ( storiesCollection . inSearch && storiesCollection . searchQuery ) {
2014-11-17 16:07:58 -08:00
theFeedDetailURL = [ NSString stringWithFormat : @ "%@&query=%@" ,
theFeedDetailURL ,
2014-12-02 18:36:38 -08:00
[ storiesCollection . searchQuery stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ] ;
2014-11-17 16:07:58 -08:00
}
2014-02-12 20:46:59 -08:00
[ self cancelRequests ] ;
__weak ASIHTTPRequest * request = [ self requestWithURL : theFeedDetailURL ] ;
[ request setDelegate : self ] ;
[ request setResponseEncoding : NSUTF8StringEncoding ] ;
[ request setDefaultResponseEncoding : NSUTF8StringEncoding ] ;
2014-02-27 14:49:33 -08:00
[ request setUserInfo : @ { @ "feedPage" : [ NSNumber numberWithInt : storiesCollection . feedPage ] } ] ;
2014-02-12 20:46:59 -08:00
[ request setFailedBlock : ^ ( void ) {
if ( request . isCancelled ) {
NSLog ( @ "Cancelled" ) ;
return ;
} else {
2014-02-19 18:59:14 -08:00
self . isOnline = NO ;
self . isShowingFetching = NO ;
2014-02-27 14:49:33 -08:00
// storiesCollection . feedPage = 1 ;
2014-02-12 20:46:59 -08:00
[ self loadOfflineStories ] ;
[ self showOfflineNotifier ] ;
}
} ] ;
[ request setCompletionBlock : ^ ( void ) {
[ self finishedLoadingFeed : request ] ;
if ( callback ) {
callback ( ) ;
}
} ] ;
[ request setTimeOutSeconds : 30 ] ;
[ request startAsynchronous ] ;
2012-06-29 10:20:06 -07:00
}
# pragma mark -
# pragma mark Processing Stories
2011-09-05 22:06:31 -07:00
- ( void ) finishedLoadingFeed : ( ASIHTTPRequest * ) request {
2013-07-17 19:22:41 -07:00
if ( request . isCancelled ) {
NSLog ( @ "Cancelled" ) ;
return ;
} else if ( [ request responseStatusCode ] >= 500 ) {
2014-02-19 18:59:14 -08:00
self . isOnline = NO ;
self . isShowingFetching = NO ;
2014-02-27 14:49:33 -08:00
// storiesCollection . feedPage = 1 ;
2013-10-10 12:58:40 -07:00
[ self loadOfflineStories ] ;
[ self showOfflineNotifier ] ;
2013-07-17 19:22:41 -07:00
if ( [ request responseStatusCode ] = = 503 ) {
[ self informError : @ "In maintenance mode" ] ;
2013-07-31 18:42:18 -07:00
self . pageFinished = YES ;
2013-06-13 21:56:13 -07:00
} else {
2013-06-20 20:38:40 -07:00
[ self informError : @ "The server barfed." ] ;
2013-06-13 21:56:13 -07:00
}
2013-06-20 20:38:40 -07:00
[ self . storyTitlesTable reloadData ] ;
2011-11-30 09:38:31 -08:00
return ;
}
2013-07-17 19:22:41 -07:00
appDelegate . hasLoadedFeedDetail = YES ;
2014-02-19 18:59:14 -08:00
self . isOnline = YES ;
self . isShowingFetching = NO ;
2014-03-21 16:23:51 -07:00
storiesCollection . feedPage = [ [ request . userInfo objectForKey : @ "feedPage" ] intValue ] ;
2011-09-05 22:06:31 -07:00
NSString * responseString = [ request responseString ] ;
2012-07-27 16:21:44 -07:00
NSData * responseData = [ responseString dataUsingEncoding : NSUTF8StringEncoding ] ;
NSError * error ;
NSDictionary * results = [ NSJSONSerialization
JSONObjectWithData : responseData
options : kNilOptions
error : & error ] ;
2014-11-17 14:43:36 -08:00
if ( storiesCollection . isSavedView &&
! [ [ results objectForKey : @ "stories" ] count ] &&
2014-11-17 16:07:58 -08:00
storiesCollection . feedPage = = 1 &&
2014-11-17 14:43:36 -08:00
[ results objectForKey : @ "message" ] ) {
[ self informError : nil details : [ results objectForKey : @ "message" ] ] ;
}
2012-12-12 09:02:35 -08:00
id feedId = [ results objectForKey : @ "feed_id" ] ;
2012-12-27 00:15:26 -08:00
NSString * feedIdStr = [ NSString stringWithFormat : @ "%@" , feedId ] ;
2014-02-12 20:09:37 -08:00
if ( ! ( storiesCollection . isRiverView ||
2014-05-20 15:29:16 -07:00
storiesCollection . isSavedView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isReadView ||
2014-02-12 20:09:37 -08:00
storiesCollection . isSocialView ||
storiesCollection . isSocialRiverView )
2012-12-12 09:02:35 -08:00
&& request . tag ! = [ feedId intValue ] ) {
2011-10-26 20:09:28 -07:00
return ;
}
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isSocialView ||
2014-05-20 15:29:16 -07:00
storiesCollection . isSocialRiverView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isSavedView ||
storiesCollection . isReadView ) {
2012-06-26 16:24:19 -07:00
NSArray * newFeeds = [ results objectForKey : @ "feeds" ] ;
for ( int i = 0 ; i < newFeeds . count ; i + + ) {
NSString * feedKey = [ NSString stringWithFormat : @ "%@" , [ [ newFeeds objectAtIndex : i ] objectForKey : @ "id" ] ] ;
[ appDelegate . dictActiveFeeds setObject : [ newFeeds objectAtIndex : i ]
forKey : feedKey ] ;
}
2012-06-29 10:20:06 -07:00
[ self loadFaviconsFromActiveFeed ] ;
2012-06-26 16:24:19 -07:00
}
2014-02-12 15:48:16 -08:00
2012-12-11 11:50:06 -08:00
NSMutableDictionary * newClassifiers = [ [ results objectForKey : @ "classifiers" ] mutableCopy ] ;
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ||
2014-05-20 15:29:16 -07:00
storiesCollection . isSavedView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isReadView ||
2014-02-12 20:09:37 -08:00
storiesCollection . isSocialView ||
storiesCollection . isSocialRiverView ) {
2012-12-12 09:02:35 -08:00
for ( id key in [ newClassifiers allKeys ] ) {
2014-02-12 20:09:37 -08:00
[ storiesCollection . activeClassifiers setObject : [ newClassifiers objectForKey : key ] forKey : key ] ;
2012-12-12 09:02:35 -08:00
}
2013-10-01 19:32:40 -07:00
} else if ( newClassifiers ) {
2014-02-12 20:09:37 -08:00
[ storiesCollection . activeClassifiers setObject : newClassifiers forKey : feedIdStr ] ;
2012-12-11 11:50:06 -08:00
}
2014-02-12 20:09:37 -08:00
storiesCollection . activePopularAuthors = [ results objectForKey : @ "feed_authors" ] ;
storiesCollection . activePopularTags = [ results objectForKey : @ "feed_tags" ] ;
2012-12-07 16:17:48 -08:00
2011-10-26 20:09:28 -07:00
NSArray * newStories = [ results objectForKey : @ "stories" ] ;
2012-08-06 15:46:05 -07:00
NSMutableArray * confirmedNewStories = [ [ NSMutableArray alloc ] init ] ;
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2013-06-13 17:56:58 -07:00
confirmedNewStories = [ newStories copy ] ;
} else {
2011-10-26 20:09:28 -07:00
NSMutableSet * storyIds = [ NSMutableSet set ] ;
2014-02-12 20:09:37 -08:00
for ( id story in storiesCollection . activeFeedStories ) {
2014-02-12 20:41:29 -08:00
[ storyIds addObject : [ story objectForKey : @ "story_hash" ] ] ;
2011-10-26 20:09:28 -07:00
}
for ( id story in newStories ) {
2014-02-12 20:41:29 -08:00
if ( ! [ storyIds containsObject : [ story objectForKey : @ "story_hash" ] ] ) {
2011-10-26 20:09:28 -07:00
[ confirmedNewStories addObject : story ] ;
2011-10-26 09:05:59 -07:00
}
}
2011-09-06 17:51:02 -07:00
}
2014-02-12 15:48:16 -08:00
2012-06-22 18:01:08 -07:00
// Adding new user profiles to appDelegate . activeFeedUserProfiles
2012-08-15 15:38:21 -07:00
NSArray * newUserProfiles = [ [ NSArray alloc ] init ] ;
if ( [ results objectForKey : @ "user_profiles" ] ! = nil ) {
newUserProfiles = [ results objectForKey : @ "user_profiles" ] ;
}
2012-07-20 00:21:24 -07:00
// add self to user profiles
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2013-03-04 20:21:29 -08:00
newUserProfiles = [ newUserProfiles arrayByAddingObject : appDelegate . dictSocialProfile ] ;
2012-07-20 00:21:24 -07:00
}
2012-06-22 18:01:08 -07:00
if ( [ newUserProfiles count ] ) {
NSMutableArray * confirmedNewUserProfiles = [ NSMutableArray array ] ;
2014-02-12 20:09:37 -08:00
if ( [ storiesCollection . activeFeedUserProfiles count ] ) {
2012-06-22 18:01:08 -07:00
NSMutableSet * userProfileIds = [ NSMutableSet set ] ;
2014-02-12 20:09:37 -08:00
for ( id userProfile in storiesCollection . activeFeedUserProfiles ) {
2012-06-22 18:01:08 -07:00
[ userProfileIds addObject : [ userProfile objectForKey : @ "id" ] ] ;
}
for ( id userProfile in newUserProfiles ) {
if ( ! [ userProfileIds containsObject : [ userProfile objectForKey : @ "id" ] ] ) {
[ confirmedNewUserProfiles addObject : userProfile ] ;
}
}
} else {
2012-07-15 15:06:06 -07:00
confirmedNewUserProfiles = [ newUserProfiles copy ] ;
2012-06-22 18:01:08 -07:00
}
2012-07-16 10:10:52 -07:00
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2014-02-12 20:09:37 -08:00
[ storiesCollection setFeedUserProfiles : confirmedNewUserProfiles ] ;
2012-07-16 10:10:52 -07:00
} else if ( newUserProfiles . count > 0 ) {
2014-02-12 20:09:37 -08:00
[ storiesCollection addFeedUserProfiles : confirmedNewUserProfiles ] ;
2012-07-16 10:10:52 -07:00
}
2012-06-22 18:01:08 -07:00
}
2014-02-12 15:48:16 -08:00
2013-06-13 21:56:13 -07:00
self . pageFinished = NO ;
2011-10-26 20:09:28 -07:00
[ self renderStories : confirmedNewStories ] ;
2014-02-12 15:48:16 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
[ appDelegate . storyPageControl resizeScrollView ] ;
[ appDelegate . storyPageControl setStoryFromScroll : YES ] ;
}
2014-03-04 11:27:29 -08:00
[ appDelegate . storyPageControl advanceToNextUnread ] ;
2013-06-20 19:25:57 -07:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT ,
( unsigned long ) NULL ) , ^ ( void ) {
[ appDelegate . database inTransaction : ^ ( FMDatabase * db , BOOL * rollback ) {
for ( NSDictionary * story in confirmedNewStories ) {
[ db executeUpdate : @ "INSERT into stories"
2013-06-23 22:19:08 -07:00
"(story_feed_id, story_hash, story_timestamp, story_json) VALUES "
"(?, ?, ?, ?)" ,
2013-06-20 19:25:57 -07:00
[ story objectForKey : @ "story_feed_id" ] ,
[ story objectForKey : @ "story_hash" ] ,
[ story objectForKey : @ "story_timestamp" ] ,
[ story JSONRepresentation ]
] ;
}
// NSLog ( @ "Inserting %d stories: %@" , [ confirmedNewStories count ] , [ db lastErrorMessage ] ) ;
} ] ;
} ) ;
2013-06-07 02:47:43 -04:00
2013-06-12 19:49:10 -07:00
[ self . notifier hide ] ;
2014-02-12 15:48:16 -08:00
2011-08-21 13:46:43 -07:00
}
2013-06-05 17:11:01 -07:00
# pragma mark -
2011-10-25 09:47:55 -07:00
# pragma mark Stories
2011-08-21 13:46:43 -07:00
- ( void ) renderStories : ( NSArray * ) newStories {
2011-08-09 17:58:43 -07:00
NSInteger newStoriesCount = [ newStories count ] ;
2011-07-22 09:10:13 -07:00
2013-06-13 17:56:58 -07:00
if ( newStoriesCount > 0 ) {
2014-02-27 14:49:33 -08:00
if ( storiesCollection . feedPage = = 1 ) {
2014-02-12 20:09:37 -08:00
[ storiesCollection setStories : newStories ] ;
2013-06-13 17:56:58 -07:00
} else {
2014-02-12 20:09:37 -08:00
[ storiesCollection addStories : newStories ] ;
2011-07-22 09:10:13 -07:00
}
2013-09-10 16:02:49 -07:00
} else {
2011-07-24 16:52:24 -07:00
self . pageFinished = YES ;
2011-07-20 22:21:11 -07:00
}
2014-02-12 15:48:16 -08:00
2013-09-10 16:02:49 -07:00
[ self . storyTitlesTable reloadData ] ;
2014-02-19 18:59:14 -08:00
2013-02-21 17:57:32 -08:00
if ( self . finishedAnimatingIn ) {
[ self testForTryFeed ] ;
}
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
[ appDelegate . masterContainerViewController syncNextPreviousButtons ] ;
}
2011-07-24 16:52:24 -07:00
2014-02-12 15:48:16 -08:00
NSMutableArray * storyImageUrls = [ NSMutableArray array ] ;
for ( NSDictionary * story in newStories ) {
if ( [ story objectForKey : @ "image_urls" ] && [ [ story objectForKey : @ "image_urls" ] count ] ) {
[ storyImageUrls addObject : [ [ story objectForKey : @ "image_urls" ] objectAtIndex : 0 ] ] ;
}
}
[ self performSelector : @ selector ( cacheStoryImages : ) withObject : storyImageUrls afterDelay : 0.2 ] ;
2014-02-19 18:59:14 -08:00
self . pageFetching = NO ;
2013-02-21 17:57:32 -08:00
}
- ( void ) testForTryFeed {
2014-02-24 18:35:35 -08:00
if ( self . isDashboardModule ||
! appDelegate . inFindingStoryMode ||
! appDelegate . tryFeedStoryId ) return ;
2014-12-15 15:24:47 -08:00
if ( ! self . view . window ) {
NSLog ( @ "No longer looking for try feed." ) ;
appDelegate . inFindingStoryMode = NO ;
appDelegate . tryFeedStoryId = nil ;
return ;
}
NSLog ( @ "Test for try feed" ) ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
MBProgressHUD * HUD = [ MBProgressHUD showHUDAddedTo : self . view animated : YES ] ;
HUD . labelText = @ "Finding story..." ;
2014-02-24 18:35:35 -08:00
for ( int i = 0 ; i < [ storiesCollection . activeFeedStories count ] ; i + + ) {
NSString * storyIdStr = [ [ storiesCollection . activeFeedStories
objectAtIndex : i ] objectForKey : @ "id" ] ;
NSString * storyHashStr = [ [ storiesCollection . activeFeedStories
objectAtIndex : i ] objectForKey : @ "story_hash" ] ;
if ( [ storyHashStr isEqualToString : appDelegate . tryFeedStoryId ] ||
[ storyIdStr isEqualToString : appDelegate . tryFeedStoryId ] ) {
NSDictionary * feed = [ storiesCollection . activeFeedStories objectAtIndex : i ] ;
NSInteger score = [ NewsBlurAppDelegate computeStoryScore : [ feed objectForKey : @ "intelligence" ] ] ;
if ( score < appDelegate . selectedIntelligence ) {
[ self changeIntelligence : score ] ;
}
NSInteger locationOfStoryId = [ storiesCollection locationOfStoryId : storyHashStr ] ;
NSIndexPath * indexPath = [ NSIndexPath indexPathForRow : locationOfStoryId inSection : 0 ] ;
[ self . storyTitlesTable selectRowAtIndexPath : indexPath
animated : NO
scrollPosition : UITableViewScrollPositionMiddle ] ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2012-07-23 10:57:11 -07:00
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ self . storyTitlesTable cellForRowAtIndexPath : indexPath ] ;
2012-07-31 23:49:51 -07:00
[ self loadStory : cell atRow : indexPath . row ] ;
2014-02-24 18:35:35 -08:00
} ) ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
// found the story , reset the two flags .
appDelegate . tryFeedStoryId = nil ;
appDelegate . inFindingStoryMode = NO ;
2012-07-16 19:45:14 -07:00
}
}
2010-06-21 17:17:26 -04:00
}
2011-07-24 20:34:54 -07:00
- ( void ) connection : ( NSURLConnection * ) connection didFailWithError : ( NSError * ) error {
2010-07-15 23:32:37 -04:00
// inform the user
2010-11-15 19:40:17 -05:00
NSLog ( @ "Connection failed! Error - %@" ,
[ error localizedDescription ] ) ;
2011-07-20 22:21:11 -07:00
self . pageFetching = NO ;
2011-08-18 09:56:52 -07:00
// User clicking on another link before the page loads is OK .
if ( [ error code ] ! = NSURLErrorCancelled ) {
2011-10-27 09:44:58 -07:00
[ self informError : error ] ;
2011-08-18 09:56:52 -07:00
}
2010-07-15 23:32:37 -04:00
}
2011-07-24 16:52:24 -07:00
- ( UITableViewCell * ) makeLoadingCell {
2013-09-25 17:43:00 -07:00
NSInteger height = 40 ;
2013-06-12 19:21:56 -07:00
UITableViewCell * cell = [ [ UITableViewCell alloc ]
initWithStyle : UITableViewCellStyleSubtitle
reuseIdentifier : @ "NoReuse" ] ;
2011-07-24 17:18:05 -07:00
cell . selectionStyle = UITableViewCellSelectionStyleNone ;
2013-06-12 19:21:56 -07:00
2011-07-24 16:52:24 -07:00
if ( self . pageFinished ) {
2012-07-09 18:17:01 -07:00
UIImage * img = [ UIImage imageNamed : @ "fleuron.png" ] ;
UIImageView * fleuron = [ [ UIImageView alloc ] initWithImage : img ] ;
2012-08-08 18:23:48 -07:00
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad
&& ! appDelegate . masterContainerViewController . storyTitlesOnLeft
&& UIInterfaceOrientationIsPortrait ( orientation ) ) {
height = height - kTableViewShortRowDifference ;
}
2012-07-09 18:17:01 -07:00
fleuron . frame = CGRectMake ( 0 , 0 , self . view . frame . size . width , height ) ;
fleuron . contentMode = UIViewContentModeCenter ;
2014-03-04 10:21:48 -08:00
fleuron . tag = 99 ;
2012-07-09 18:17:01 -07:00
[ cell . contentView addSubview : fleuron ] ;
2013-09-24 17:18:20 -07:00
cell . backgroundColor = [ UIColor clearColor ] ;
2013-06-12 19:21:56 -07:00
return cell ;
2013-06-13 17:56:58 -07:00
} else { // if ( [ appDelegate . storyLocationsCount ] ) {
2013-06-12 19:21:56 -07:00
NBLoadingCell * loadingCell = [ [ NBLoadingCell alloc ] initWithFrame : CGRectMake ( 0 , 0 , self . view . frame . size . width , height ) ] ;
return loadingCell ;
2011-07-24 16:52:24 -07:00
}
return cell ;
}
2010-06-21 17:17:26 -04:00
2010-06-24 00:22:26 -04:00
# pragma mark -
# pragma mark Table View - Feed List
2010-06-21 17:17:26 -04:00
2012-06-15 19:12:48 -07:00
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section {
2014-02-12 20:09:37 -08:00
NSInteger storyCount = storiesCollection . storyLocationsCount ;
2011-08-21 13:46:43 -07:00
2012-06-15 19:12:48 -07:00
// The + 1 is for the finished / loading bar .
return storyCount + 1 ;
2010-06-21 17:17:26 -04:00
}
2011-08-13 17:08:26 -07:00
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView
cellForRowAtIndexPath : ( NSIndexPath * ) indexPath {
2012-06-29 10:20:06 -07:00
2012-07-22 17:21:32 -07:00
NSString * cellIdentifier ;
2012-06-26 16:24:19 -07:00
NSDictionary * feed ;
2014-02-12 20:09:37 -08:00
if ( indexPath . row >= storiesCollection . storyLocationsCount ) {
2013-09-27 17:23:03 -07:00
return [ self makeLoadingCell ] ;
}
2014-03-04 10:21:48 -08:00
2014-05-20 15:29:16 -07:00
if ( storiesCollection . isRiverView ||
storiesCollection . isSocialView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isSavedView ||
storiesCollection . isReadView ) {
2011-10-26 20:09:28 -07:00
cellIdentifier = @ "FeedRiverDetailCellIdentifier" ;
2011-10-25 09:28:05 -07:00
} else {
cellIdentifier = @ "FeedDetailCellIdentifier" ;
}
2012-07-22 17:08:29 -07:00
2013-09-27 17:23:03 -07:00
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ tableView
dequeueReusableCellWithIdentifier : cellIdentifier ] ;
2012-07-22 17:08:29 -07:00
if ( cell = = nil ) {
cell = [ [ FeedDetailTableCell alloc ] initWithStyle : UITableViewCellStyleDefault
2014-02-25 15:29:05 -08:00
reuseIdentifier : cellIdentifier ] ;
2012-07-22 17:08:29 -07:00
}
2013-09-27 17:23:03 -07:00
2014-03-04 10:21:48 -08:00
for ( UIView * view in cell . contentView . subviews ) {
if ( [ view isKindOfClass : [ UIImageView class ] ] && ( ( UIImageView * ) view ) . tag = = 99 ) {
[ view removeFromSuperview ] ;
break ;
}
}
2011-07-29 09:06:17 -07:00
NSDictionary * story = [ self getStoryAtRow : indexPath . row ] ;
2012-06-26 16:24:19 -07:00
2011-10-26 20:09:28 -07:00
id feedId = [ story objectForKey : @ "story_feed_id" ] ;
2012-06-26 16:24:19 -07:00
NSString * feedIdStr = [ NSString stringWithFormat : @ "%@" , feedId ] ;
2014-05-20 15:29:16 -07:00
if ( storiesCollection . isSocialView ||
storiesCollection . isSocialRiverView ) {
2012-06-26 16:24:19 -07:00
feed = [ appDelegate . dictActiveFeeds objectForKey : feedIdStr ] ;
// this is to catch when a user is already subscribed
if ( ! feed ) {
feed = [ appDelegate . dictFeeds objectForKey : feedIdStr ] ;
}
} else {
feed = [ appDelegate . dictFeeds objectForKey : feedIdStr ] ;
}
2014-02-11 11:52:32 -08:00
cell . inDashboard = self . isDashboardModule ;
2012-07-22 17:08:29 -07:00
NSString * siteTitle = [ feed objectForKey : @ "feed_title" ] ;
cell . siteTitle = siteTitle ;
NSString * title = [ story objectForKey : @ "story_title" ] ;
cell . storyTitle = [ title stringByDecodingHTMLEntities ] ;
2013-10-10 12:58:40 -07:00
2012-07-22 17:08:29 -07:00
cell . storyDate = [ story objectForKey : @ "short_parsed_date" ] ;
2013-10-10 12:58:40 -07:00
cell . storyTimestamp = [ [ story objectForKey : @ "story_timestamp" ] integerValue ] ;
2014-05-20 12:58:04 -07:00
cell . isSaved = [ [ story objectForKey : @ "starred" ] boolValue ] ;
2013-10-01 14:19:12 -07:00
cell . isShared = [ [ story objectForKey : @ "shared" ] boolValue ] ;
2014-02-26 20:18:43 -08:00
cell . storyImageUrl = nil ;
2014-02-13 17:18:29 -08:00
if ( self . showImagePreview &&
[ story objectForKey : @ "image_urls" ] && [ [ story objectForKey : @ "image_urls" ] count ] ) {
2014-02-10 19:21:53 -08:00
cell . storyImageUrl = [ [ story objectForKey : @ "image_urls" ] objectAtIndex : 0 ] ;
}
2011-10-26 20:09:28 -07:00
2010-07-15 00:44:38 -04:00
if ( [ [ story objectForKey : @ "story_authors" ] class ] ! = [ NSNull class ] ) {
2012-07-22 17:08:29 -07:00
cell . storyAuthor = [ [ story objectForKey : @ "story_authors" ] uppercaseString ] ;
2010-07-15 00:44:38 -04:00
} else {
2012-07-22 17:08:29 -07:00
cell . storyAuthor = @ "" ;
2011-10-27 19:05:38 -07:00
}
2012-06-26 16:24:19 -07:00
2014-02-26 20:18:43 -08:00
cell . storyContent = nil ;
2014-02-13 17:18:29 -08:00
if ( self . isDashboardModule || self . showContentPreview ) {
2014-02-10 19:21:53 -08:00
cell . storyContent = [ [ story objectForKey : @ "story_content" ]
stringByConvertingHTMLToPlainText ] ;
}
2012-07-22 17:08:29 -07:00
// feed color bar border
unsigned int colorBorder = 0 ;
2013-02-14 15:36:21 -08:00
NSString * faviconColor = [ feed valueForKey : @ "favicon_fade" ] ;
2010-07-15 00:44:38 -04:00
2013-08-05 18:32:43 -07:00
if ( [ faviconColor class ] = = [ NSNull class ] || ! faviconColor ) {
2013-02-14 15:36:21 -08:00
faviconColor = @ "707070" ;
2012-07-22 17:08:29 -07:00
}
NSScanner * scannerBorder = [ NSScanner scannerWithString : faviconColor ] ;
[ scannerBorder scanHexInt : & colorBorder ] ;
2010-06-21 17:17:26 -04:00
2012-07-22 17:08:29 -07:00
cell . feedColorBar = UIColorFromRGB ( colorBorder ) ;
2012-07-22 14:23:50 -07:00
2012-07-22 17:08:29 -07:00
// feed color bar border
2013-02-14 15:36:21 -08:00
NSString * faviconFade = [ feed valueForKey : @ "favicon_color" ] ;
2013-08-05 18:32:43 -07:00
if ( [ faviconFade class ] = = [ NSNull class ] || ! faviconFade ) {
2012-07-22 14:23:50 -07:00
faviconFade = @ "505050" ;
}
2012-07-22 17:08:29 -07:00
scannerBorder = [ NSScanner scannerWithString : faviconFade ] ;
2012-07-22 14:23:50 -07:00
[ scannerBorder scanHexInt : & colorBorder ] ;
2012-07-22 17:08:29 -07:00
cell . feedColorBarTopBorder = UIColorFromRGB ( colorBorder ) ;
// favicon
2014-02-24 18:56:51 -08:00
cell . siteFavicon = [ appDelegate getFavicon : feedIdStr ] ;
2014-02-26 18:01:10 -08:00
cell . hasAlpha = NO ;
2012-07-22 17:08:29 -07:00
// undread indicator
2014-03-21 16:23:51 -07:00
int score = [ NewsBlurAppDelegate computeStoryScore : [ story objectForKey : @ "intelligence" ] ] ;
2012-08-10 18:10:07 -07:00
cell . storyScore = score ;
2012-10-16 17:24:01 -07:00
2014-02-12 20:09:37 -08:00
cell . isRead = ! [ storiesCollection isStoryUnread : story ] ;
2012-08-08 18:23:48 -07:00
2014-02-26 20:18:43 -08:00
cell . isShort = NO ;
2012-08-08 18:23:48 -07:00
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
2014-02-21 16:06:49 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad &&
! self . isDashboardModule &&
! appDelegate . masterContainerViewController . storyTitlesOnLeft &&
UIInterfaceOrientationIsPortrait ( orientation ) ) {
2012-08-08 18:23:48 -07:00
cell . isShort = YES ;
}
2014-02-26 20:18:43 -08:00
cell . isRiverOrSocial = NO ;
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ||
2014-05-20 15:29:16 -07:00
storiesCollection . isSavedView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isReadView ||
2014-02-12 20:09:37 -08:00
storiesCollection . isSocialView ||
storiesCollection . isSocialRiverView ) {
2012-07-23 10:57:11 -07:00
cell . isRiverOrSocial = YES ;
}
2012-07-22 14:23:50 -07:00
2014-03-04 10:21:48 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad && ! self . isDashboardModule ) {
2014-02-12 20:09:37 -08:00
NSInteger rowIndex = [ storiesCollection locationOfActiveStory ] ;
2012-08-07 09:57:21 -07:00
if ( rowIndex = = indexPath . row ) {
2014-10-01 14:23:57 -07:00
[ tableView selectRowAtIndexPath : indexPath animated : NO scrollPosition : UITableViewScrollPositionNone ] ;
2012-08-07 09:57:21 -07:00
}
}
2013-10-01 15:38:29 -07:00
[ cell setupGestures ] ;
2014-02-25 15:29:05 -08:00
[ cell setNeedsDisplay ] ;
2013-09-27 17:23:03 -07:00
return cell ;
2012-07-22 17:08:29 -07:00
}
2013-09-24 17:18:20 -07:00
- ( void ) loadStory : ( FeedDetailTableCell * ) cell atRow : ( NSInteger ) row {
2014-02-12 20:09:37 -08:00
NSInteger storyIndex = [ storiesCollection indexFromLocation : row ] ;
appDelegate . activeStory = [ [ storiesCollection activeFeedStories ] objectAtIndex : storyIndex ] ;
if ( [ storiesCollection isStoryUnread : appDelegate . activeStory ] ) {
2014-03-05 14:13:49 -08:00
[ storiesCollection markStoryRead : appDelegate . activeStory ] ;
[ storiesCollection syncStoryAsRead : appDelegate . activeStory ] ;
2013-10-04 12:37:51 -07:00
}
2014-02-24 18:35:35 -08:00
[ self setTitleForBackButton ] ;
2012-07-16 19:45:14 -07:00
[ appDelegate loadStoryDetailView ] ;
2013-10-04 16:41:22 -07:00
[ self redrawUnreadStory ] ;
2012-07-16 19:45:14 -07:00
}
2014-02-24 18:35:35 -08:00
- ( void ) setTitleForBackButton {
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPhone ) {
NSString * feedTitle ;
if ( storiesCollection . isRiverView ) {
if ( [ storiesCollection . activeFolder isEqualToString : @ "river_blurblogs" ] ) {
feedTitle = @ "All Shared Stories" ;
} else if ( [ storiesCollection . activeFolder isEqualToString : @ "river_global" ] ) {
feedTitle = @ "Global Shared Stories" ;
} else if ( [ storiesCollection . activeFolder isEqualToString : @ "everything" ] ) {
feedTitle = @ "All Stories" ;
2014-05-20 15:29:16 -07:00
} else if ( storiesCollection . isSavedView && storiesCollection . activeSavedStoryTag ) {
2014-05-20 15:46:38 -07:00
feedTitle = storiesCollection . activeSavedStoryTag ;
2014-10-22 16:39:37 -07:00
} else if ( [ storiesCollection . activeFolder isEqualToString : @ "read_stories" ] ) {
feedTitle = @ "Read Stories" ;
2014-02-24 18:35:35 -08:00
} else if ( [ storiesCollection . activeFolder isEqualToString : @ "saved_stories" ] ) {
feedTitle = @ "Saved Stories" ;
} else {
feedTitle = storiesCollection . activeFolder ;
}
} else {
feedTitle = [ storiesCollection . activeFeed objectForKey : @ "feed_title" ] ;
}
if ( [ feedTitle length ] >= 12 ) {
feedTitle = [ NSString stringWithFormat : @ "%@..." , [ feedTitle substringToIndex : MIN ( 9 , [ feedTitle length ] ) ] ] ;
}
UIBarButtonItem * newBackButton = [ [ UIBarButtonItem alloc ] initWithTitle : feedTitle style : UIBarButtonItemStylePlain target : nil action : nil ] ;
[ self . navigationItem setBackBarButtonItem : newBackButton ] ;
}
}
2012-10-16 17:24:01 -07:00
- ( void ) redrawUnreadStory {
2014-02-12 20:09:37 -08:00
NSInteger rowIndex = [ storiesCollection locationOfActiveStory ] ;
2012-10-16 17:24:01 -07:00
NSIndexPath * indexPath = [ NSIndexPath indexPathForRow : rowIndex inSection : 0 ] ;
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ self . storyTitlesTable cellForRowAtIndexPath : indexPath ] ;
2014-02-12 20:09:37 -08:00
cell . isRead = ! [ storiesCollection isStoryUnread : appDelegate . activeStory ] ;
2013-08-05 17:29:42 -07:00
cell . isShared = [ [ appDelegate . activeStory objectForKey : @ "shared" ] boolValue ] ;
2014-05-20 12:58:04 -07:00
cell . isSaved = [ [ appDelegate . activeStory objectForKey : @ "starred" ] boolValue ] ;
2012-10-16 17:24:01 -07:00
[ cell setNeedsDisplay ] ;
}
2012-07-29 20:55:11 -07:00
- ( void ) changeActiveStoryTitleCellLayout {
2014-02-12 20:09:37 -08:00
NSInteger rowIndex = [ storiesCollection locationOfActiveStory ] ;
2012-07-27 15:01:03 -07:00
NSIndexPath * indexPath = [ NSIndexPath indexPathForRow : rowIndex inSection : 0 ] ;
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ self . storyTitlesTable cellForRowAtIndexPath : indexPath ] ;
cell . isRead = YES ;
[ cell setNeedsLayout ] ;
}
2010-06-21 17:17:26 -04:00
- ( void ) tableView : ( UITableView * ) tableView didSelectRowAtIndexPath : ( NSIndexPath * ) indexPath {
2014-02-12 20:09:37 -08:00
if ( indexPath . row < storiesCollection . storyLocationsCount ) {
2012-07-29 13:24:03 -07:00
// mark the cell as read
2014-02-10 19:21:53 -08:00
if ( self . isDashboardModule ) {
2014-02-12 20:09:37 -08:00
NSInteger storyIndex = [ storiesCollection indexFromLocation : indexPath . row ] ;
NSDictionary * activeStory = [ [ storiesCollection activeFeedStories ] objectAtIndex : storyIndex ] ;
2014-02-20 14:52:27 -08:00
appDelegate . activeStory = activeStory ;
2014-02-18 18:38:07 -08:00
[ appDelegate openDashboardRiverForStory : [ activeStory objectForKey : @ "story_hash" ] showFindingStory : NO ] ;
2014-12-02 18:36:38 -08:00
2014-02-10 19:21:53 -08:00
} else {
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ tableView cellForRowAtIndexPath : indexPath ] ;
2014-03-03 12:52:26 -08:00
NSInteger storyIndex = [ storiesCollection indexFromLocation : indexPath . row ] ;
NSDictionary * story = [ [ storiesCollection activeFeedStories ] objectAtIndex : storyIndex ] ;
2014-04-18 15:50:32 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad &&
appDelegate . activeStory &&
2014-03-03 12:52:26 -08:00
[ [ story objectForKey : @ "story_hash" ]
isEqualToString : [ appDelegate . activeStory objectForKey : @ "story_hash" ] ] ) {
return ;
}
2014-02-10 19:21:53 -08:00
[ self loadStory : cell atRow : indexPath . row ] ;
}
2014-12-02 18:36:38 -08:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
[ appDelegate . dashboardViewController . storiesModule . view endEditing : YES ] ;
}
2011-07-24 16:52:24 -07:00
}
2010-06-21 17:17:26 -04:00
}
2013-06-12 19:21:56 -07:00
- ( void ) tableView : ( UITableView * ) tableView didEndDisplayingCell : ( UITableViewCell * ) cell forRowAtIndexPath : ( NSIndexPath * ) indexPath {
if ( [ cell class ] = = [ NBLoadingCell class ] ) {
[ ( NBLoadingCell * ) cell endAnimation ] ;
}
}
- ( void ) tableView : ( UITableView * ) tableView willDisplayCell : ( UITableViewCell * ) cell forRowAtIndexPath : ( NSIndexPath * ) indexPath {
if ( [ cell class ] = = [ NBLoadingCell class ] ) {
[ ( NBLoadingCell * ) cell animate ] ;
}
2014-02-19 18:59:14 -08:00
if ( [ indexPath row ] = = ( ( NSIndexPath * ) [ [ tableView indexPathsForVisibleRows ] lastObject ] ) . row ) {
[ self performSelector : @ selector ( checkScroll )
withObject : nil
afterDelay : 0.1 ] ;
}
2013-06-12 19:21:56 -07:00
}
2014-02-21 16:06:49 -08:00
2013-10-17 18:56:14 -07:00
- ( CGFloat ) tableView : ( UITableView * ) tableView
heightForRowAtIndexPath : ( NSIndexPath * ) indexPath {
2013-06-12 17:36:09 -07:00
2014-02-12 20:09:37 -08:00
NSInteger storyCount = storiesCollection . storyLocationsCount ;
2013-06-12 19:21:56 -07:00
if ( storyCount && indexPath . row = = storyCount ) {
2013-06-11 23:00:00 -07:00
return 40 ;
2014-02-12 20:09:37 -08:00
} else if ( storiesCollection . isRiverView ||
2014-05-20 15:29:16 -07:00
storiesCollection . isSavedView ||
2014-10-22 17:03:48 -07:00
storiesCollection . isReadView ||
2014-02-12 20:09:37 -08:00
storiesCollection . isSocialView ||
storiesCollection . isSocialRiverView ) {
2013-09-25 17:43:00 -07:00
NSInteger height = kTableViewRiverRowHeight ;
2014-02-21 16:06:49 -08:00
if ( [ self isShortTitles ] ) {
2012-08-08 18:23:48 -07:00
height = height - kTableViewShortRowDifference ;
}
2013-11-23 13:03:14 -08:00
UIFontDescriptor * fontDescriptor = [ self fontDescriptorUsingPreferredSize : UIFontTextStyleCaption1 ] ;
2013-10-17 18:56:14 -07:00
UIFont * font = [ UIFont fontWithDescriptor : fontDescriptor size : 0.0 ] ;
2014-02-21 16:06:49 -08:00
if ( [ self isShortTitles ] && self . showContentPreview ) {
return height + font . pointSize * 3.25 ;
} else if ( self . isDashboardModule || self . showContentPreview ) {
2014-02-10 19:21:53 -08:00
return height + font . pointSize * 5 ;
} else {
return height + font . pointSize * 2 ;
}
2011-07-24 16:52:24 -07:00
} else {
2013-09-25 17:43:00 -07:00
NSInteger height = kTableViewRowHeight ;
2014-02-21 16:06:49 -08:00
if ( [ self isShortTitles ] ) {
2012-08-08 18:23:48 -07:00
height = height - kTableViewShortRowDifference ;
}
2013-11-23 13:03:14 -08:00
UIFontDescriptor * fontDescriptor = [ self fontDescriptorUsingPreferredSize : UIFontTextStyleCaption1 ] ;
2013-10-17 18:56:14 -07:00
UIFont * font = [ UIFont fontWithDescriptor : fontDescriptor size : 0.0 ] ;
2014-02-21 16:06:49 -08:00
if ( [ self isShortTitles ] && self . showContentPreview ) {
return height + font . pointSize * 3.25 ;
} else if ( self . isDashboardModule || self . showContentPreview ) {
2014-02-10 19:21:53 -08:00
return height + font . pointSize * 5 ;
} else {
return height + font . pointSize * 2 ;
}
2011-07-24 16:52:24 -07:00
}
2010-07-15 00:44:38 -04:00
}
2013-09-24 17:18:20 -07:00
- ( CGFloat ) tableView : ( UITableView * ) tableView heightForFooterInSection : ( NSInteger ) section {
// This will create a "invisible" footer
return 0.01 f ;
}
2011-07-20 22:21:11 -07:00
- ( void ) scrollViewDidScroll : ( UIScrollView * ) scroll {
2011-07-29 21:56:54 -07:00
[ self checkScroll ] ;
}
2013-11-23 13:03:14 -08:00
- ( UIFontDescriptor * ) fontDescriptorUsingPreferredSize : ( NSString * ) textStyle {
2014-10-01 14:54:06 -07:00
UIFontDescriptor * fontDescriptor = appDelegate . fontDescriptorTitleSize ;
if ( fontDescriptor ) return fontDescriptor ;
2014-09-26 18:35:40 -07:00
2014-10-01 14:54:06 -07:00
fontDescriptor = [ UIFontDescriptor preferredFontDescriptorWithTextStyle : textStyle ] ;
2013-11-23 13:03:14 -08:00
NSUserDefaults * userPreferences = [ NSUserDefaults standardUserDefaults ] ;
if ( ! [ userPreferences boolForKey : @ "use_system_font_size" ] ) {
if ( [ [ userPreferences stringForKey : @ "feed_list_font_size" ] isEqualToString : @ "xs" ] ) {
2014-10-01 14:54:06 -07:00
fontDescriptor = [ fontDescriptor fontDescriptorWithSize : 10.0 f ] ;
2013-11-23 13:03:14 -08:00
} else if ( [ [ userPreferences stringForKey : @ "feed_list_font_size" ] isEqualToString : @ "small" ] ) {
2014-10-01 14:54:06 -07:00
fontDescriptor = [ fontDescriptor fontDescriptorWithSize : 11.0 f ] ;
2013-11-23 13:03:14 -08:00
} else if ( [ [ userPreferences stringForKey : @ "feed_list_font_size" ] isEqualToString : @ "medium" ] ) {
2014-10-01 14:54:06 -07:00
fontDescriptor = [ fontDescriptor fontDescriptorWithSize : 12.0 f ] ;
2013-11-23 13:03:14 -08:00
} else if ( [ [ userPreferences stringForKey : @ "feed_list_font_size" ] isEqualToString : @ "large" ] ) {
2014-10-01 14:54:06 -07:00
fontDescriptor = [ fontDescriptor fontDescriptorWithSize : 14.0 f ] ;
2013-11-23 13:03:14 -08:00
} else if ( [ [ userPreferences stringForKey : @ "feed_list_font_size" ] isEqualToString : @ "xl" ] ) {
2014-10-01 14:54:06 -07:00
fontDescriptor = [ fontDescriptor fontDescriptorWithSize : 16.0 f ] ;
2013-11-23 13:03:14 -08:00
}
}
2014-10-01 14:54:06 -07:00
return fontDescriptor ;
2013-11-23 13:03:14 -08:00
}
2014-02-21 16:06:49 -08:00
- ( BOOL ) isShortTitles {
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
return UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad &&
! appDelegate . masterContainerViewController . storyTitlesOnLeft &&
UIInterfaceOrientationIsPortrait ( orientation ) &&
! self . isDashboardModule ;
}
2014-02-27 17:12:34 -08:00
2011-07-29 21:56:54 -07:00
- ( void ) checkScroll {
NSInteger currentOffset = self . storyTitlesTable . contentOffset . y ;
NSInteger maximumOffset = self . storyTitlesTable . contentSize . height - self . storyTitlesTable . frame . size . height ;
2011-07-20 22:21:11 -07:00
2014-02-19 18:59:14 -08:00
if ( self . pageFetching ) {
return ;
}
2014-02-12 20:09:37 -08:00
if ( ! [ storiesCollection . activeFeedStories count ] ) return ;
2013-09-05 18:48:23 -07:00
2014-02-12 15:48:16 -08:00
if ( maximumOffset - currentOffset <= 500.0 ||
2012-07-16 22:35:28 -07:00
( appDelegate . inFindingStoryMode ) ) {
2014-02-27 17:12:34 -08:00
if ( storiesCollection . isRiverView && storiesCollection . activeFolder ) {
2014-02-27 14:49:33 -08:00
[ self fetchRiverPage : storiesCollection . feedPage + 1 withCallback : nil ] ;
2011-10-26 08:40:31 -07:00
} else {
2014-02-27 14:49:33 -08:00
[ self fetchFeedDetail : storiesCollection . feedPage + 1 withCallback : nil ] ;
2011-10-26 08:40:31 -07:00
}
2011-07-20 22:21:11 -07:00
}
}
2012-07-16 19:45:14 -07:00
- ( void ) changeIntelligence : ( NSInteger ) newLevel {
2011-07-29 09:06:17 -07:00
NSInteger previousLevel = [ appDelegate selectedIntelligence ] ;
2011-08-02 09:16:54 -07:00
if ( newLevel = = previousLevel ) return ;
2011-07-29 21:27:37 -07:00
if ( newLevel < previousLevel ) {
[ appDelegate setSelectedIntelligence : newLevel ] ;
2012-07-16 19:45:14 -07:00
NSUserDefaults * userPreferences = [ NSUserDefaults standardUserDefaults ] ;
[ userPreferences setInteger : ( newLevel + 1 ) forKey : @ "selectedIntelligence" ] ;
[ userPreferences synchronize ] ;
2014-02-12 20:09:37 -08:00
[ storiesCollection calculateStoryLocations ] ;
2011-07-29 21:27:37 -07:00
}
2012-07-16 19:45:14 -07:00
2013-09-11 17:05:47 -07:00
[ self . storyTitlesTable reloadData ] ;
2011-07-29 09:06:17 -07:00
}
- ( NSDictionary * ) getStoryAtRow : ( NSInteger ) indexPathRow {
2014-03-04 10:21:48 -08:00
if ( indexPathRow >= [ [ storiesCollection activeFeedStoryLocations ] count ] ) return nil ;
id location = [ [ storiesCollection activeFeedStoryLocations ] objectAtIndex : indexPathRow ] ;
if ( ! location ) return nil ;
NSInteger row = [ location intValue ] ;
2014-02-12 20:09:37 -08:00
return [ storiesCollection . activeFeedStories objectAtIndex : row ] ;
2011-07-24 22:23:38 -07:00
}
2013-09-27 17:23:03 -07:00
# pragma mark - MCSwipeTableViewCellDelegate
// When the user starts swiping the cell this method is called
- ( void ) swipeTableViewCellDidStartSwiping : ( MCSwipeTableViewCell * ) cell {
2013-09-30 16:18:11 -07:00
// NSLog ( @ "Did start swiping the cell!" ) ;
2013-09-27 17:23:03 -07:00
}
2013-09-30 16:18:11 -07:00
// When the user is dragging , this method is called and return the dragged percentage from the border
- ( void ) swipeTableViewCell : ( MCSwipeTableViewCell * ) cell didSwipWithPercentage : ( CGFloat ) percentage {
// NSLog ( @ "Did swipe with percentage : %f" , percentage ) ;
}
2013-09-27 17:23:03 -07:00
2014-02-20 18:23:58 -08:00
- ( void ) swipeTableViewCell : ( MCSwipeTableViewCell * ) cell
didEndSwipingSwipingWithState : ( MCSwipeTableViewCellState ) state
mode : ( MCSwipeTableViewCellMode ) mode {
2013-10-01 14:19:12 -07:00
NSIndexPath * indexPath = [ self . storyTitlesTable indexPathForCell : cell ] ;
2013-10-08 13:57:15 -07:00
if ( ! indexPath ) {
// This can happen if the user swipes on a cell that is being refreshed .
return ;
}
2014-02-12 20:09:37 -08:00
NSInteger storyIndex = [ storiesCollection indexFromLocation : indexPath . row ] ;
NSDictionary * story = [ [ storiesCollection activeFeedStories ] objectAtIndex : storyIndex ] ;
2013-10-01 14:19:12 -07:00
if ( state = = MCSwipeTableViewCellState1 ) {
// Saved
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStorySaved : story ] ;
2013-10-01 14:19:12 -07:00
[ self . storyTitlesTable reloadRowsAtIndexPaths : @ [ indexPath ]
withRowAnimation : UITableViewRowAnimationFade ] ;
} else if ( state = = MCSwipeTableViewCellState3 ) {
// Read
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStoryUnread : story ] ;
2013-10-01 15:38:29 -07:00
[ self . storyTitlesTable reloadRowsAtIndexPaths : @ [ indexPath ]
withRowAnimation : UITableViewRowAnimationFade ] ;
2014-03-21 16:23:51 -07:00
if ( self . isDashboardModule ) {
[ appDelegate refreshFeedCount : [ story objectForKey : @ "story_feed_id" ] ] ;
}
2013-10-01 14:19:12 -07:00
}
2013-09-27 17:23:03 -07:00
}
2011-10-17 09:28:15 -07:00
# pragma mark -
# pragma mark Feed Actions
2013-10-09 14:54:49 -07:00
- ( void ) handleLongPress : ( UILongPressGestureRecognizer * ) gestureRecognizer {
CGPoint p = [ gestureRecognizer locationInView : self . storyTitlesTable ] ;
NSIndexPath * indexPath = [ self . storyTitlesTable indexPathForRowAtPoint : p ] ;
FeedDetailTableCell * cell = ( FeedDetailTableCell * ) [ self . storyTitlesTable cellForRowAtIndexPath : indexPath ] ;
if ( gestureRecognizer . state ! = UIGestureRecognizerStateBegan ) return ;
if ( indexPath = = nil ) return ;
NSDictionary * story = [ self getStoryAtRow : indexPath . row ] ;
2014-03-04 18:09:43 -08:00
NSUserDefaults * preferences = [ NSUserDefaults standardUserDefaults ] ;
NSString * longPressStoryTitle = [ preferences stringForKey : @ "long_press_story_title" ] ;
if ( [ longPressStoryTitle isEqualToString : @ "open_send_to" ] ) {
2014-03-05 14:13:49 -08:00
appDelegate . activeStory = story ;
2014-03-04 18:09:43 -08:00
[ appDelegate showSendTo : self sender : cell ] ;
} else if ( [ longPressStoryTitle isEqualToString : @ "mark_unread" ] ) {
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStoryUnread : story ] ;
[ self . storyTitlesTable reloadRowsAtIndexPaths : @ [ indexPath ]
withRowAnimation : UITableViewRowAnimationFade ] ;
2014-03-04 18:09:43 -08:00
} else if ( [ longPressStoryTitle isEqualToString : @ "save_story" ] ) {
2014-03-05 14:13:49 -08:00
[ storiesCollection toggleStorySaved : story ] ;
[ self . storyTitlesTable reloadRowsAtIndexPaths : @ [ indexPath ]
withRowAnimation : UITableViewRowAnimationFade ] ;
2014-03-04 18:09:43 -08:00
} else if ( [ longPressStoryTitle isEqualToString : @ "train_story" ] ) {
[ appDelegate openTrainStory : cell ] ;
}
2013-10-09 14:54:49 -07:00
}
2011-11-09 09:51:42 -08:00
- ( void ) markFeedsReadWithAllStories : ( BOOL ) includeHidden {
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView && includeHidden &&
[ storiesCollection . activeFolder isEqualToString : @ "everything" ] ) {
2012-12-10 18:34:13 -08:00
// Mark folder as read
2013-06-18 21:23:20 -04:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/mark_all_as_read" ,
2012-12-10 18:34:13 -08:00
NEWSBLUR_URL ] ;
NSURL * url = [ NSURL URLWithString : urlString ] ;
ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : url ] ;
[ request setDelegate : nil ] ;
[ request startAsynchronous ] ;
[ appDelegate markActiveFolderAllRead ] ;
2014-02-12 20:09:37 -08:00
} else if ( storiesCollection . isRiverView && includeHidden ) {
2011-11-09 09:51:42 -08:00
// Mark folder as read
2013-06-18 21:23:20 -04:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/mark_feed_as_read" ,
2011-11-09 09:51:42 -08:00
NEWSBLUR_URL ] ;
NSURL * url = [ NSURL URLWithString : urlString ] ;
ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : url ] ;
2014-02-12 20:09:37 -08:00
for ( id feed_id in [ appDelegate . dictFolders objectForKey : storiesCollection . activeFolder ] ) {
2011-11-29 17:57:20 -08:00
[ request addPostValue : feed_id forKey : @ "feed_id" ] ;
}
2014-02-12 20:09:37 -08:00
[ request setUserInfo : @ { @ "feeds" : storiesCollection . activeFolderFeeds } ] ;
2013-09-30 16:18:11 -07:00
[ request setDelegate : self ] ;
[ request setDidFinishSelector : @ selector ( finishMarkAllAsRead : ) ] ;
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
2011-11-09 09:51:42 -08:00
[ request startAsynchronous ] ;
2011-11-10 18:28:22 -08:00
[ appDelegate markActiveFolderAllRead ] ;
2014-02-12 20:09:37 -08:00
} else if ( ! storiesCollection . isRiverView && includeHidden ) {
2011-11-09 09:51:42 -08:00
// Mark feed as read
2013-06-18 21:23:20 -04:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/mark_feed_as_read" ,
2011-11-04 08:46:24 -07:00
NEWSBLUR_URL ] ;
NSURL * url = [ NSURL URLWithString : urlString ] ;
ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : url ] ;
2014-02-12 20:09:37 -08:00
[ request setPostValue : [ storiesCollection . activeFeed objectForKey : @ "id" ] forKey : @ "feed_id" ] ;
2012-08-01 23:16:04 -07:00
[ request setDidFinishSelector : @ selector ( finishMarkAllAsRead : ) ] ;
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
2014-02-12 20:09:37 -08:00
[ request setUserInfo : @ { @ "feeds" : @ [ [ storiesCollection . activeFeed objectForKey : @ "id" ] ] } ] ;
2012-08-01 23:16:04 -07:00
[ request setDelegate : self ] ;
2011-11-04 08:46:24 -07:00
[ request startAsynchronous ] ;
2014-02-12 20:09:37 -08:00
[ appDelegate markFeedAllRead : [ storiesCollection . activeFeed objectForKey : @ "id" ] ] ;
2013-08-06 18:08:55 -07:00
} else if ( ! includeHidden ) {
2011-11-09 09:51:42 -08:00
// Mark visible stories as read
2011-11-10 18:28:22 -08:00
NSDictionary * feedsStories = [ appDelegate markVisibleStoriesRead ] ;
2013-06-18 21:23:20 -04:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/mark_feed_stories_as_read" ,
2011-11-09 09:51:42 -08:00
NEWSBLUR_URL ] ;
NSURL * url = [ NSURL URLWithString : urlString ] ;
ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : url ] ;
2011-11-10 18:28:22 -08:00
[ request setPostValue : [ feedsStories JSONRepresentation ] forKey : @ "feeds_stories" ] ;
2012-08-01 23:16:04 -07:00
[ request setDelegate : self ] ;
2013-09-30 16:18:11 -07:00
[ request setUserInfo : @ { @ "stories" : feedsStories } ] ;
2012-08-01 23:16:04 -07:00
[ request setDidFinishSelector : @ selector ( finishMarkAllAsRead : ) ] ;
2013-08-06 18:08:55 -07:00
[ request setDidFailSelector : @ selector ( requestFailedMarkStoryRead : ) ] ;
2011-11-09 09:51:42 -08:00
[ request startAsynchronous ] ;
2012-08-01 23:16:04 -07:00
}
2013-09-30 16:18:11 -07:00
2012-08-01 23:16:04 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
2012-08-06 19:21:39 -07:00
[ appDelegate . navigationController popToRootViewControllerAnimated : YES ] ;
2012-08-01 23:16:04 -07:00
[ appDelegate . masterContainerViewController transitionFromFeedDetail ] ;
} else {
2011-11-09 09:51:42 -08:00
[ appDelegate . navigationController
popToViewController : [ appDelegate . navigationController . viewControllers
objectAtIndex : 0 ]
animated : YES ] ;
2011-11-04 08:46:24 -07:00
}
}
2013-08-06 18:08:55 -07:00
- ( void ) requestFailedMarkStoryRead : ( ASIFormDataRequest * ) request {
// [ self informError : @ "Failed to mark story as read" ] ;
2013-09-30 16:18:11 -07:00
[ appDelegate markStoriesRead : [ request . userInfo objectForKey : @ "stories" ]
2013-10-08 19:33:11 -07:00
inFeeds : [ request . userInfo objectForKey : @ "feeds" ]
cutoffTimestamp : nil ] ;
2013-08-06 18:08:55 -07:00
}
- ( void ) finishMarkAllAsRead : ( ASIFormDataRequest * ) request {
if ( request . responseStatusCode ! = 200 ) {
[ self requestFailedMarkStoryRead : request ] ;
2013-09-30 16:18:11 -07:00
return ;
2013-08-06 18:08:55 -07:00
}
2012-08-01 23:16:04 -07:00
2013-10-17 11:21:37 -07:00
if ( [ request . userInfo objectForKey : @ "feeds" ] ) {
[ appDelegate markFeedReadInCache : @ [ [ request . userInfo objectForKey : @ "feeds" ] ] ] ;
}
2012-08-01 23:16:04 -07:00
}
2011-11-04 08:46:24 -07:00
- ( IBAction ) doOpenMarkReadActionSheet : ( id ) sender {
2012-08-08 19:31:33 -07:00
// already displaying action sheet ?
if ( self . actionSheet_ ) {
[ self . actionSheet_ dismissWithClickedButtonIndex : -1 animated : YES ] ;
self . actionSheet_ = nil ;
return ;
}
2011-11-29 17:57:20 -08:00
// Individual sites just get marked as read , no action sheet needed .
2014-02-12 20:09:37 -08:00
if ( ! storiesCollection . isRiverView ) {
2011-11-29 17:57:20 -08:00
[ self markFeedsReadWithAllStories : YES ] ;
return ;
}
2014-02-12 20:09:37 -08:00
NSString * title = storiesCollection . isRiverView ?
storiesCollection . activeFolder :
[ storiesCollection . activeFeed objectForKey : @ "feed_title" ] ;
2011-11-04 08:46:24 -07:00
UIActionSheet * options = [ [ UIActionSheet alloc ]
2011-11-09 09:51:42 -08:00
initWithTitle : title
2011-11-04 08:46:24 -07:00
delegate : self
cancelButtonTitle : nil
destructiveButtonTitle : nil
otherButtonTitles : nil ] ;
2012-08-08 19:31:33 -07:00
self . actionSheet_ = options ;
2014-02-12 20:09:37 -08:00
[ storiesCollection calculateStoryLocations ] ;
NSInteger visibleUnreadCount = storiesCollection . visibleUnreadCount ;
2013-09-25 17:43:00 -07:00
NSInteger totalUnreadCount = [ appDelegate unreadCount ] ;
2011-11-09 09:51:42 -08:00
NSArray * buttonTitles = nil ;
2011-12-05 18:10:40 -08:00
BOOL showVisible = YES ;
BOOL showEntire = YES ;
2012-12-10 18:34:13 -08:00
// if ( [ appDelegate . activeFolder isEqualToString : @ "everything" ] ) showEntire = NO ;
2012-10-17 15:07:53 -07:00
if ( visibleUnreadCount >= totalUnreadCount || visibleUnreadCount <= 0 ) showVisible = NO ;
2011-12-05 18:10:40 -08:00
NSString * entireText = [ NSString stringWithFormat : @ "Mark %@ read" ,
2014-02-12 20:09:37 -08:00
storiesCollection . isRiverView ?
[ storiesCollection . activeFolder isEqualToString : @ "everything" ] ?
2012-12-10 18:34:13 -08:00
@ "everything" :
2011-12-05 18:10:40 -08:00
@ "entire folder" :
@ "this site" ] ;
NSString * visibleText = [ NSString stringWithFormat : @ "Mark %@ read" ,
visibleUnreadCount = = 1 ? @ "this story as" :
2013-09-25 17:43:00 -07:00
[ NSString stringWithFormat : @ "these %ld stories" ,
( long ) visibleUnreadCount ] ] ;
2011-12-05 18:10:40 -08:00
if ( showVisible && showEntire ) {
2011-11-09 09:51:42 -08:00
buttonTitles = [ NSArray arrayWithObjects : visibleText , entireText , nil ] ;
options . destructiveButtonIndex = 1 ;
2011-12-05 18:10:40 -08:00
} else if ( showVisible && ! showEntire ) {
buttonTitles = [ NSArray arrayWithObjects : visibleText , nil ] ;
options . destructiveButtonIndex = -1 ;
} else if ( ! showVisible && showEntire ) {
buttonTitles = [ NSArray arrayWithObjects : entireText , nil ] ;
options . destructiveButtonIndex = 0 ;
2011-11-09 09:51:42 -08:00
}
2011-11-04 08:46:24 -07:00
for ( id title in buttonTitles ) {
[ options addButtonWithTitle : title ] ;
}
options . cancelButtonIndex = [ options addButtonWithTitle : @ "Cancel" ] ;
2011-11-05 16:25:04 -07:00
options . tag = kMarkReadActionSheet ;
2012-08-08 19:31:33 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
[ options showFromBarButtonItem : self . feedMarkReadButton animated : YES ] ;
} else {
[ options showInView : self . view ] ;
}
2011-11-04 08:46:24 -07:00
}
2011-10-17 09:28:15 -07:00
- ( void ) actionSheet : ( UIActionSheet * ) actionSheet clickedButtonAtIndex : ( NSInteger ) buttonIndex {
2011-12-03 18:22:14 -08:00
// NSLog ( @ "Action option #%d on %d" , buttonIndex , actionSheet . tag ) ;
2011-11-05 16:25:04 -07:00
if ( actionSheet . tag = = 1 ) {
2014-02-12 20:09:37 -08:00
NSInteger visibleUnreadCount = storiesCollection . visibleUnreadCount ;
2013-09-25 17:43:00 -07:00
NSInteger totalUnreadCount = [ appDelegate unreadCount ] ;
2011-12-05 18:10:40 -08:00
BOOL showVisible = YES ;
BOOL showEntire = YES ;
2012-12-10 18:34:13 -08:00
// if ( [ appDelegate . activeFolder isEqualToString : @ "everything" ] ) showEntire = NO ;
2011-12-05 18:10:40 -08:00
if ( visibleUnreadCount >= totalUnreadCount || visibleUnreadCount <= 0 ) showVisible = NO ;
2011-12-03 18:22:14 -08:00
// NSLog ( @ "Counts: %d %d = %d" , visibleUnreadCount , totalUnreadCount , visibleUnreadCount >= totalUnreadCount || visibleUnreadCount <= 0 ) ;
2013-02-06 18:24:41 -08:00
2011-12-05 18:10:40 -08:00
if ( showVisible && showEntire ) {
2011-11-09 09:51:42 -08:00
if ( buttonIndex = = 0 ) {
2011-12-20 08:56:55 -08:00
[ self markFeedsReadWithAllStories : NO ] ;
} else if ( buttonIndex = = 1 ) {
[ self markFeedsReadWithAllStories : YES ] ;
}
2011-12-05 18:10:40 -08:00
} else if ( showVisible && ! showEntire ) {
2011-11-09 09:51:42 -08:00
if ( buttonIndex = = 0 ) {
[ self markFeedsReadWithAllStories : NO ] ;
2011-12-05 18:10:40 -08:00
}
} else if ( ! showVisible && showEntire ) {
if ( buttonIndex = = 0 ) {
2011-11-09 09:51:42 -08:00
[ self markFeedsReadWithAllStories : YES ] ;
2011-12-05 18:10:40 -08:00
}
2011-11-09 09:51:42 -08:00
}
2011-11-05 16:25:04 -07:00
} else if ( actionSheet . tag = = 2 ) {
if ( buttonIndex = = 0 ) {
[ self confirmDeleteSite ] ;
2011-12-03 18:22:14 -08:00
} else if ( buttonIndex = = 1 ) {
[ self openMoveView ] ;
2012-06-27 15:38:51 -07:00
} else if ( buttonIndex = = 2 ) {
[ self instafetchFeed ] ;
2011-11-05 16:25:04 -07:00
}
2013-10-08 19:33:11 -07:00
}
2011-10-17 09:28:15 -07:00
}
2012-08-08 19:31:33 -07:00
- ( void ) actionSheet : ( UIActionSheet * ) actionSheet didDismissWithButtonIndex : ( NSInteger ) buttonIndex {
// just set to nil
actionSheet_ = nil ;
}
2012-10-12 13:58:26 -04:00
- ( IBAction ) doOpenSettingsActionSheet : ( id ) sender {
2012-08-08 19:31:33 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
2013-03-01 15:48:18 -08:00
[ appDelegate . masterContainerViewController showFeedDetailMenuPopover : self . settingsBarButton ] ;
2012-08-08 19:31:33 -07:00
} else {
2012-10-12 13:58:26 -04:00
if ( self . popoverController = = nil ) {
2014-11-04 12:15:49 -08:00
self . popoverController = [ [ WYPopoverController alloc ]
2012-10-15 14:57:20 -07:00
initWithContentViewController : ( UIViewController * ) appDelegate . feedDetailMenuViewController ] ;
2013-04-22 17:15:50 -07:00
[ appDelegate . feedDetailMenuViewController buildMenuOptions ] ;
2012-10-12 13:58:26 -04:00
self . popoverController . delegate = self ;
} else {
[ self . popoverController dismissPopoverAnimated : YES ] ;
self . popoverController = nil ;
}
2013-09-25 17:43:00 -07:00
NSInteger menuCount = [ appDelegate . feedDetailMenuViewController . menuOptions count ] + 2 ;
2013-04-22 17:15:50 -07:00
[ self . popoverController setPopoverContentSize : CGSizeMake ( 260 , 38 * menuCount ) ] ;
2013-02-27 17:22:49 -08:00
[ self . popoverController presentPopoverFromBarButtonItem : self . settingsBarButton
permittedArrowDirections : UIPopoverArrowDirectionUp
2012-10-12 13:58:26 -04:00
animated : YES ] ;
2012-08-08 19:31:33 -07:00
}
2012-10-12 13:58:26 -04:00
2011-12-20 08:56:55 -08:00
}
2015-04-26 22:05:28 -07:00
2011-10-17 09:28:15 -07:00
- ( void ) confirmDeleteSite {
2012-07-09 21:26:53 -07:00
UIAlertView * deleteConfirm = [ [ UIAlertView alloc ]
initWithTitle : @ "Positive?"
message : nil
delegate : self
cancelButtonTitle : @ "Cancel"
otherButtonTitles : @ "Delete" ,
nil ] ;
2011-10-17 09:28:15 -07:00
[ deleteConfirm show ] ;
[ deleteConfirm setTag : 0 ] ;
}
- ( void ) alertView : ( UIAlertView * ) alertView clickedButtonAtIndex : ( NSInteger ) buttonIndex {
if ( alertView . tag = = 0 ) {
2015-04-26 22:05:28 -07:00
// Delete
if ( buttonIndex ! = alertView . cancelButtonIndex ) {
2014-02-12 20:09:37 -08:00
if ( storiesCollection . isRiverView ) {
2011-12-05 09:26:02 -08:00
[ self deleteFolder ] ;
} else {
[ self deleteSite ] ;
}
2011-10-17 09:28:15 -07:00
}
2015-04-26 22:05:28 -07:00
} else if ( alertView . tag = = 1 ) {
// Rename
if ( buttonIndex ! = alertView . cancelButtonIndex ) {
NSString * newTitle = [ [ alertView textFieldAtIndex : 0 ] text ] ;
2015-04-26 22:34:12 -07:00
[ self renameTo : newTitle ] ;
2015-04-26 22:05:28 -07:00
}
2011-10-17 09:28:15 -07:00
}
}
2015-04-26 22:34:12 -07:00
- ( void ) renameTo : ( NSString * ) newTitle {
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
MBProgressHUD * HUD = [ MBProgressHUD showHUDAddedTo : self . view animated : YES ] ;
HUD . labelText = @ "Renaming..." ;
2015-04-26 22:05:28 -07:00
2015-04-26 22:34:12 -07:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/rename_feed" , NEWSBLUR_URL ] ;
if ( storiesCollection . isRiverView ) {
urlString = [ NSString stringWithFormat : @ "%@/reader/rename_folder" , NEWSBLUR_URL ] ;
}
NSURL * url = [ NSURL URLWithString : urlString ] ;
2015-04-26 22:05:28 -07:00
2015-04-26 22:34:12 -07:00
__block ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : url ] ;
[ request setDelegate : self ] ;
if ( storiesCollection . isRiverView ) {
[ request addPostValue : [ appDelegate extractFolderName : storiesCollection . activeFolder ] forKey : @ "folder_name" ] ;
[ request addPostValue : [ appDelegate extractParentFolderName : storiesCollection . activeFolder ] forKey : @ "in_folder" ] ;
[ request addPostValue : newTitle forKey : @ "new_folder_name" ] ;
} else {
[ request addPostValue : [ storiesCollection . activeFeed objectForKey : @ "id" ] forKey : @ "feed_id" ] ;
[ request addPostValue : newTitle forKey : @ "feed_title" ] ;
}
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
[ request setCompletionBlock : ^ ( void ) {
[ appDelegate reloadFeedsView : YES ] ;
if ( storiesCollection . isRiverView ) {
[ appDelegate renameFolder : newTitle ] ;
} else {
[ appDelegate renameFeed : newTitle ] ;
}
[ self . view setNeedsDisplay ] ;
2015-05-19 11:10:59 -07:00
if ( UI_USER _INTERFACE _IDIOM ( ) = = UIUserInterfaceIdiomPad ) {
appDelegate . storyPageControl . navigationItem . titleView = [ appDelegate makeFeedTitle : storiesCollection . activeFeed ] ;
} else {
self . navigationItem . titleView = [ appDelegate makeFeedTitle : storiesCollection . activeFeed ] ;
}
2015-04-26 22:34:12 -07:00
[ self . navigationController . view setNeedsDisplay ] ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
2015-05-19 10:55:26 -07:00
2015-04-26 22:34:12 -07:00
} ] ;
[ request setTimeOutSeconds : 30 ] ;
[ request setTag : [ [ storiesCollection . activeFeed objectForKey : @ "id" ] intValue ] ] ;
[ request startAsynchronous ] ;
2015-04-26 22:05:28 -07:00
}
2011-10-17 09:28:15 -07:00
- ( void ) deleteSite {
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
MBProgressHUD * HUD = [ MBProgressHUD showHUDAddedTo : self . view animated : YES ] ;
HUD . labelText = @ "Deleting..." ;
2011-10-20 09:32:39 -07:00
2013-06-18 21:23:20 -04:00
NSString * theFeedDetailURL = [ NSString stringWithFormat : @ "%@/reader/delete_feed" ,
2011-10-20 09:32:39 -07:00
NEWSBLUR_URL ] ;
NSURL * urlFeedDetail = [ NSURL URLWithString : theFeedDetailURL ] ;
2013-06-04 17:56:10 -07:00
__block ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : urlFeedDetail ] ;
2011-10-20 09:32:39 -07:00
[ request setDelegate : self ] ;
2014-02-12 20:09:37 -08:00
[ request addPostValue : [ storiesCollection . activeFeed objectForKey : @ "id" ] forKey : @ "feed_id" ] ;
[ request addPostValue : [ appDelegate extractFolderName : storiesCollection . activeFolder ] forKey : @ "in_folder" ] ;
2013-06-04 18:02:05 -07:00
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
2011-10-20 09:32:39 -07:00
[ request setCompletionBlock : ^ ( void ) {
2011-12-03 18:22:14 -08:00
[ appDelegate reloadFeedsView : YES ] ;
2011-10-20 09:32:39 -07:00
[ appDelegate . navigationController
popToViewController : [ appDelegate . navigationController . viewControllers
objectAtIndex : 0 ]
animated : YES ] ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
} ] ;
2013-06-03 12:43:45 -07:00
[ request setTimeOutSeconds : 30 ] ;
2014-02-12 20:09:37 -08:00
[ request setTag : [ [ storiesCollection . activeFeed objectForKey : @ "id" ] intValue ] ] ;
2011-10-20 09:32:39 -07:00
[ request startAsynchronous ] ;
2011-10-17 09:28:15 -07:00
}
2011-08-18 09:56:52 -07:00
2011-12-05 09:26:02 -08:00
- ( void ) deleteFolder {
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
MBProgressHUD * HUD = [ MBProgressHUD showHUDAddedTo : self . view animated : YES ] ;
HUD . labelText = @ "Deleting..." ;
2013-06-18 21:23:20 -04:00
NSString * theFeedDetailURL = [ NSString stringWithFormat : @ "%@/reader/delete_folder" ,
2011-12-05 09:26:02 -08:00
NEWSBLUR_URL ] ;
NSURL * urlFeedDetail = [ NSURL URLWithString : theFeedDetailURL ] ;
2013-06-04 17:56:10 -07:00
__block ASIFormDataRequest * request = [ ASIFormDataRequest requestWithURL : urlFeedDetail ] ;
2011-12-05 09:26:02 -08:00
[ request setDelegate : self ] ;
2014-02-12 20:09:37 -08:00
[ request addPostValue : [ appDelegate extractFolderName : storiesCollection . activeFolder ]
2011-12-05 09:26:02 -08:00
forKey : @ "folder_to_delete" ] ;
2014-02-12 20:09:37 -08:00
[ request addPostValue : [ appDelegate extractFolderName : [ appDelegate
extractParentFolderName : storiesCollection . activeFolder ] ]
2011-12-05 09:26:02 -08:00
forKey : @ "in_folder" ] ;
2013-06-04 18:02:05 -07:00
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
2011-12-05 09:26:02 -08:00
[ request setCompletionBlock : ^ ( void ) {
[ appDelegate reloadFeedsView : YES ] ;
[ appDelegate . navigationController
popToViewController : [ appDelegate . navigationController . viewControllers
objectAtIndex : 0 ]
animated : YES ] ;
[ MBProgressHUD hideHUDForView : self . view animated : YES ] ;
} ] ;
[ request setTimeOutSeconds : 30 ] ;
[ request startAsynchronous ] ;
}
2011-12-03 18:22:14 -08:00
- ( void ) openMoveView {
[ appDelegate showMoveSite ] ;
}
2012-12-24 23:01:25 -08:00
- ( void ) openTrainSite {
[ appDelegate openTrainSite ] ;
}
2015-04-26 22:05:28 -07:00
- ( void ) openRenameSite {
NSString * title = [ NSString stringWithFormat : @ "Rename \" % @ \ "" , appDelegate . storiesCollection . isRiverView ?
2015-04-26 22:34:12 -07:00
[ appDelegate extractFolderName : appDelegate . storiesCollection . activeFolder ] : [ appDelegate . storiesCollection . activeFeed objectForKey : @ "feed_title" ] ] ;
2015-04-26 22:05:28 -07:00
NSString * subtitle = ( appDelegate . storiesCollection . isRiverView ?
nil : [ appDelegate . storiesCollection . activeFeed objectForKey : @ "feed_address" ] ) ;
UIAlertView * alertView = [ [ UIAlertView alloc ] initWithTitle : title
message : subtitle
delegate : self
cancelButtonTitle : @ "Cancel"
otherButtonTitles : @ "Rename" , nil ] ;
[ alertView setTag : 1 ] ;
[ alertView setAlertViewStyle : UIAlertViewStylePlainTextInput ] ;
[ [ alertView textFieldAtIndex : 0 ] setText : appDelegate . storiesCollection . isRiverView ?
2015-04-26 22:34:12 -07:00
[ appDelegate extractFolderName : appDelegate . storiesCollection . activeFolder ] :
2015-04-26 22:05:28 -07:00
[ appDelegate . storiesCollection . activeFeed objectForKey : @ "feed_title" ] ] ;
[ alertView show ] ;
}
2012-07-25 17:29:29 -07:00
- ( void ) showUserProfile {
2014-02-12 20:09:37 -08:00
appDelegate . activeUserProfileId = [ NSString stringWithFormat : @ "%@" ,
[ storiesCollection . activeFeed objectForKey : @ "user_id" ] ] ;
appDelegate . activeUserProfileName = [ NSString stringWithFormat : @ "%@" ,
[ storiesCollection . activeFeed objectForKey : @ "username" ] ] ;
2013-10-11 17:46:09 -07:00
[ appDelegate showUserProfileModal : titleImageBarButton ] ;
2012-07-12 22:05:23 -07:00
}
2012-06-20 19:18:29 -07:00
- ( void ) changeActiveFeedDetailRow {
2014-02-12 20:09:37 -08:00
NSInteger rowIndex = [ storiesCollection locationOfActiveStory ] ;
2014-02-27 16:10:28 -08:00
int offset = 1 ;
if ( [ [ self . storyTitlesTable visibleCells ] count ] <= 4 ) {
offset = 0 ;
}
2012-06-21 11:53:48 -07:00
2012-06-18 16:45:36 -07:00
NSIndexPath * indexPath = [ NSIndexPath indexPathForRow : rowIndex inSection : 0 ] ;
2014-02-27 16:10:28 -08:00
NSIndexPath * offsetIndexPath = [ NSIndexPath indexPathForRow : ( rowIndex - offset ) inSection : 0 ] ;
2012-06-18 16:45:36 -07:00
2012-06-18 17:20:34 -07:00
[ storyTitlesTable selectRowAtIndexPath : indexPath
animated : YES
scrollPosition : UITableViewScrollPositionNone ] ;
2012-06-18 16:45:36 -07:00
// check to see if the cell is completely visible
CGRect cellRect = [ storyTitlesTable rectForRowAtIndexPath : indexPath ] ;
cellRect = [ storyTitlesTable convertRect : cellRect toView : storyTitlesTable . superview ] ;
BOOL completelyVisible = CGRectContainsRect ( storyTitlesTable . frame , cellRect ) ;
if ( ! completelyVisible ) {
2012-06-18 17:20:34 -07:00
[ storyTitlesTable scrollToRowAtIndexPath : offsetIndexPath
atScrollPosition : UITableViewScrollPositionTop
animated : YES ] ;
2012-06-18 16:45:36 -07:00
}
}
2013-10-01 15:38:29 -07:00
# pragma mark -
# pragma mark Story Actions - save
2013-10-01 14:19:12 -07:00
- ( void ) finishMarkAsSaved : ( ASIFormDataRequest * ) request {
2014-03-05 14:13:49 -08:00
2013-10-01 14:19:12 -07:00
}
- ( void ) failedMarkAsSaved : ( ASIFormDataRequest * ) request {
[ self informError : @ "Failed to save story" ] ;
[ self . storyTitlesTable reloadData ] ;
}
- ( void ) finishMarkAsUnsaved : ( ASIFormDataRequest * ) request {
2014-03-05 14:13:49 -08:00
2013-10-01 14:19:12 -07:00
}
- ( void ) failedMarkAsUnsaved : ( ASIFormDataRequest * ) request {
[ self informError : @ "Failed to unsave story" ] ;
2014-03-05 14:13:49 -08:00
[ self . storyTitlesTable reloadData ] ;
}
- ( void ) failedMarkAsUnread : ( ASIFormDataRequest * ) request {
[ self informError : @ "Failed to unread story" ] ;
2013-10-01 14:19:12 -07:00
[ self . storyTitlesTable reloadData ] ;
}
2011-08-18 09:56:52 -07:00
2012-06-22 18:33:44 -07:00
# pragma mark -
# pragma mark instafetchFeed
// called when the user taps refresh button
2012-06-27 15:38:51 -07:00
- ( void ) instafetchFeed {
2013-01-07 16:34:59 -08:00
NSString * urlString = [ NSString
2013-06-18 21:23:20 -04:00
stringWithFormat : @ "%@/reader/refresh_feed/%@" ,
2012-06-22 18:33:44 -07:00
NEWSBLUR_URL ,
2014-02-12 20:09:37 -08:00
[ storiesCollection . activeFeed objectForKey : @ "id" ] ] ;
2012-06-22 18:33:44 -07:00
[ self cancelRequests ] ;
2013-06-04 18:02:05 -07:00
ASIHTTPRequest * request = [ self requestWithURL : urlString ] ;
2012-06-22 18:33:44 -07:00
[ request setDelegate : self ] ;
[ request setResponseEncoding : NSUTF8StringEncoding ] ;
[ request setDefaultResponseEncoding : NSUTF8StringEncoding ] ;
[ request setDidFinishSelector : @ selector ( finishedRefreshingFeed : ) ] ;
[ request setDidFailSelector : @ selector ( failRefreshingFeed : ) ] ;
[ request setTimeOutSeconds : 60 ] ;
[ request startAsynchronous ] ;
2014-02-12 20:09:37 -08:00
[ storiesCollection setStories : nil ] ;
2014-02-27 14:49:33 -08:00
storiesCollection . feedPage = 1 ;
2012-06-22 18:33:44 -07:00
self . pageFetching = YES ;
[ self . storyTitlesTable reloadData ] ;
[ storyTitlesTable scrollRectToVisible : CGRectMake ( 0 , 0 , 1 , 1 ) animated : YES ] ;
}
- ( void ) finishedRefreshingFeed : ( ASIHTTPRequest * ) request {
NSString * responseString = [ request responseString ] ;
2012-07-27 16:21:44 -07:00
NSData * responseData = [ responseString dataUsingEncoding : NSUTF8StringEncoding ] ;
NSError * error ;
NSDictionary * results = [ NSJSONSerialization
JSONObjectWithData : responseData
options : kNilOptions
error : & error ] ;
2012-07-16 19:45:14 -07:00
2012-06-22 18:33:44 -07:00
[ self renderStories : [ results objectForKey : @ "stories" ] ] ;
}
- ( void ) failRefreshingFeed : ( ASIHTTPRequest * ) request {
NSLog ( @ "Fail: %@" , request ) ;
[ self informError : [ request error ] ] ;
[ self fetchFeedDetail : 1 withCallback : nil ] ;
}
2012-06-29 10:20:06 -07:00
# pragma mark -
# pragma mark loadSocial Feeds
- ( void ) loadFaviconsFromActiveFeed {
NSArray * keys = [ appDelegate . dictActiveFeeds allKeys ] ;
2012-07-20 15:54:10 -07:00
if ( ! [ keys count ] ) {
// if no new favicons , return
return ;
}
2012-06-29 10:20:06 -07:00
NSString * feedIdsQuery = [ NSString stringWithFormat : @ "?feed_ids=%@" ,
[ [ keys valueForKey : @ "description" ] componentsJoinedByString : @ "&feed_ids=" ] ] ;
2013-06-18 21:23:20 -04:00
NSString * urlString = [ NSString stringWithFormat : @ "%@/reader/favicons%@" ,
2012-06-29 10:20:06 -07:00
NEWSBLUR_URL ,
feedIdsQuery ] ;
NSURL * url = [ NSURL URLWithString : urlString ] ;
ASIHTTPRequest * request = [ ASIHTTPRequest requestWithURL : url ] ;
[ request setDidFinishSelector : @ selector ( saveAndDrawFavicons : ) ] ;
[ request setDidFailSelector : @ selector ( requestFailed : ) ] ;
[ request setDelegate : self ] ;
[ request startAsynchronous ] ;
}
- ( void ) saveAndDrawFavicons : ( ASIHTTPRequest * ) request {
NSString * responseString = [ request responseString ] ;
2012-07-27 16:21:44 -07:00
NSData * responseData = [ responseString dataUsingEncoding : NSUTF8StringEncoding ] ;
NSError * error ;
NSDictionary * results = [ NSJSONSerialization
JSONObjectWithData : responseData
options : kNilOptions
error : & error ] ;
2012-06-29 10:20:06 -07:00
2014-02-03 18:54:50 -08:00
dispatch_queue _t queue = dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _LOW , 0 ul ) ;
2012-06-29 10:20:06 -07:00
dispatch_async ( queue , ^ {
for ( id feed_id in results ) {
2012-07-27 16:21:44 -07:00
NSMutableDictionary * feed = [ [ appDelegate . dictActiveFeeds objectForKey : feed_id ] mutableCopy ] ;
2012-06-29 10:20:06 -07:00
[ feed setValue : [ results objectForKey : feed_id ] forKey : @ "favicon" ] ;
[ appDelegate . dictActiveFeeds setValue : feed forKey : feed_id ] ;
NSString * favicon = [ feed objectForKey : @ "favicon" ] ;
if ( ( NSNull * ) favicon ! = [ NSNull null ] && [ favicon length ] > 0 ) {
NSData * imageData = [ NSData dataWithBase64EncodedString : favicon ] ;
UIImage * faviconImage = [ UIImage imageWithData : imageData ] ;
2014-02-24 18:56:51 -08:00
[ appDelegate saveFavicon : faviconImage feedId : feed_id ] ;
2012-06-29 10:20:06 -07:00
}
}
2014-02-24 18:35:35 -08:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2012-06-29 10:20:06 -07:00
[ self . storyTitlesTable reloadData ] ;
} ) ;
} ) ;
}
- ( void ) requestFailed : ( ASIHTTPRequest * ) request {
NSError * error = [ request error ] ;
NSLog ( @ "Error: %@" , error ) ;
2013-03-06 14:29:40 -08:00
[ appDelegate informError : error ] ;
2012-06-29 10:20:06 -07:00
}
2012-10-12 13:58:26 -04:00
# pragma mark -
2014-11-04 12:15:49 -08:00
# pragma mark WYPopoverControllerDelegate implementation
2012-10-12 13:58:26 -04:00
2014-11-04 12:15:49 -08:00
- ( void ) popoverControllerDidDismissPopover : ( WYPopoverController * ) thePopoverController {
2012-10-12 13:58:26 -04:00
// Safe to release the popover here
self . popoverController = nil ;
}
2014-11-04 12:15:49 -08:00
- ( BOOL ) popoverControllerShouldDismissPopover : ( WYPopoverController * ) thePopoverController {
2012-10-12 13:58:26 -04:00
// The popover is automatically dismissed if you click outside it , unless you return NO here
return YES ;
}
2010-06-21 17:17:26 -04:00
@ end