Refactoring Part 1: Dynamic menus (stable)

This commit is contained in:
relikd
2018-11-24 14:18:06 +01:00
parent 080991ebc4
commit 6223d1a169
22 changed files with 1026 additions and 973 deletions

View File

@@ -0,0 +1,32 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "Feed+CoreDataClass.h"
@class RSParsedFeed;
@interface Feed (Ext)
+ (Feed*)feedFromRSS:(RSParsedFeed*)obj inContext:(NSManagedObjectContext*)context alreadyRead:(NSArray<NSString*>*)urls unread:(int*)unreadCount;
- (NSArray<NSString*>*)alreadyReadURLs;
- (void)markAllItemsRead;
- (void)markAllItemsUnread;
@end

View File

@@ -0,0 +1,89 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "Feed+Ext.h"
#import "FeedConfig+Ext.h"
#import "FeedItem+CoreDataClass.h"
#import <RSXML/RSXML.h>
@implementation Feed (Ext)
+ (FeedItem*)createFeedItemFrom:(RSParsedArticle*)entry inContext:(NSManagedObjectContext*)context {
FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:context];
b.guid = entry.guid;
b.title = entry.title;
b.abstract = entry.abstract;
b.body = entry.body;
b.author = entry.author;
b.link = entry.link;
b.published = entry.datePublished;
return b;
}
+ (Feed*)feedFromRSS:(RSParsedFeed*)obj inContext:(NSManagedObjectContext*)context alreadyRead:(NSArray<NSString*>*)urls unread:(int*)unreadCount {
Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:context];
a.title = obj.title;
a.subtitle = obj.subtitle;
a.link = obj.link;
for (RSParsedArticle *article in obj.articles) {
FeedItem *b = [self createFeedItemFrom:article inContext:context];
if ([urls containsObject:b.link]) {
b.unread = NO;
} else {
*unreadCount += 1;
}
[a addItemsObject:b];
}
return a;
}
- (NSArray<NSString*>*)alreadyReadURLs {
if (!self.items || self.items.count == 0) return nil;
NSMutableArray<NSString*> *mArr = [NSMutableArray arrayWithCapacity:self.items.count];
for (FeedItem *f in self.items) {
if (!f.unread) {
[mArr addObject:f.link];
}
}
return mArr;
}
- (void)markAllItemsRead {
[self markAllArticlesRead:YES];
}
- (void)markAllItemsUnread {
[self markAllArticlesRead:NO];
}
- (void)markAllArticlesRead:(BOOL)readFlag {
int count = 0;
for (FeedItem *i in self.items) {
if (i.unread == readFlag) {
i.unread = !readFlag;
++count;
}
}
[self.config markUnread:(readFlag ? -count : +count) ancestorsOnly:NO];
}
@end

View File

@@ -0,0 +1,48 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "FeedConfig+CoreDataClass.h"
@class FeedItem, RSParsedFeed;
@interface FeedConfig (Ext)
/// Enum type to distinguish different @c FeedConfig types
typedef enum int16_t {
GROUP = 0,
FEED = 1,
SEPARATOR = 2
} FeedConfigType;
@property (getter=typ, setter=setTyp:) FeedConfigType typ;
- (NSArray<FeedConfig*>*)sortedChildren;
- (NSIndexPath*)indexPath;
- (void)markUnread:(int)count ancestorsOnly:(BOOL)flag;
- (void)calculateAndSetScheduled;
- (BOOL)iterateSorted:(BOOL)ordered overDescendantFeeds:(void(^)(Feed*,BOOL*))block;
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
- (void)updateRSSFeed:(RSParsedFeed*)obj;
- (NSString*)readableRefreshString;
- (NSString*)readableDescription;
@end

View File

@@ -0,0 +1,135 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "FeedConfig+Ext.h"
#import "Feed+Ext.h"
#import "FeedMeta+CoreDataClass.h"
#import "Constants.h"
@implementation FeedConfig (Ext)
/// Enum tpye getter see @c FeedConfigType
- (FeedConfigType)typ { return (FeedConfigType)self.type; }
/// Enum type setter see @c FeedConfigType
- (void)setTyp:(FeedConfigType)typ { self.type = typ; }
/**
Sorted children array based on sort order provided in feed settings.
@return Sorted array of @c FeedConfig items.
*/
- (NSArray<FeedConfig*>*)sortedChildren {
if (self.children.count == 0)
return nil;
return [self.children sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
}
/// IndexPath for sorted children starting with root index.
- (NSIndexPath*)indexPath {
if (self.parent == nil)
return [NSIndexPath indexPathWithIndex:(NSUInteger)self.sortIndex];
return [[self.parent indexPath] indexPathByAddingIndex:(NSUInteger)self.sortIndex];
}
/**
Change unread counter for all parents recursively. Result will never be negative.
@param count If negative, mark items read.
*/
- (void)markUnread:(int)count ancestorsOnly:(BOOL)flag {
FeedConfig *par = (flag ? self.parent : self);
while (par) {
[self.managedObjectContext refreshObject:par mergeChanges:YES];
par.unreadCount += count;
NSAssert(par.unreadCount >= 0, @"ERROR ancestorsMarkUnread: Count should never be negative.");
par = par.parent;
}
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountChanged
object:[NSNumber numberWithInt:count]];
}
/// @return Time interval respecting the selected unit. E.g., returns @c 180 for @c '3m'
- (NSTimeInterval)timeInterval {
static const int unit[] = {1, 60, 3600, 86400, 604800}; // smhdw
return self.refreshNum * unit[self.refreshUnit % 5];
}
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
- (void)calculateAndSetScheduled {
self.scheduled = [[NSDate date] dateByAddingTimeInterval:[self timeInterval]];
}
/// Update FeedMeta or create new one if needed.
- (void)setEtag:(NSString*)etag modified:(NSString*)modified {
// TODO: move to separate function and add icon download
if (!self.meta) {
self.meta = [[FeedMeta alloc] initWithEntity:FeedMeta.entity insertIntoManagedObjectContext:self.managedObjectContext];
}
self.meta.httpEtag = etag;
self.meta.httpModified = modified;
}
/// Delete any existing feed object and parse new one. Read state will be copied.
- (void)updateRSSFeed:(RSParsedFeed*)obj {
NSArray<NSString*> *readURLs = [self.feed alreadyReadURLs];
int unreadBefore = self.unreadCount;
int unreadAfter = 0;
if (self.feed)
[self.managedObjectContext deleteObject:(NSManagedObject*)self.feed];
if (obj) {
// TODO: update and dont re-create each time
self.feed = [Feed feedFromRSS:obj inContext:self.managedObjectContext alreadyRead:readURLs unread:&unreadAfter];
}
[self markUnread:(unreadAfter - unreadBefore) ancestorsOnly:NO];
}
- (BOOL)iterateSorted:(BOOL)ordered overDescendantFeeds:(void(^)(Feed*,BOOL*))block {
if (self.feed) {
BOOL stopEarly = NO;
block(self.feed, &stopEarly);
if (stopEarly) return NO;
} else {
for (FeedConfig *fc in (ordered ? [self sortedChildren] : self.children)) {
if (![fc iterateSorted:ordered overDescendantFeeds:block])
return NO;
}
}
return YES;
}
#pragma mark - Printing -
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
- (NSString*)readableRefreshString {
return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]];
}
/// @return Simplified description of the feed object.
- (NSString*)readableDescription {
switch (self.typ) {
case SEPARATOR: return @"-------------";
case GROUP: return [NSString stringWithFormat:@"%@", self.name];
case FEED:
return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.url, [self readableRefreshString]];
}
}
@end