Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5894b12c1d | ||
|
|
0700eebb13 | ||
|
|
4c4a133fe2 | ||
|
|
ccca329630 | ||
|
|
831159904c | ||
|
|
cf3e9e4b4a | ||
|
|
184e5c0882 | ||
|
|
575d1eaec8 |
22
CHANGELOG.md
22
CHANGELOG.md
@@ -5,9 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
## [1.4.1] – 2025-10-27
|
## [1.5.3] – 2025-10-29
|
||||||
|
### Fixed
|
||||||
|
- *Notifications:* Use user-provided feed title instead of server provided title
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.2] – 2025-10-29
|
||||||
### Added
|
### Added
|
||||||
- Notifications
|
- *Notifications:* Reply with "Open in background", "Mark read & dismiss", or "Open but keep unread"
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.1] – 2025-10-27
|
||||||
|
### Fixed
|
||||||
|
- *Status Bar Menu:* Simplified options for "Show only unread"
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.0] – 2025-10-27
|
||||||
|
### Added
|
||||||
|
- *UI:* Notifications
|
||||||
|
|
||||||
|
|
||||||
## [1.4.1] – 2025-07-29
|
## [1.4.1] – 2025-07-29
|
||||||
@@ -215,6 +230,9 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
|||||||
Initial release
|
Initial release
|
||||||
|
|
||||||
|
|
||||||
|
[1.5.3]: https://github.com/relikd/baRSS/compare/v1.5.2...v1.5.3
|
||||||
|
[1.5.2]: https://github.com/relikd/baRSS/compare/v1.5.1...v1.5.2
|
||||||
|
[1.5.1]: https://github.com/relikd/baRSS/compare/v1.5.0...v1.5.1
|
||||||
[1.5.0]: https://github.com/relikd/baRSS/compare/v1.4.1...v1.5.0
|
[1.5.0]: https://github.com/relikd/baRSS/compare/v1.4.1...v1.5.0
|
||||||
[1.4.1]: https://github.com/relikd/baRSS/compare/v1.4.0...v1.4.1
|
[1.4.1]: https://github.com/relikd/baRSS/compare/v1.4.0...v1.4.1
|
||||||
[1.4.0]: https://github.com/relikd/baRSS/compare/v1.3.2...v1.4.0
|
[1.4.0]: https://github.com/relikd/baRSS/compare/v1.3.2...v1.4.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](#download--install)
|
[](#download--install)
|
||||||
[](https://github.com/relikd/baRSS/releases)
|
[](https://github.com/relikd/baRSS/releases)
|
||||||
[](https://github.com/relikd/baRSS/releases)
|
[](https://github.com/relikd/baRSS/releases)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
@@ -35,7 +35,7 @@ Further, tuning the update frequently will decrease the traffic even more.
|
|||||||
Download & Install
|
Download & Install
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Requires macOS High Sierra (10.13) or higher.
|
Requires macOS Mojave (10.14) or higher.
|
||||||
|
|
||||||
### Easy way
|
### Easy way
|
||||||
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
|
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
|
||||||
|
|||||||
@@ -805,7 +805,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 16711;
|
CURRENT_PROJECT_VERSION = 16785;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||||
@@ -823,7 +823,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MARKETING_VERSION = 1.5.0;
|
MARKETING_VERSION = 1.5.3;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -866,7 +866,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 16711;
|
CURRENT_PROJECT_VERSION = 16785;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||||
@@ -881,7 +881,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MARKETING_VERSION = 1.5.0;
|
MARKETING_VERSION = 1.5.3;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -225,9 +225,11 @@
|
|||||||
return nil; // if success == NO, do not modify unread state & exit
|
return nil; // if success == NO, do not modify unread state & exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSInteger countChange = 0;
|
||||||
for (FeedArticle *fa in list) {
|
for (FeedArticle *fa in list) {
|
||||||
if (fa.unread == markRead) { // only if differs
|
if (fa.unread == markRead) { // only if differs
|
||||||
fa.unread = !markRead;
|
fa.unread = !markRead;
|
||||||
|
countChange += markRead ? -1 : +1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[self saveContext:moc andParent:YES];
|
[self saveContext:moc andParent:YES];
|
||||||
@@ -242,8 +244,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[moc reset];
|
[moc reset];
|
||||||
NSNumber *num = [NSNumber numberWithInteger: (markRead ? -1 : +1) * (NSInteger)list.count ];
|
PostNotification(kNotificationTotalUnreadCountChanged, @(countChange));
|
||||||
PostNotification(kNotificationTotalUnreadCountChanged, num);
|
|
||||||
return dbRefs;
|
return dbRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
|
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
|
||||||
/** default: @c YES */ static NSString* const Pref_globalMarkRead = @"globalMarkRead";
|
/** default: @c YES */ static NSString* const Pref_globalMarkRead = @"globalMarkRead";
|
||||||
/** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread";
|
/** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread";
|
||||||
/** default: @c NO */ static NSString* const Pref_globalUnreadOnly = @"globalUnreadOnly";
|
|
||||||
/** default: @c YES */ static NSString* const Pref_globalUnreadCount = @"globalUnreadCount";
|
/** default: @c YES */ static NSString* const Pref_globalUnreadCount = @"globalUnreadCount";
|
||||||
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
|
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
|
||||||
/** default: @c YES */ static NSString* const Pref_groupMarkRead = @"groupMarkRead";
|
/** default: @c YES */ static NSString* const Pref_groupMarkRead = @"groupMarkRead";
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ void UserPrefsInit(void) {
|
|||||||
Pref_feedUnreadIndicator
|
Pref_feedUnreadIndicator
|
||||||
]);
|
]);
|
||||||
defaultsAppend(defs, @NO, @[
|
defaultsAppend(defs, @NO, @[
|
||||||
Pref_globalUnreadOnly, Pref_groupUnreadOnly, Pref_feedUnreadOnly,
|
Pref_groupUnreadOnly, Pref_feedUnreadOnly,
|
||||||
Pref_groupUnreadIndicator,
|
Pref_groupUnreadIndicator,
|
||||||
Pref_feedTruncateTitle,
|
Pref_feedTruncateTitle,
|
||||||
Pref_feedLimitArticles
|
Pref_feedLimitArticles
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#import "UserPrefs.h"
|
#import "UserPrefs.h"
|
||||||
#import "StoreCoordinator.h"
|
#import "StoreCoordinator.h"
|
||||||
#import "Feed+Ext.h"
|
#import "Feed+Ext.h"
|
||||||
|
#import "FeedGroup+Ext.h"
|
||||||
#import "FeedArticle+Ext.h"
|
#import "FeedArticle+Ext.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,6 +10,11 @@
|
|||||||
*/
|
*/
|
||||||
static NSString* const kNotifyIdGlobal = @"global";
|
static NSString* const kNotifyIdGlobal = @"global";
|
||||||
|
|
||||||
|
static NSString* const kCategoryDismissable = @"DISMISSIBLE";
|
||||||
|
static NSString* const kActionOpenBackground = @"OPEN_IN_BACKGROUND";
|
||||||
|
static NSString* const kActionMarkRead = @"MARK_READ_DONT_OPEN";
|
||||||
|
static NSString* const kActionOpenOnly = @"OPEN_ONLY_DONT_MARK_READ";
|
||||||
|
|
||||||
|
|
||||||
@implementation NotifyEndpoint
|
@implementation NotifyEndpoint
|
||||||
|
|
||||||
@@ -18,20 +24,27 @@ static NotificationType notifyType;
|
|||||||
/// Ask user for permission to send notifications @b AND register delegate to respond to alert banner clicks.
|
/// Ask user for permission to send notifications @b AND register delegate to respond to alert banner clicks.
|
||||||
/// @note Called every time user changes notification settings
|
/// @note Called every time user changes notification settings
|
||||||
+ (void)activate {
|
+ (void)activate {
|
||||||
|
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
|
||||||
notifyType = UserPrefsNotificationType();
|
notifyType = UserPrefsNotificationType();
|
||||||
|
|
||||||
// even if disabled, register delegate. This allows to open previously sent notifications
|
// even if disabled, register delegate. This allows to open previously sent notifications
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
singleton = [NotifyEndpoint new];
|
singleton = [NotifyEndpoint new];
|
||||||
UNUserNotificationCenter.currentNotificationCenter.delegate = singleton;
|
center.delegate = singleton;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (notifyType == NotificationTypeDisabled) {
|
if (notifyType == NotificationTypeDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// register action types (allow mark read without opening notification)
|
||||||
|
UNNotificationAction *openBackgroundAction = [UNNotificationAction actionWithIdentifier:kActionOpenBackground title:NSLocalizedString(@"Open in background", nil) options:UNNotificationActionOptionNone];
|
||||||
|
UNNotificationAction *dontOpenAction = [UNNotificationAction actionWithIdentifier:kActionMarkRead title:NSLocalizedString(@"Mark read & dismiss", nil) options:UNNotificationActionOptionNone];
|
||||||
|
UNNotificationAction *dontReadAction = [UNNotificationAction actionWithIdentifier:kActionOpenOnly title:NSLocalizedString(@"Open but keep unread", nil) options:UNNotificationActionOptionNone];
|
||||||
|
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:kCategoryDismissable actions:@[openBackgroundAction, dontOpenAction, dontReadAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
|
||||||
|
[center setNotificationCategories:[NSSet setWithObject:category]];
|
||||||
|
|
||||||
[UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
NSAlert *alert = [[NSAlert alloc] init];
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
@@ -72,7 +85,7 @@ static NotificationType notifyType;
|
|||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
[feed.managedObjectContext obtainPermanentIDsForObjects:@[feed] error:nil];
|
[feed.managedObjectContext obtainPermanentIDsForObjects:@[feed] error:nil];
|
||||||
[self send:feed.notificationID
|
[self send:feed.notificationID
|
||||||
title:feed.title
|
title:feed.group.anyName
|
||||||
body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), count]];
|
body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), count]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +97,7 @@ static NotificationType notifyType;
|
|||||||
}
|
}
|
||||||
[article.managedObjectContext obtainPermanentIDsForObjects:@[article] error:nil];
|
[article.managedObjectContext obtainPermanentIDsForObjects:@[article] error:nil];
|
||||||
[self send:article.notificationID
|
[self send:article.notificationID
|
||||||
title:article.feed.title
|
title:article.feed.group.anyName
|
||||||
body:article.title];
|
body:article.title];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +118,7 @@ static NotificationType notifyType;
|
|||||||
if (title != nil) msg.title = title;
|
if (title != nil) msg.title = title;
|
||||||
if (body != nil) msg.body = body;
|
if (body != nil) msg.body = body;
|
||||||
// common settings:
|
// common settings:
|
||||||
|
msg.categoryIdentifier = kCategoryDismissable;
|
||||||
// TODO: make sound configurable?
|
// TODO: make sound configurable?
|
||||||
msg.sound = [UNNotificationSound defaultSound];
|
msg.sound = [UNNotificationSound defaultSound];
|
||||||
[self send:identifier content: msg];
|
[self send:identifier content: msg];
|
||||||
@@ -167,7 +181,12 @@ static NotificationType notifyType;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[StoreCoordinator updateArticles:articles markRead:YES andOpen:YES inContext:moc];
|
|
||||||
|
// open-in-background performs the same operation as a normal click
|
||||||
|
// the "background" part is triggered by _NOT_ having the UNNotificationActionOptionForeground option
|
||||||
|
BOOL dontOpen = [response.actionIdentifier isEqualToString:kActionMarkRead];
|
||||||
|
BOOL dontMarkRead = [response.actionIdentifier isEqualToString:kActionOpenOnly];
|
||||||
|
[StoreCoordinator updateArticles:articles markRead:!dontMarkRead andOpen:!dontOpen inContext:moc];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -28,9 +28,9 @@
|
|||||||
[self entry:NSLocalizedString(@"Open all unread", nil) c1:Pref_globalOpenUnread c2:Pref_groupOpenUnread c3:Pref_feedOpenUnread];
|
[self entry:NSLocalizedString(@"Open all unread", nil) c1:Pref_globalOpenUnread c2:Pref_groupOpenUnread c3:Pref_feedOpenUnread];
|
||||||
[self entry:NSLocalizedString(@"Mark all read", nil) c1:Pref_globalMarkRead c2:Pref_groupMarkRead c3:Pref_feedMarkRead];
|
[self entry:NSLocalizedString(@"Mark all read", nil) c1:Pref_globalMarkRead c2:Pref_groupMarkRead c3:Pref_feedMarkRead];
|
||||||
[self entry:NSLocalizedString(@"Mark all unread", nil) c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread];
|
[self entry:NSLocalizedString(@"Mark all unread", nil) c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread];
|
||||||
[self entry:NSLocalizedString(@"Show only unread / hide read", nil) c1:Pref_globalUnreadOnly c2:Pref_groupUnreadOnly c3:Pref_feedUnreadOnly];
|
|
||||||
[self entry:NSLocalizedString(@"Number of unread articles", nil) c1:Pref_globalUnreadCount c2:Pref_groupUnreadCount c3:Pref_feedUnreadCount];
|
[self entry:NSLocalizedString(@"Number of unread articles", nil) c1:Pref_globalUnreadCount c2:Pref_groupUnreadCount c3:Pref_feedUnreadCount];
|
||||||
[self entry:NSLocalizedString(@"Indicator for unread articles", nil) c1:nil c2:Pref_groupUnreadIndicator c3:Pref_feedUnreadIndicator];
|
[self entry:NSLocalizedString(@"Indicator for unread articles", nil) c1:nil c2:Pref_groupUnreadIndicator c3:Pref_feedUnreadIndicator];
|
||||||
|
[self entry:NSLocalizedString(@"Show only unread / hide read", nil) c1:nil c2:Pref_groupUnreadOnly c3:Pref_feedUnreadOnly];
|
||||||
[[self entry:NSLocalizedString(@"Truncate article title", nil) c1:nil c2:nil c3:Pref_feedTruncateTitle]
|
[[self entry:NSLocalizedString(@"Truncate article title", nil) c1:nil c2:nil c3:Pref_feedTruncateTitle]
|
||||||
tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)];
|
tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)];
|
||||||
[[self entry:NSLocalizedString(@"Limit number of articles", nil) c1:nil c2:nil c3:Pref_feedLimitArticles]
|
[[self entry:NSLocalizedString(@"Limit number of articles", nil) c1:nil c2:nil c3:Pref_feedLimitArticles]
|
||||||
|
|||||||
@@ -132,12 +132,12 @@
|
|||||||
if (item) { // nil on last loop (aka main menu, see below)
|
if (item) { // nil on last loop (aka main menu, see below)
|
||||||
[item.submenu setHeaderHasUnread:uct];
|
[item.submenu setHeaderHasUnread:uct];
|
||||||
[item setTitleCount:uct.unread];
|
[item setTitleCount:uct.unread];
|
||||||
|
item.hidden = NO;
|
||||||
item = item.parentItem;
|
item = item.parentItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// call on main menu
|
// call on main menu
|
||||||
[self.statusItem.mainMenu setHeaderHasUnread:itms.firstObject];
|
[self.statusItem.mainMenu setHeaderHasUnread:itms.firstObject];
|
||||||
// TODO: need to re-create groups if user chose to hide already read articles
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
|||||||
NSUInteger unread = unreadMap[[t substringFromIndex:2]].unread;
|
NSUInteger unread = unreadMap[[t substringFromIndex:2]].unread;
|
||||||
|
|
||||||
// Check user preferences to show only unread entries
|
// Check user preferences to show only unread entries
|
||||||
if (unread == 0 &&
|
if (unread == 0
|
||||||
((fg.type == FEED && UserPrefsBool(Pref_groupUnreadOnly)) ||
|
&& (fg.type == FEED || fg.type == GROUP)
|
||||||
(fg.type == GROUP && UserPrefsBool(Pref_globalUnreadOnly)))) {
|
&& UserPrefsBool(Pref_groupUnreadOnly)) {
|
||||||
return nil;
|
item.hidden = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.submenu = [[NSMenu alloc] initWithTitle:t];
|
item.submenu = [[NSMenu alloc] initWithTitle:t];
|
||||||
|
|||||||
Reference in New Issue
Block a user