From 7e68c1752cf9acc7c8701f1d63aaf23f7cc112fb Mon Sep 17 00:00:00 2001 From: relikd Date: Thu, 14 Feb 2019 18:31:28 +0100 Subject: [PATCH] Auto increase bundle version, handling url scheme, image generation --- README.md | 88 ++++++++++----------- baRSS.xcodeproj/project.pbxproj | 31 ++++++-- baRSS/AppHook.m | 13 +-- baRSS/Core Data/Feed+Ext.h | 3 +- baRSS/Core Data/Feed+Ext.m | 35 +++----- baRSS/Core Data/FeedGroup+Ext.h | 3 +- baRSS/Core Data/FeedGroup+Ext.m | 44 +++++++---- baRSS/Info.plist | 6 +- baRSS/Preferences/Feeds Tab/SettingsFeeds.m | 2 +- 9 files changed, 124 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 59dd161..0e1f6c0 100644 --- a/README.md +++ b/README.md @@ -2,74 +2,72 @@ ![screenshot](doc/screenshot.png) -For nearly a decade I've been using the then free version of [RSS Menu](https://itunes.apple.com/us/app/rss-menu/id423069534). However, with the release of macOS Mojave, 32bit applications are no longer supported. Furthermore, the currently available version in the Mac App Store was last updated in 2014 (as of writing). +For nearly a decade I've been using the then free version of [RSS Menu](https://itunes.apple.com/us/app/rss-menu/id423069534). +However, with the release of macOS Mojave, 32bit applications are no longer supported. +Furthermore, the currently available version in the Mac App Store was last updated in 2014 (as of writing). -*baRSS* was build from scratch with a minimal footprint in mind. It will be available on the AppStore eventually. If you want a feature to be added, drop me an email or create an issue. Look at the other issues, in case somebody else already filed one similar. If you like this project and want to say thank you drop me a line (or other stuff like money). Regardless, I'll continue development as long as I'm using it on my own. Admittedly, I've invested way too much time in this project already (1400h+) … +*baRSS* was build from scratch with a minimal footprint in mind. It will be available on the AppStore eventually. +If you want a feature to be added, drop me an email or create an issue. +Look at the other issues, in case somebody else already filed one similar. +If you like this project and want to say thank you drop me a line (or other stuff like money). +Regardless, I'll continue development as long as I'm using it on my own. +Admittedly, I've invested way too much time in this project already (1400h+) … -Why is this project not written in Swift? ------------------------------------------ +### Why is this project not written in Swift? -Actually, I started this project with Swift. Even without adding much functionality, the app was exceeding the 10 Mb file size. Compared to the nearly finished Alpha version with 500 Kb written in Objective-C. The reason for that, Swift frameworks are always packed into the final application. I decided that this level of encapsulation is a waste of space for such a small application. +Actually, I started this project with Swift. Even without adding much functionality, the app was exceeding the 10 Mb file size. +Compared to the nearly finished Alpha version with 500 Kb written in Objective-C. +The reason for that, Swift frameworks are always packed into the final application. +I decided that this level of encapsulation is a waste of space for such a small application. -3rd Party Libraries -------------------- +### 3rd Party Libraries -This project uses a modified version of Brent Simmons [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing. RSXML is licensed under a MIT license (same as this project). +This project uses a modified version of Brent Simmons [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing. +RSXML is licensed under a MIT license (same as this project). -Current project state ---------------------- - -All basic functionality is there. What's missing? - -- Authenticated feeds -- Online sync with other services -- Text / UI localisation -- App icon & UI icons - -All in all, the software is in a usable state. The remaining features will be added in the coming weeks. - Hidden options -------------- -1) When holding down the option key, the menu will show an item to open only a few unread items at a time. This number can be changed with the following Terminal command (default: 10): +1) When holding down the option key, the menu will show an item to open only a few unread items at a time. +This number can be changed with the following Terminal command (default: 10): - defaults write de.relikd.baRSS openFewLinksLimit -int 10 +```defaults write de.relikd.baRSS openFewLinksLimit -int 10``` -2) In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default). With this Terminal command you can customize this number: +2) In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default). +With this Terminal command you can customize this number: + +```defaults write de.relikd.baRSS shortArticleNamesLimit -int 50``` + +3) If you hold down the option key and click on an article item, you can mark a single item (un-)read. - defaults write de.relikd.baRSS shortArticleNamesLimit -int 50 ToDo ---- -- [ ] Edit feed - - [ ] Show statistics - - [x] How often gets the feed updated (min, max, avg) - - [ ] Automatically choose best interval? - - [x] Show time of next update +- [ ] Missing + - [ ] App Icon & UI icons (a shout out to all designers out there!) + - [ ] Text / UI localization - [ ] Feeds with authentication + - [ ] Sandbox (does work, except for:) + - [ ] Default RSS application checkbox (disable or other workaround) -- [ ] Other - - [ ] App Icon - - [ ] Translate text to different languages - - [x] Download with ephemeral url session? - - [ ] Add Sandboxing - - [ ] Disable Startup checkbox (or other workaround) - - -- [ ] Additional features - - [ ] Sync with online services! +- [ ] Nice to have (... on increased demand) + - [ ] Automatically choose best update interval (e.g., avg) + - [ ] Sync with online services - [ ] Notification Center - - [ ] Sleep timer. (e.g., disable updates during working hours) - - [ ] Pure image feed? (show images directly in menu) + - [ ] Distraction Mode + - [ ] Distract less: Sleep timer. (e.g., disable updates during working hours) + - [ ] Distract more: Automatically open feed items + - [ ] Add support for media types + - [ ] music / video? (open media player) + - [ ] Pure image feed? (show images directly in menu) + - [ ] Per feed / group settings + - [ ] select launch application (e.g., for podcasts) + - [ ] exclude unread count from menu bar (e.g., unimportant feeds) - [ ] ~~Infinite storage. (load more button)~~ - - [ ] Automatically open feed items? - - [ ] Per feed launch application (e.g., for podcasts) - - [ ] Per group setting to exclude unread count from menu bar - diff --git a/baRSS.xcodeproj/project.pbxproj b/baRSS.xcodeproj/project.pbxproj index 30d22c2..4ee136a 100644 --- a/baRSS.xcodeproj/project.pbxproj +++ b/baRSS.xcodeproj/project.pbxproj @@ -112,7 +112,7 @@ 54A07A7E220E04CF00082C51 /* NSFetchRequest+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSFetchRequest+Ext.m"; sourceTree = ""; }; 54A07A80220E723D00082C51 /* MapUnreadTotal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapUnreadTotal.h; sourceTree = ""; }; 54A07A81220E723D00082C51 /* MapUnreadTotal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapUnreadTotal.m; sourceTree = ""; }; - 54ACC27C21061B3B0020715F /* baRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = baRSS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 54ACC27C21061B3B0020715F /* baRSS Beta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "baRSS Beta.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = ""; }; 54ACC28521061B3C0020715F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -261,7 +261,7 @@ 54ACC27D21061B3B0020715F /* Products */ = { isa = PBXGroup; children = ( - 54ACC27C21061B3B0020715F /* baRSS.app */, + 54ACC27C21061B3B0020715F /* baRSS Beta.app */, 54CC042C2162532800A48795 /* baRSS-Helper.app */, ); name = Products; @@ -322,6 +322,7 @@ 544DCCBB212A2B4D002DBC46 /* Embed Frameworks */, 544DCCBC212A2B5A002DBC46 /* CopyFiles */, 54CC043D2162565F00A48795 /* CopyFiles */, + 543964EE2215C27B0016AAA3 /* ShellScript */, ); buildRules = ( ); @@ -329,7 +330,7 @@ ); name = baRSS; productName = baRRS; - productReference = 54ACC27C21061B3B0020715F /* baRSS.app */; + productReference = 54ACC27C21061B3B0020715F /* baRSS Beta.app */; productType = "com.apple.product-type.application"; }; 54CC042B2162532800A48795 /* baRSS-Helper */ = { @@ -421,6 +422,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 543964EE2215C27B0016AAA3 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# https://crunchybagel.com/auto-incrementing-build-numbers-in-xcode/\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 54ACC27821061B3B0020715F /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -617,7 +638,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(TARGET_NAME) Beta"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -666,7 +687,7 @@ "$(FRAMEWORK_SEARCH_PATHS)", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta; + PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; diff --git a/baRSS/AppHook.m b/baRSS/AppHook.m index 8463d3d..db58c90 100644 --- a/baRSS/AppHook.m +++ b/baRSS/AppHook.m @@ -55,15 +55,18 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - if ([url hasPrefix:@"feed:"]) { - // TODO: handle other app schemes like configuration export / import - url = [url substringFromIndex:5]; - if ([url hasPrefix:@"//"]) - url = [url substringFromIndex:2]; + NSString *scheme = [[[NSURL URLWithString:url] scheme] lowercaseString]; + url = [url substringFromIndex:scheme.length + 1]; // + ':' + if (url.length >= 2 && [[url substringToIndex:2] isEqualToString:@"//"]) { + url = [url substringFromIndex:2]; + } + if ([scheme isEqualToString:@"feed"]) { [FeedDownload autoDownloadAndParseURL:url successBlock:^{ [self reopenPreferencesIfOpen]; }]; } + // TODO: handle other app schemes like configuration export / import + // NSURLComponents *comp = [NSURLComponents componentsWithString:url]; } diff --git a/baRSS/Core Data/Feed+Ext.h b/baRSS/Core Data/Feed+Ext.h index e548d82..ce55384 100644 --- a/baRSS/Core Data/Feed+Ext.h +++ b/baRSS/Core Data/Feed+Ext.h @@ -26,6 +26,8 @@ @class RSParsedFeed; @interface Feed (Ext) +@property (nonnull, readonly) NSImage* iconImage16; + // Generator methods / Feed update + (instancetype)newFeedAndMetaInContext:(NSManagedObjectContext*)context; + (instancetype)appendToRootWithDefaultIntervalInContext:(NSManagedObjectContext*)moc; @@ -35,6 +37,5 @@ // Article properties - (NSArray*)sortedArticles; // Icon -- (NSImage*)iconImage16; - (BOOL)setIconImage:(NSImage*)img; @end diff --git a/baRSS/Core Data/Feed+Ext.m b/baRSS/Core Data/Feed+Ext.m index cba06b4..24f7df7 100644 --- a/baRSS/Core Data/Feed+Ext.m +++ b/baRSS/Core Data/Feed+Ext.m @@ -62,7 +62,7 @@ item.title = self.group.nameOrError; item.toolTip = self.subtitle; item.enabled = (self.articles.count > 0); - item.image = [self iconImage16]; + item.image = self.iconImage16; item.representedObject = self.indexPath; item.target = [self class]; item.action = @selector(didClickOnMenuItem:); @@ -200,30 +200,17 @@ /** @return Return @c 16x16px image. Either from core data storage or generated default RSS icon. */ -- (NSImage*)iconImage16 { - NSData *imgData = self.icon.icon; - if (imgData) - { - NSImage *img = [[NSImage alloc] initWithData:imgData]; - [img setSize:NSMakeSize(16, 16)]; - return img; - } - else if (self.articles.count == 0) - { - static NSImage *warningIcon; - if (!warningIcon) { - warningIcon = [NSImage imageNamed:NSImageNameCaution]; - [warningIcon setSize:NSMakeSize(16, 16)]; - } - return warningIcon; - } - else - { - static NSImage *defaultRSSIcon; // TODO: setup imageNamed: for default rss icon - if (!defaultRSSIcon) - defaultRSSIcon = [RSSIcon iconWithSize:16]; - return defaultRSSIcon; +- (nonnull NSImage*)iconImage16 { + NSImage *img = nil; + if (self.articles.count == 0) { + img = [NSImage imageNamed:NSImageNameCaution]; + } else if (self.icon.icon) { + img = [[NSImage alloc] initWithData:self.icon.icon]; + } else { + return [RSSIcon iconWithSize:16]; // TODO: setup imageNamed: for default rss icon? } + [img setSize:NSMakeSize(16, 16)]; + return img; } /** diff --git a/baRSS/Core Data/FeedGroup+Ext.h b/baRSS/Core Data/FeedGroup+Ext.h index 08b7ad5..b5440c5 100644 --- a/baRSS/Core Data/FeedGroup+Ext.h +++ b/baRSS/Core Data/FeedGroup+Ext.h @@ -34,11 +34,12 @@ typedef NS_ENUM(int16_t, FeedGroupType) { /// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR. @property (nonatomic) FeedGroupType type; @property (nonnull, readonly) NSString *nameOrError; +@property (nonnull, readonly) NSImage* groupIconImage16; +@property (nonnull, readonly) NSImage* iconImage16; + (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)context; - (void)setParent:(FeedGroup *)parent andSortIndex:(int32_t)sortIndex; - (void)setNameIfChanged:(NSString*)name; -- (NSImage*)groupIconImage16; - (NSMenuItem*)newMenuItem; // Handle children and parents - (NSString*)indexPathString; diff --git a/baRSS/Core Data/FeedGroup+Ext.m b/baRSS/Core Data/FeedGroup+Ext.m index 2a99985..ca66785 100644 --- a/baRSS/Core Data/FeedGroup+Ext.m +++ b/baRSS/Core Data/FeedGroup+Ext.m @@ -27,6 +27,33 @@ @implementation FeedGroup (Ext) +#pragma mark - Properties + +/// @return Returns "(error)" if @c self.name is @c nil. +- (nonnull NSString*)nameOrError { + return (self.name ? self.name : NSLocalizedString(@"(error)", nil)); +} + +/// @return Return @c 16x16px NSImageNameFolder image. +- (nonnull NSImage*)groupIconImage16 { + NSImage *groupIcon = [NSImage imageNamed:NSImageNameFolder]; + groupIcon.size = NSMakeSize(16, 16); + return groupIcon; +} + +/** + @return Return @c 16x16px image. + Either feed icon ( @c type @c == @c FEED ) or @c NSImageNameFolder ( @c type @c == @c GROUP ). + */ +- (nonnull NSImage*)iconImage16 { + if (self.type == FEED) + return self.feed.iconImage16; + return self.groupIconImage16; +} + + +#pragma mark - Generator + /// Create new instance and set @c Feed and @c FeedMeta if group type is @c FEED + (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)moc { FeedGroup *fg = [[FeedGroup alloc] initWithEntity: FeedGroup.entity insertIntoManagedObjectContext:moc]; @@ -49,27 +76,12 @@ self.name = name; } -/// @return Return static @c 16x16px NSImageNameFolder image. -- (NSImage*)groupIconImage16 { - static NSImage *groupIcon; - if (!groupIcon) { - groupIcon = [NSImage imageNamed:NSImageNameFolder]; - groupIcon.size = NSMakeSize(16, 16); - } - return groupIcon; -} - -/// @return Returns "(error)" if @c self.name is @c nil. -- (nonnull NSString*)nameOrError { - return (self.name ? self.name : NSLocalizedString(@"(error)", nil)); -} - /// @return Fully initialized @c NSMenuItem with @c title and @c image. - (NSMenuItem*)newMenuItem { NSMenuItem *item = [NSMenuItem new]; item.title = self.nameOrError; item.enabled = (self.children.count > 0); - item.image = [self groupIconImage16]; + item.image = self.groupIconImage16; item.representedObject = self.objectID; return item; } diff --git a/baRSS/Info.plist b/baRSS/Info.plist index d9a8ea7..50d9cc2 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9 + 0.9.1 CFBundleURLTypes @@ -32,7 +32,7 @@ CFBundleVersion - 1 + 1032 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement @@ -43,7 +43,7 @@ NSHumanReadableCopyright - Copyright © 2018 relikd. Public Domain. + Copyright © 2019 relikd. Public Domain. NSPrincipalClass AppHook diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m index 7eb2e17..26241ef 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m @@ -419,7 +419,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; return cellView; // refresh cell already skipped with the above if condition } else { cellView.textField.objectValue = fg.name; - cellView.imageView.image = (fg.type == GROUP ? [NSImage imageNamed:NSImageNameFolder] : [fg.feed iconImage16]); + cellView.imageView.image = fg.iconImage16; } return cellView; }