diff --git a/apps/reader/views.py b/apps/reader/views.py index 4a53a7ce5..4a8a002f3 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -611,7 +611,7 @@ def load_single_feed(request, feed_id): if not include_story_content: del story['story_content'] story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) - story['short_parsed_date'] = format_story_link_date__short(story_date, now) + story['short_parsed_date'] = format_story_link_date__short(story_date) story['long_parsed_date'] = format_story_link_date__long(story_date, now) if usersub: story['read_status'] = 1 @@ -683,6 +683,9 @@ def load_single_feed(request, feed_id): # if page <= 1: # import random # time.sleep(random.randint(0, 3)) + + # if page == 2: + # assert False return data @@ -797,7 +800,7 @@ def load_starred_stories(request): for story in stories: story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) - story['short_parsed_date'] = format_story_link_date__short(story_date, now) + story['short_parsed_date'] = format_story_link_date__short(story_date) story['long_parsed_date'] = format_story_link_date__long(story_date, now) starred_date = localtime_for_timezone(story['starred_date'], user.profile.timezone) story['starred_date'] = format_story_link_date__long(starred_date, now) @@ -960,7 +963,7 @@ def load_river_stories__redis(request): story['story_hash'] not in unread_feed_story_hashes): story['read_status'] = 1 story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) - story['short_parsed_date'] = format_story_link_date__short(story_date, now) + story['short_parsed_date'] = format_story_link_date__short(story_date) story['long_parsed_date'] = format_story_link_date__long(story_date, now) if story['story_hash'] in starred_stories: story['starred'] = True diff --git a/apps/social/views.py b/apps/social/views.py index fa2b3f567..fc0d6f527 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -125,8 +125,8 @@ def load_social_stories(request, user_id, username=None): story['social_user_id'] = social_user_id # story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone) - story['short_parsed_date'] = format_story_link_date__short(shared_date, now) - story['long_parsed_date'] = format_story_link_date__long(shared_date, now) + story['short_parsed_date'] = format_story_link_date__short(shared_date) + story['long_parsed_date'] = format_story_link_date__long(shared_date) story['read_status'] = 1 if (read_filter == 'all' or query) and socialsub: @@ -279,7 +279,7 @@ def load_river_blurblog(request): if story['story_hash'] not in unread_feed_story_hashes: story['read_status'] = 1 story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) - story['short_parsed_date'] = format_story_link_date__short(story_date, now) + story['short_parsed_date'] = format_story_link_date__short(story_date) story['long_parsed_date'] = format_story_link_date__long(story_date, now) if story['story_hash'] in starred_stories: story['starred'] = True diff --git a/clients/ios/Classes/FeedDetailTableCell.h b/clients/ios/Classes/FeedDetailTableCell.h index 6245990a5..c6fec6636 100644 --- a/clients/ios/Classes/FeedDetailTableCell.h +++ b/clients/ios/Classes/FeedDetailTableCell.h @@ -17,6 +17,7 @@ NSString *storyTitle; NSString *storyAuthor; NSString *storyDate; + NSInteger storyTimestamp; int storyScore; BOOL isStarred; BOOL isShared; @@ -44,6 +45,7 @@ @property (nonatomic) NSString *storyTitle; @property (nonatomic) NSString *storyAuthor; @property (nonatomic) NSString *storyDate; +@property (nonatomic) NSInteger storyTimestamp; @property (nonatomic) UIColor *feedColorBar; @property (nonatomic) UIColor *feedColorBarTopBorder; diff --git a/clients/ios/Classes/FeedDetailTableCell.m b/clients/ios/Classes/FeedDetailTableCell.m index 3c6a9e200..cf69a7c2a 100644 --- a/clients/ios/Classes/FeedDetailTableCell.m +++ b/clients/ios/Classes/FeedDetailTableCell.m @@ -23,6 +23,7 @@ static UIFont *indicatorFont = nil; @synthesize storyTitle; @synthesize storyAuthor; @synthesize storyDate; +@synthesize storyTimestamp; @synthesize storyScore; @synthesize siteTitle; @synthesize siteFavicon; @@ -223,7 +224,8 @@ static UIFont *indicatorFont = nil; } paragraphStyle.alignment = NSTextAlignmentRight; - [cell.storyDate + NSString *date = [Utilities formatShortDateFromTimestamp:cell.storyTimestamp]; + [date drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0) withAttributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: textColor, diff --git a/clients/ios/Classes/FeedDetailViewController.m b/clients/ios/Classes/FeedDetailViewController.m index 357c15fd4..7f13e4b93 100644 --- a/clients/ios/Classes/FeedDetailViewController.m +++ b/clients/ios/Classes/FeedDetailViewController.m @@ -301,7 +301,8 @@ - (void)beginOfflineTimer { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - if (!appDelegate.storyLocationsCount && self.feedPage == 1 && !self.isOffline) { + if (!appDelegate.storyLocationsCount && !self.pageFinished && + self.feedPage == 1 && !self.isOffline) { self.isShowingOffline = YES; self.isOffline = YES; [self showLoadingNotifier]; @@ -381,13 +382,11 @@ if (request.isCancelled) { NSLog(@"Cancelled"); return; - } else if (self.feedPage == 1) { + } else { self.isOffline = YES; + self.feedPage = 1; [self loadOfflineStories]; [self showOfflineNotifier]; - } else { - [self informError:[request error]]; - self.pageFinished = YES; } [self.storyTitlesTable reloadData]; }]; @@ -574,15 +573,12 @@ if (request.isCancelled) { NSLog(@"Cancelled"); return; - } else if (self.feedPage == 1) { + } else { self.isOffline = YES; self.isShowingOffline = NO; + self.feedPage = 1; [self loadOfflineStories]; [self showOfflineNotifier]; - } else { - [self informError:[request error]]; - self.pageFinished = YES; - [self.storyTitlesTable reloadData]; } }]; [request setCompletionBlock:^(void) { @@ -604,12 +600,11 @@ NSLog(@"Cancelled"); return; } else if ([request responseStatusCode] >= 500) { - if (self.feedPage == 1) { - self.isOffline = YES; - self.isShowingOffline = NO; - [self loadOfflineStories]; - [self showOfflineNotifier]; - } + self.isOffline = YES; + self.isShowingOffline = NO; + self.feedPage = 1; + [self loadOfflineStories]; + [self showOfflineNotifier]; if ([request responseStatusCode] == 503) { [self informError:@"In maintenance mode"]; self.pageFinished = YES; @@ -902,8 +897,9 @@ NSString *title = [story objectForKey:@"story_title"]; cell.storyTitle = [title stringByDecodingHTMLEntities]; - + cell.storyDate = [story objectForKey:@"short_parsed_date"]; + cell.storyTimestamp = [[story objectForKey:@"story_timestamp"] integerValue]; cell.isStarred = [[story objectForKey:@"starred"] boolValue]; cell.isShared = [[story objectForKey:@"shared"] boolValue]; diff --git a/clients/ios/Classes/NewsBlurAppDelegate.m b/clients/ios/Classes/NewsBlurAppDelegate.m index 60ee8c8bf..d4915ef68 100644 --- a/clients/ios/Classes/NewsBlurAppDelegate.m +++ b/clients/ios/Classes/NewsBlurAppDelegate.m @@ -1922,7 +1922,7 @@ NSMutableDictionary *newStory = [story mutableCopy]; [newStory setValue:[NSNumber numberWithBool:saved] forKey:@"starred"]; if (saved) { - [newStory setValue:[Utilities formatDateFromTimestamp:nil] forKey:@"starred_date"]; + [newStory setValue:[Utilities formatLongDateFromTimestamp:nil] forKey:@"starred_date"]; } else { [newStory removeObjectForKey:@"starred_date"]; } diff --git a/clients/ios/Classes/StoryDetailViewController.m b/clients/ios/Classes/StoryDetailViewController.m index 1198f6561..a14bd33a1 100644 --- a/clients/ios/Classes/StoryDetailViewController.m +++ b/clients/ios/Classes/StoryDetailViewController.m @@ -360,6 +360,9 @@ } } + NSString *storyDate = [Utilities formatLongDateFromTimestamp:[[self.activeStory + objectForKey:@"story_timestamp"] + integerValue]]; NSString *storyHeader = [NSString stringWithFormat:@ "
" "
" @@ -373,7 +376,7 @@ "
", storyUnread, storyTitle, - [self.activeStory objectForKey:@"long_parsed_date"], + storyDate, storyAuthor, storyTags, storyStarred]; diff --git a/clients/ios/Classes/Utilities.h b/clients/ios/Classes/Utilities.h index 568bd6cf0..13aa077a1 100644 --- a/clients/ios/Classes/Utilities.h +++ b/clients/ios/Classes/Utilities.h @@ -22,6 +22,7 @@ void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor + (void)saveimagesToDisk; + (UIImage *)roundCorneredImage:(UIImage *)orig radius:(CGFloat)r; + (NSString *)md5:(NSString *)string; -+ (NSString *)formatDateFromTimestamp:(NSInteger)timestamp; ++ (NSString *)formatLongDateFromTimestamp:(NSInteger)timestamp; ++ (NSString *)formatShortDateFromTimestamp:(NSInteger)timestamp; @end diff --git a/clients/ios/Classes/Utilities.m b/clients/ios/Classes/Utilities.m index ad5105246..9ed7fbc3d 100644 --- a/clients/ios/Classes/Utilities.m +++ b/clients/ios/Classes/Utilities.m @@ -154,14 +154,117 @@ static NSMutableDictionary *imageCache; ]; } -+ (NSString *)formatDateFromTimestamp:(NSInteger)timestamp { ++ (NSString *)formatLongDateFromTimestamp:(NSInteger)timestamp { if (!timestamp) timestamp = [[NSDate date] timeIntervalSince1970]; - + NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)timestamp]; - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"LLLL, d Y h:mma"]; + static NSDateFormatter *dateFormatter = nil; + static NSDateFormatter *todayFormatter = nil; + static NSDateFormatter *yesterdayFormatter = nil; + static NSDateFormatter *formatterPeriod = nil; + + NSDate *today = [NSDate date]; + NSDateComponents *components = [[NSCalendar currentCalendar] + components:NSIntegerMax + fromDate:today]; + [components setHour:0]; + [components setMinute:0]; + [components setSecond:0]; + NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components]; + NSDate *yesterday = [NSDate dateWithTimeInterval:-60*60*24 sinceDate:midnight]; + + if (!dateFormatter || !todayFormatter || !yesterdayFormatter || !formatterPeriod) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"EEEE, MMMM d'Sth', y h:mm"]; + todayFormatter = [[NSDateFormatter alloc] init]; + [todayFormatter setDateFormat:@"'Today', MMMM d'Sth' h:mm"]; + yesterdayFormatter = [[NSDateFormatter alloc] init]; + [yesterdayFormatter setDateFormat:@"'Yesterday', MMMM d'Sth' h:mm"]; + formatterPeriod = [[NSDateFormatter alloc] init]; + [formatterPeriod setDateFormat:@"a"]; + } + + NSString *dateString; + if ([date compare:midnight] == NSOrderedDescending) { + dateString = [NSString stringWithFormat:@"%@%@", + [todayFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } else if ([date compare:yesterday] == NSOrderedDescending) { + dateString = [NSString stringWithFormat:@"%@%@", + [yesterdayFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } else { + dateString = [NSString stringWithFormat:@"%@%@", + [dateFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } + dateString = [dateString stringByReplacingOccurrencesOfString:@"Sth" + withString:[Utilities suffixForDayInDate:date]]; - return [formatter stringFromDate:date]; + return dateString; +} + ++ (NSString *)formatShortDateFromTimestamp:(NSInteger)timestamp { + if (!timestamp) timestamp = [[NSDate date] timeIntervalSince1970]; + + NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)timestamp]; + static NSDateFormatter *dateFormatter = nil; + static NSDateFormatter *todayFormatter = nil; + static NSDateFormatter *yesterdayFormatter = nil; + static NSDateFormatter *formatterPeriod = nil; + + NSDate *today = [NSDate date]; + NSDateComponents *components = [[NSCalendar currentCalendar] + components:NSIntegerMax + fromDate:today]; + [components setHour:0]; + [components setMinute:0]; + [components setSecond:0]; + NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components]; + NSDate *yesterday = [NSDate dateWithTimeInterval:-60*60*24 sinceDate:midnight]; + + if (!dateFormatter || !todayFormatter || !yesterdayFormatter || !formatterPeriod) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"dd LLL y, h:mm"]; + todayFormatter = [[NSDateFormatter alloc] init]; + [todayFormatter setDateFormat:@"h:mm"]; + yesterdayFormatter = [[NSDateFormatter alloc] init]; + [yesterdayFormatter setDateFormat:@"'Yesterday', h:mm"]; + formatterPeriod = [[NSDateFormatter alloc] init]; + [formatterPeriod setDateFormat:@"a"]; + } + + NSString *dateString; + if ([date compare:midnight] == NSOrderedDescending) { + dateString = [NSString stringWithFormat:@"%@%@", + [todayFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } else if ([date compare:yesterday] == NSOrderedDescending) { + dateString = [NSString stringWithFormat:@"%@%@", + [yesterdayFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } else { + dateString = [NSString stringWithFormat:@"%@%@", + [dateFormatter stringFromDate:date], + [[formatterPeriod stringFromDate:date] lowercaseString]]; + } + + return dateString; +} + ++ (NSString *)suffixForDayInDate:(NSDate *)date { + NSInteger day = [[[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] components:NSDayCalendarUnit fromDate:date] day]; + if (day == 11) { + return @"th"; + } else if (day % 10 == 1) { + return @"st"; + } else if (day % 10 == 2) { + return @"nd"; + } else if (day % 10 == 3) { + return @"rd"; + } else { + return @"th"; + } } @end \ No newline at end of file diff --git a/clients/ios/NewsBlur.xcodeproj/project.pbxproj b/clients/ios/NewsBlur.xcodeproj/project.pbxproj index 36eb1513d..9f04df064 100755 --- a/clients/ios/NewsBlur.xcodeproj/project.pbxproj +++ b/clients/ios/NewsBlur.xcodeproj/project.pbxproj @@ -2606,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"; }; @@ -2638,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; }; diff --git a/clients/ios/NewsBlur_Prefix.pch b/clients/ios/NewsBlur_Prefix.pch index 8c9950395..eca4c9f46 100644 --- a/clients/ios/NewsBlur_Prefix.pch +++ b/clients/ios/NewsBlur_Prefix.pch @@ -5,7 +5,7 @@ #import #import -//#define DEBUG 1 +#define DEBUG 1 #ifdef DEBUG #define BACKGROUND_REFRESH_SECONDS -5 diff --git a/utils/story_functions.py b/utils/story_functions.py index 1441b4018..3debbc6e2 100644 --- a/utils/story_functions.py +++ b/utils/story_functions.py @@ -17,25 +17,39 @@ from vendor import reseekfile # COMMENTS_RE = re.compile('\') COMMENTS_RE = re.compile('\