Long press on feeds to choose mark as read days. Also works offline. Also fixing move feed dialog on iphone.

This commit is contained in:
Samuel Clay 2013-10-08 19:33:11 -07:00
parent a17939a902
commit feaa1b8d98
9 changed files with 187 additions and 37 deletions

View file

@ -1199,7 +1199,8 @@
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request {
// [self informError:@"Failed to mark story as read"];
[appDelegate markStoriesRead:[request.userInfo objectForKey:@"stories"]
inFeeds:[request.userInfo objectForKey:@"feeds"]];
inFeeds:[request.userInfo objectForKey:@"feeds"]
cutoffTimestamp:nil];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {
@ -1312,7 +1313,7 @@
} else if (buttonIndex == 2) {
[self instafetchFeed];
}
}
}
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {

View file

@ -48,6 +48,10 @@
[fromFolderInput setLeftViewMode:UITextFieldViewModeAlways];
navBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9];
CGRect frame = self.navBar.frame;
frame.size.height += 20;
self.navBar.frame = frame;
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];

View file

@ -336,7 +336,8 @@
- (void)markActiveFolderAllRead;
- (void)markFeedAllRead:(id)feedId;
- (void)markFeedReadInCache:(NSArray *)feedIds;
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds;
- (void)markFeedReadInCache:(NSArray *)feedIds cutoffTimestamp:(NSInteger)cutoff;
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds cutoffTimestamp:(NSInteger)cutoff;
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request;
- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request;

View file

@ -1792,7 +1792,70 @@
});
}
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds {
- (void)markFeedReadInCache:(NSArray *)feedIds cutoffTimestamp:(NSInteger)cutoff {
for (NSString *feedId in feedIds) {
NSDictionary *unreadCounts = [self.dictUnreadCounts objectForKey:feedId];
NSMutableDictionary *newUnreadCounts = [unreadCounts mutableCopy];
NSMutableArray *stories = [NSMutableArray array];
[self.database inDatabase:^(FMDatabase *db) {
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM stories s "
"INNER JOIN unread_hashes uh ON s.story_hash = uh.story_hash "
"WHERE s.story_feed_id = %@ AND s.story_timestamp < %ld",
feedId, (long)cutoff];
FMResultSet *cursor = [db executeQuery:sql];
while ([cursor next]) {
NSDictionary *story = [cursor resultDictionary];
[stories addObject:[NSJSONSerialization
JSONObjectWithData:[[story objectForKey:@"story_json"]
dataUsingEncoding:NSUTF8StringEncoding]
options:nil error:nil]];
}
[cursor close];
}];
for (NSDictionary *story in stories) {
NSInteger score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]];
if (score > 0) {
int unreads = MAX(0, [[newUnreadCounts objectForKey:@"ps"] intValue] - 1);
[newUnreadCounts setValue:[NSNumber numberWithInt:unreads] forKey:@"ps"];
} else if (score == 0) {
int unreads = MAX(0, [[newUnreadCounts objectForKey:@"nt"] intValue] - 1);
[newUnreadCounts setValue:[NSNumber numberWithInt:unreads] forKey:@"nt"];
} else if (score < 0) {
int unreads = MAX(0, [[newUnreadCounts objectForKey:@"ng"] intValue] - 1);
[newUnreadCounts setValue:[NSNumber numberWithInt:unreads] forKey:@"ng"];
}
[self.dictUnreadCounts setObject:newUnreadCounts forKey:feedId];
}
[self.database inTransaction:^(FMDatabase *db, BOOL *rollback) {
for (NSDictionary *story in stories) {
NSMutableDictionary *newStory = [story mutableCopy];
[newStory setObject:[NSNumber numberWithInt:1] forKey:@"read_status"];
NSString *storyHash = [newStory objectForKey:@"story_hash"];
[db executeUpdate:@"UPDATE stories SET story_json = ? WHERE story_hash = ?",
[newStory JSONRepresentation],
storyHash];
}
NSString *deleteSql = [NSString
stringWithFormat:@"DELETE FROM unread_hashes "
"WHERE story_feed_id = \"%@\" "
"AND story_timestamp < %ld",
feedId, (long)cutoff];
[db executeUpdate:deleteSql];
[db executeUpdate:@"UPDATE unread_counts SET ps = ?, nt = ?, ng = ? WHERE feed_id = ?",
[newUnreadCounts objectForKey:@"ps"],
[newUnreadCounts objectForKey:@"nt"],
[newUnreadCounts objectForKey:@"ng"],
feedId];
}];
}
}
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds cutoffTimestamp:(NSInteger)cutoff {
// Must be offline and marking all as read, so load all stories.
if (stories && [[stories allKeys] count]) {
@ -1806,6 +1869,9 @@
NSString *sql = [NSString stringWithFormat:@"SELECT u.story_feed_id, u.story_hash "
"FROM unread_hashes u WHERE u.story_feed_id IN (\"%@\")",
[feeds componentsJoinedByString:@"\",\""]];
if (cutoff) {
sql = [NSString stringWithFormat:@"%@ AND u.story_timestamp < %ld", sql, (long)cutoff];
}
FMResultSet *cursor = [db executeQuery:sql];
while ([cursor next]) {
@ -1824,10 +1890,14 @@
[cursor close];
}];
[self queueReadStories:feedsStories];
for (NSString *feedId in [feedsStories allKeys]) {
[self markFeedAllRead:feedId];
if (cutoff) {
[self markFeedReadInCache:[feedsStories allKeys] cutoffTimestamp:cutoff];
} else {
for (NSString *feedId in [feedsStories allKeys]) {
[self markFeedAllRead:feedId];
}
[self markFeedReadInCache:[feedsStories allKeys]];
}
[self markFeedReadInCache:[feedsStories allKeys]];
}
}
@ -1836,7 +1906,7 @@
NSArray *feedIds = [request.userInfo objectForKey:@"feeds"];
NSDictionary *stories = [request.userInfo objectForKey:@"stories"];
[self markStoriesRead:stories inFeeds:feedIds];
[self markStoriesRead:stories inFeeds:feedIds cutoffTimestamp:nil];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {

View file

@ -26,7 +26,9 @@ ASIHTTPRequestDelegate, NSCacheDelegate,
WEPopoverControllerDelegate,
UIPopoverControllerDelegate,
IASKSettingsDelegate,
MCSwipeTableViewCellDelegate> {
MCSwipeTableViewCellDelegate,
UIGestureRecognizerDelegate,
UIActionSheetDelegate> {
NewsBlurAppDelegate *appDelegate;
NSMutableDictionary * activeFeedLocations;

View file

@ -133,6 +133,12 @@ static const CGFloat kFolderTitleHeight = 28.0f;
appDelegate.activeClassifiers = [NSMutableDictionary dictionary];
UILongPressGestureRecognizer *longpress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleLongPress:)];
longpress.minimumPressDuration = 1.0;
longpress.delegate = self;
[self.feedTitlesTable addGestureRecognizer:longpress];
self.notifier = [[NBNotifier alloc] initWithTitle:@"Fetching stories..."
inView:self.view
withOffset:CGPointMake(0, self.feedViewToolbar.frame.size.height)];
@ -156,7 +162,8 @@ static const CGFloat kFolderTitleHeight = 28.0f;
self.viewShowingAllFeeds = NO;
[self.intelligenceControl setSelectedSegmentIndex:1];
[appDelegate setSelectedIntelligence:0];
} else { // default state, ALL BLURBLOG STORIES
} else {
// default state, ALL BLURBLOG STORIES
self.viewShowingAllFeeds = YES;
[self.intelligenceControl setSelectedSegmentIndex:0];
[appDelegate setSelectedIntelligence:0];
@ -754,6 +761,52 @@ static const CGFloat kFolderTitleHeight = 28.0f;
}
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.feedTitlesTable];
NSIndexPath *indexPath = [self.feedTitlesTable indexPathForRowAtPoint:p];
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) return;
if (indexPath == nil) return;
NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section];
id feedId = [[appDelegate.dictFolders objectForKey:folderName] objectAtIndex:indexPath.row];
NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId];
BOOL isSocial = [appDelegate isSocialFeed:feedIdStr];
NSDictionary *feed = isSocial ?
[appDelegate.dictSocialFeeds objectForKey:feedIdStr] :
[appDelegate.dictFeeds objectForKey:feedIdStr];
UIActionSheet *markReadSheet = [[UIActionSheet alloc] initWithTitle:[feed objectForKey:@"feed_title"]
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:@"Mark site as read"
otherButtonTitles:@"1 day", @"3 days", @"7 days", @"14 days", nil];
markReadSheet.accessibilityValue = feedIdStr;
[markReadSheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *feedId = actionSheet.accessibilityValue;
switch (buttonIndex) {
case 0:
[self markFeedRead:feedId cutoffDays:0];
break;
case 1:
[self markFeedRead:feedId cutoffDays:1];
break;
case 2:
[self markFeedRead:feedId cutoffDays:3];
break;
case 3:
[self markFeedRead:feedId cutoffDays:7];
break;
case 4:
[self markFeedRead:feedId cutoffDays:14];
break;
}
}
#pragma mark -
#pragma mark Preferences
@ -1107,19 +1160,8 @@ heightForHeaderInSection:(NSInteger)section {
}
} else if (state == MCSwipeTableViewCellState3) {
// Mark read
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:feedId forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request setUserInfo:@{@"feeds": @[feedId]}];
[request setDelegate:self];
[request startAsynchronous];
[self markFeedRead:feedId cutoffDays:0];
[appDelegate markFeedAllRead:feedId];
[self.stillVisibleFeeds setObject:indexPath forKey:feedId];
[self.feedTitlesTable beginUpdates];
[self.feedTitlesTable reloadRowsAtIndexPaths:@[indexPath]
@ -1130,7 +1172,10 @@ heightForHeaderInSection:(NSInteger)section {
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request {
[appDelegate markStoriesRead:nil
inFeeds:[request.userInfo objectForKey:@"feeds"]];
inFeeds:[request.userInfo objectForKey:@"feeds"]
cutoffTimestamp:[[request.userInfo objectForKey:@"cutoffTimestamp"] integerValue]];
[self showOfflineNotifier];
[self.feedTitlesTable reloadData];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {
@ -1139,9 +1184,38 @@ heightForHeaderInSection:(NSInteger)section {
return;
}
if ([[request.userInfo objectForKey:@"cutoffTimestamp"] integerValue]) {
[self refreshFeedList:[[request.userInfo objectForKey:@"feeds"] objectAtIndex:0]];
}
[appDelegate markFeedReadInCache:[request.userInfo objectForKey:@"feeds"]];
}
- (void)markFeedRead:(NSString *)feedId cutoffDays:(NSInteger)days {
NSTimeInterval cutoffTimestamp = [[NSDate date] timeIntervalSince1970];
cutoffTimestamp -= (days * 60*60*24);
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:feedId forKey:@"feed_id"];
if (days) {
[request setPostValue:[NSNumber numberWithInteger:cutoffTimestamp]
forKey:@"cutoff_timestamp"];
}
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request setUserInfo:@{@"feeds": @[feedId],
@"cutoffTimestamp": [NSNumber numberWithInteger:cutoffTimestamp]}];
[request setDelegate:self];
[request startAsynchronous];
if (!days) {
[appDelegate markFeedAllRead:feedId];
} else {
[self showRefreshNotifier];
}
}
#pragma mark - Table Actions

View file

@ -2584,7 +2584,6 @@
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
@ -2607,7 +2606,7 @@
"-all_load",
);
PRODUCT_NAME = NewsBlur;
PROVISIONING_PROFILE = "A0156932-124B-4F8E-8B93-EE6598D778F0";
PROVISIONING_PROFILE = "EB97D956-BB90-4F2F-9919-F71949B04B3F";
TARGETED_DEVICE_FAMILY = "1,2";
"WARNING_CFLAGS[arch=*]" = "-Wall";
};
@ -2620,7 +2619,6 @@
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = NewsBlur_Prefix.pch;
@ -2640,7 +2638,7 @@
"-all_load",
);
PRODUCT_NAME = NewsBlur;
PROVISIONING_PROFILE = "A0156932-124B-4F8E-8B93-EE6598D778F0";
PROVISIONING_PROFILE = "EB97D956-BB90-4F2F-9919-F71949B04B3F";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};

View file

@ -5,7 +5,7 @@
#import <SystemConfiguration/SystemConfiguration.h>
#import <MobileCoreServices/MobileCoreServices.h>
//#define DEBUG 1
#define DEBUG 1
#ifdef DEBUG
#define BACKGROUND_REFRESH_SECONDS -5

View file

@ -26,12 +26,12 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" id="31">
<rect key="frame" x="87" y="67" width="219" height="26"/>
<rect key="frame" x="86" y="77" width="220" height="23"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" text="MOVING" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="41">
<rect key="frame" x="7" y="65" width="61" height="28"/>
<rect key="frame" x="7" y="75" width="61" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
@ -40,7 +40,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" text="FROM" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="43">
<rect key="frame" x="19" y="105" width="49" height="31"/>
<rect key="frame" x="19" y="115" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
@ -49,7 +49,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" text="INTO" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="42">
<rect key="frame" x="19" y="148" width="49" height="31"/>
<rect key="frame" x="19" y="158" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
@ -58,7 +58,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" text="Moving..." lineBreakMode="tailTruncation" minimumFontSize="10" id="15">
<rect key="frame" x="111" y="184" width="189" height="52"/>
<rect key="frame" x="111" y="194" width="189" height="52"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Helvetica" family="Helvetica" pointSize="17"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
@ -67,7 +67,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" text="Error" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="4" minimumFontSize="17" id="16">
<rect key="frame" x="20" y="188" width="280" height="48"/>
<rect key="frame" x="20" y="198" width="280" height="48"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Helvetica" family="Helvetica" pointSize="17"/>
<color key="textColor" red="0.92408370969999998" green="0.35912829639999999" blue="0.23267127570000001" alpha="1" colorSpace="calibratedRGB"/>
@ -76,7 +76,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" hidesWhenStopped="YES" style="gray" id="9">
<rect key="frame" x="86" y="201" width="20" height="20"/>
<rect key="frame" x="86" y="211" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
</activityIndicatorView>
<pickerView contentMode="scaleToFill" id="8">
@ -88,7 +88,7 @@
</connections>
</pickerView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Choose a folder" minimumFontSize="12" id="13">
<rect key="frame" x="87" y="148" width="219" height="31"/>
<rect key="frame" x="87" y="158" width="219" height="31"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES" button="YES" image="YES"/>
@ -100,7 +100,7 @@
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" minimumFontSize="12" id="44">
<rect key="frame" x="87" y="105" width="219" height="30"/>
<rect key="frame" x="87" y="115" width="219" height="30"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES" button="YES" image="YES"/>