Custom colors via defaults plist

This commit is contained in:
relikd
2019-09-26 16:24:51 +02:00
parent 23b5bba794
commit b25565c74f
11 changed files with 126 additions and 56 deletions

View File

@@ -24,6 +24,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader - *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
- *UI:* Accessibility hints for most UI elements - *UI:* Accessibility hints for most UI elements
- *UI*: Show welcome message upon first usage (empty db) - *UI*: Show welcome message upon first usage (empty db)
- *UI*: Custom colors via user defaults plist (bar icon tint & unread indicator)
- Welcome message also adds Github releases feed - Welcome message also adds Github releases feed
- Config URL scheme `barss:` with `open/preferences`, `config/fixcache`, and `backup/show` - Config URL scheme `barss:` with `open/preferences`, `config/fixcache`, and `backup/show`

View File

@@ -95,25 +95,36 @@ If you prefer the optimized release version go to `Product > Archive`.
Hidden options 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 listing contains of options that have no UI that can be configured.
Most likely, you wouldn't ever stumble upon these if not reading this chapter.
**Note:** To reset an option run `defaults delete de.relikd.baRSS {KEY}`, where `{KEY}` is an option from below.
1. If you hold down the option key and click on an article item, you can mark a single item (un-)read.
2. 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): 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``` 3. In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default).
2) If you hold down the option key and click on an article item, you can mark a single item (un-)read.
3) 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 limit: With this Terminal command you can customize this limit:
```
defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
```
```defaults write de.relikd.baRSS shortArticleNamesLimit -int 50``` 4. Limit the number of displayed articles per feed menu.
4) Limit the number of displayed articles per feed menu.
**Note:** displayed unread count may be different than the unread items inside ('Open unread' will open hidden items too). **Note:** displayed unread count may be different than the unread items inside ('Open unread' will open hidden items too).
```
defaults write de.relikd.baRSS articlesInMenuLimit -int 40
```
```defaults write de.relikd.baRSS articlesInMenuLimit -int 40``` 5. You can change the appearance of colors throughout the application. E.g., The tint color of the menu bar icon and the color of the blue dot of unread articles.
```
defaults write de.relikd.baRSS colorStatusIconTint -string "#37F"
defaults write de.relikd.baRSS colorUnreadTickMark -string "#FBA33A"
```

View File

@@ -22,14 +22,6 @@
@import Cocoa; @import Cocoa;
@interface NSColor (RandomColor)
/// just for testing purposes
+ (NSColor*)randomColor;
/// RGB color with (251, 163, 58)
+ (NSColor*)rssOrange;
@end
/// Draw separator line in @c NSOutlineView /// Draw separator line in @c NSOutlineView
IB_DESIGNABLE IB_DESIGNABLE
@interface DrawSeparator : NSView @interface DrawSeparator : NSView

View File

@@ -22,20 +22,7 @@
#import "DrawImage.h" #import "DrawImage.h"
#import "Constants.h" #import "Constants.h"
#import "UserPrefs.h"
@implementation NSColor (RandomColor)
/// @return Color with random R, G, B values for testing purposes
+ (NSColor*)randomColor {
return [NSColor colorWithRed:(arc4random()%50+20)/100.0
green:(arc4random()%50+20)/100.0
blue:(arc4random()%50+20)/100.0
alpha:1];
}
/// @return Orange color that is typically used for RSS
+ (NSColor*)rssOrange {
return [NSColor colorWithCalibratedRed:251/255.0 green:163/255.0 blue:58/255.0 alpha:1.0];
}
@end
@implementation DrawSeparator @implementation DrawSeparator
@@ -278,14 +265,14 @@ static void DrawRSSIcon(CGRect r, CGColorRef color, BOOL background, BOOL connec
} }
/// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves) /// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves)
static void DrawRSSGradientIcon(CGRect r) { static void DrawRSSGradientIcon(CGRect r, NSColor *color) {
const CGFloat size = ShorterSide(r.size); const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext; CGContextRef c = NSGraphicsContext.currentContext.CGContext;
DrawRoundedFrame(c, r, NSColor.whiteColor.CGColor, YES, 0.4, 1.0, 0.7); DrawRoundedFrame(c, r, NSColor.whiteColor.CGColor, YES, 0.4, 1.0, 0.7);
// Gradient // Gradient
CGContextSaveGState(c); CGContextSaveGState(c);
CGContextClip(c); CGContextClip(c);
DrawGradient(c, size, [NSColor rssOrange]); DrawGradient(c, size, color);
CGContextRestoreGState(c); CGContextRestoreGState(c);
// Bars // Bars
AddRSSIconPath(c, size, YES); AddRSSIconPath(c, size, YES);
@@ -297,7 +284,8 @@ static void DrawUnreadIcon(CGRect r, NSColor *color) {
CGFloat size = ShorterSide(r.size) / 2.0; CGFloat size = ShorterSide(r.size) / 2.0;
CGContextRef c = NSGraphicsContext.currentContext.CGContext; CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
SetContentScale(c, r.size, 0.8); SetContentScale(c, r.size, 0.7);
CGContextTranslateCTM(c, 0, size * -0.15); // align with baseline of menu item text
CGContextSetFillColorWithColor(c, color.CGColor); CGContextSetFillColorWithColor(c, color.CGColor);
PathAddRing(path, size, size * 0.7); PathAddRing(path, size, size * 0.7);
@@ -325,12 +313,17 @@ static void Register(CGFloat size, NSImageName name, NSString *description, BOOL
/// Register all icons that require custom drawing in @c ImageNamed cache /// Register all icons that require custom drawing in @c ImageNamed cache
void RegisterImageViewNames(void) { void RegisterImageViewNames(void) {
const CGColorRef black = [NSColor controlTextColor].CGColor; const CGColorRef black = [NSColor controlTextColor].CGColor;
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r); return YES; }); NSColor* const orange = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, orange); return YES; });
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; }); Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; });
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; }); Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; });
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; }); Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; });
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, YES); return YES; });
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, NO); return YES; }); NSColor *c1 = [UserPrefs defaultColor:orange forKey:@"colorStatusIconTint"];
Register(12, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor systemBlueColor]); return YES; }); NSColor *c2 = [UserPrefs defaultColor:[NSColor systemBlueColor] forKey:@"colorUnreadTickMark"];
// TODO: user selected color for rss bar icon & unread dot
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, YES); return YES; });
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, NO); return YES; });
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, c2); return YES; });
} }

View File

@@ -33,9 +33,10 @@
+ (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls; + (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls;
// Hidden Plist Properties // Hidden Plist Properties
+ (NSUInteger)openFewLinksLimit; // Change with: 'defaults write de.relikd.baRSS openFewLinksLimit -int 10' + (NSUInteger)openFewLinksLimit; // Change with: defaults write de.relikd.baRSS openFewLinksLimit -int 10
+ (NSUInteger)shortArticleNamesLimit; // Change with: 'defaults write de.relikd.baRSS shortArticleNamesLimit -int 50' + (NSUInteger)shortArticleNamesLimit; // Change with: defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
+ (NSUInteger)articlesInMenuLimit; // Change with: 'defaults write de.relikd.baRSS articlesInMenuLimit -int 40' + (NSUInteger)articlesInMenuLimit; // Change with: defaults write de.relikd.baRSS articlesInMenuLimit -int 40
+ (NSColor*)defaultColor:(NSColor*)defaultColor forKey:(NSString*)key; // Change with: defaults write de.relikd.baRSS {KEY} -string "#FBA33A"
// Application Info Plist // Application Info Plist
+ (NSString*)appName; + (NSString*)appName;

View File

@@ -22,6 +22,7 @@
#import "UserPrefs.h" #import "UserPrefs.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "NSString+Ext.h"
@implementation UserPrefs @implementation UserPrefs
@@ -84,6 +85,18 @@
/// Default: @c 40 /// Default: @c 40
+ (NSUInteger)articlesInMenuLimit { return [self defaultUInt:40 forKey:@"articlesInMenuLimit"]; } + (NSUInteger)articlesInMenuLimit { return [self defaultUInt:40 forKey:@"articlesInMenuLimit"]; }
/// @return Returns @c defaultColor if defaults value couldn't be parsed or wasn't modified by user.
+ (NSColor*)defaultColor:(NSColor*)defaultColor forKey:(NSString*)key {
NSString *colorStr = [[NSUserDefaults standardUserDefaults] stringForKey:key];
if (colorStr) {
NSColor *color = [colorStr hexColor];
if (color) return color;
NSLog(@"Error reading defaults '%@'. Hex color '%@' is invalid. It should be of the form #RBG or #RRGGBB.", key, colorStr);
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
return defaultColor;
}
#pragma mark - Application Info Plist #pragma mark - Application Info Plist

View File

@@ -70,7 +70,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>13723</string> <string>13931</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.news</string> <string>public.app-category.news</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -28,8 +28,9 @@
@interface NSError (Ext) @interface NSError (Ext)
// Generators // Generators
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason; + (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
+ (instancetype)canceledByUser;
+ (instancetype)feedURLNotFound:(NSURL*)url; + (instancetype)feedURLNotFound:(NSURL*)url;
+ (instancetype)canceledByUser;
//+ (instancetype)formattingError:(NSString*)description;
// User notification // User notification
- (BOOL)inCaseLog:(nullable const char*)title; - (BOOL)inCaseLog:(nullable const char*)title;
- (BOOL)inCasePresent:(NSApplication*)app; - (BOOL)inCasePresent:(NSApplication*)app;

View File

@@ -115,17 +115,23 @@ static const char* CodeDescription(NSInteger code) {
return [self errorWithDomain:NSURLErrorDomain code:errCode userInfo:info]; return [self errorWithDomain:NSURLErrorDomain code:errCode userInfo:info];
} }
/// Generate @c NSError for user canceled operation. With title "Operation canceled.".
+ (instancetype)canceledByUser {
NSDictionary *info = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Operation canceled.", nil) };
return [self errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:info];
}
/// Generate @c NSError for webpages that don't contain feed urls. /// Generate @c NSError for webpages that don't contain feed urls.
+ (instancetype)feedURLNotFound:(NSURL*)url { + (instancetype)feedURLNotFound:(NSURL*)url {
return RSXMLMakeErrorWrongParser(RSXMLErrorExpectingFeed, RSXMLErrorExpectingHTML, url); return RSXMLMakeErrorWrongParser(RSXMLErrorExpectingFeed, RSXMLErrorExpectingHTML, url);
} }
/// Generate @c NSError for user canceled operation. With title "Operation was canceled."
+ (instancetype)canceledByUser {
return [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
}
/*// Generate @c NSError for invalid or malformed input. With title "The value is invalid."
+ (instancetype)formattingError:(NSString*)description {
NSDictionary *info = nil;
if (description) info = @{ NSLocalizedRecoverySuggestionErrorKey: description };
return [NSError errorWithDomain:NSCocoaErrorDomain code:NSFormattingError userInfo:info];
}*/
// --------------------------------------------------------------- // ---------------------------------------------------------------
// | MARK: - User notification // | MARK: - User notification
// --------------------------------------------------------------- // ---------------------------------------------------------------

View File

@@ -22,7 +22,11 @@
@import Cocoa; @import Cocoa;
@interface NSString (Ext) @interface NSString (PlainHTML)
+ (NSString*)plainTextFromHTMLData:(NSData*)data; + (NSString*)plainTextFromHTMLData:(NSData*)data;
- (nonnull NSString*)htmlToPlainText; - (nonnull NSString*)htmlToPlainText;
@end @end
@interface NSString (HexColor)
- (nullable NSColor*)hexColor;
@end

View File

@@ -22,7 +22,7 @@
#import "NSString+Ext.h" #import "NSString+Ext.h"
@implementation NSString (Ext) @implementation NSString (PlainHTML)
/// Init string with @c NSUTF8StringEncoding and call @c htmlToPlainText /// Init string with @c NSUTF8StringEncoding and call @c htmlToPlainText
+ (NSString*)plainTextFromHTMLData:(NSData*)data { + (NSString*)plainTextFromHTMLData:(NSData*)data {
@@ -112,3 +112,51 @@ static inline BOOL CLOSE(NSString *tag, NSString *match) {
} }
@end @end
@implementation NSString (HexColor)
/**
Color from hex string with format: @c #[0x|0X]([A]RGB|[AA]RRGGBB)
@return @c nil if string is not properly formatted.
*/
- (nullable NSColor*)hexColor {
if ([self characterAtIndex:0] != '#') // must start with '#'
return nil;
NSScanner *scanner = [NSScanner scannerWithString:self];
scanner.scanLocation = 1;
unsigned int value;
if (![scanner scanHexInt:&value])
return nil;
NSUInteger len = scanner.scanLocation - 1; // -'#'
if (len > 1 && ([self characterAtIndex:2] == 'x' || [self characterAtIndex:3] == 'X'))
len -= 2; // ignore '0x'RRGGBB
unsigned int r = 0, g = 0, b = 0, a = 255;
switch (len) {
case 4: // #ARGB
// ignore alpha for now
// a = (value >> 8) & 0xF0; a = a | (a >> 4);
case 3: // #RGB
r = (value >> 4) & 0xF0; r = r | (r >> 4);
g = (value) & 0xF0; g = g | (g >> 4);
b = (value) & 0x0F; b = b | (b << 4);
break;
case 8: // #AARRGGBB
// a = (value >> 24) & 0xFF;
case 6: // #RRGGBB
r = (value >> 16) & 0xFF;
g = (value >> 8) & 0xFF;
b = (value) & 0xFF;
break;
default:
return nil;
}
return [NSColor colorWithCalibratedRed:r/255.f green:g/255.f blue:b/255.f alpha:a/255.f];
}
@end