Skip to content

Commit c516ee1

Browse files
authored
Merge pull request #1902 from Eitot/feature/notifications-framework
Implement UNUserNotifications framework for macOS 10.14+ This mostly replicates the previous functionality, with the exception of grouping file download notifications in Notification Centre (if not disabled by the user in System Settings).
2 parents 674b4f0 + a2fa027 commit c516ee1

File tree

10 files changed

+612
-53
lines changed

10 files changed

+612
-53
lines changed

Vienna.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@
192192
F69140E41E71B94200D51BFF /* AppController+Notifications.m in Sources */ = {isa = PBXBuildFile; fileRef = F69140E31E71B94200D51BFF /* AppController+Notifications.m */; };
193193
F692559A2572A32900D387D8 /* MMTabBarView in Frameworks */ = {isa = PBXBuildFile; productRef = F69255992572A32900D387D8 /* MMTabBarView */; };
194194
F696D23E2561F9FC003DF0C0 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = F696D23D2561F9FC003DF0C0 /* Sparkle */; };
195+
F69743E92ADAC497006C5BBC /* UserNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69743E82ADAC497006C5BBC /* UserNotificationCenter.swift */; };
196+
F69743EB2ADAC49D006C5BBC /* UserNotificationCenterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69743EA2ADAC49D006C5BBC /* UserNotificationCenterDelegate.swift */; };
197+
F69743ED2ADAC4A2006C5BBC /* UserNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69743EC2ADAC4A2006C5BBC /* UserNotificationRequest.swift */; };
198+
F69743EF2ADAC4A7006C5BBC /* UserNotificationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69743EE2ADAC4A7006C5BBC /* UserNotificationResponse.swift */; };
195199
F69964F71F13029600FC8493 /* OverlayStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69964F61F13029500FC8493 /* OverlayStatusBar.swift */; };
196200
F69EA3962DAC3B7800A97F71 /* DownloadListCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = F69EA3952DAC3B7800A97F71 /* DownloadListCellView.m */; };
197201
F6A179D326B82BE3008DDA42 /* NSFileManagerExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A179D226B82BE3008DDA42 /* NSFileManagerExtensionTests.swift */; };
@@ -538,6 +542,10 @@
538542
F69140E31E71B94200D51BFF /* AppController+Notifications.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AppController+Notifications.m"; sourceTree = "<group>"; };
539543
F695E9E1283CF02D00320578 /* FeedListConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedListConstants.h; sourceTree = "<group>"; };
540544
F695E9E2283D0C8100320578 /* FolderViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FolderViewDelegate.h; sourceTree = "<group>"; };
545+
F69743E82ADAC497006C5BBC /* UserNotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationCenter.swift; sourceTree = "<group>"; };
546+
F69743EA2ADAC49D006C5BBC /* UserNotificationCenterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterDelegate.swift; sourceTree = "<group>"; };
547+
F69743EC2ADAC4A2006C5BBC /* UserNotificationRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationRequest.swift; sourceTree = "<group>"; };
548+
F69743EE2ADAC4A7006C5BBC /* UserNotificationResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationResponse.swift; sourceTree = "<group>"; };
541549
F698E6FF29E9D1CA00D49475 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Predicates.strings; sourceTree = "<group>"; };
542550
F69964F61F13029500FC8493 /* OverlayStatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayStatusBar.swift; sourceTree = "<group>"; };
543551
F69EA3942DAC3B7800A97F71 /* DownloadListCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DownloadListCellView.h; sourceTree = "<group>"; };
@@ -973,6 +981,17 @@
973981
name = "Smart folder";
974982
sourceTree = "<group>";
975983
};
984+
F69743D72ADAC473006C5BBC /* UserNotifications */ = {
985+
isa = PBXGroup;
986+
children = (
987+
F69743E82ADAC497006C5BBC /* UserNotificationCenter.swift */,
988+
F69743EA2ADAC49D006C5BBC /* UserNotificationCenterDelegate.swift */,
989+
F69743EC2ADAC4A2006C5BBC /* UserNotificationRequest.swift */,
990+
F69743EE2ADAC4A7006C5BBC /* UserNotificationResponse.swift */,
991+
);
992+
path = UserNotifications;
993+
sourceTree = "<group>";
994+
};
976995
F6A7DDCB1E470E980017BE5E /* Vienna Help */ = {
977996
isa = PBXGroup;
978997
children = (
@@ -1138,6 +1157,7 @@
11381157
F6D7FEDB1F1CDF41004F095A /* Info panel */,
11391158
F6D7FEDC1F1CDF49004F095A /* Activity panel */,
11401159
F6D7FEDD1F1CDFCD004F095A /* Download window */,
1160+
F69743D72ADAC473006C5BBC /* UserNotifications */,
11411161
F6D7FEDE1F1CE135004F095A /* Alerts */,
11421162
F6D7FEDF1F1CE13F004F095A /* Fetching */,
11431163
F6D7FEE01F1CE147004F095A /* Parsing */,
@@ -1821,6 +1841,7 @@
18211841
2F88FF412969B2A40076B99E /* DatePredicateWithUnit.swift in Sources */,
18221842
43502896165DE9E00018EDB7 /* ActivityPanelController.m in Sources */,
18231843
0378753125E34743009AB1C9 /* FeedListCellView.m in Sources */,
1844+
F69743EF2ADAC4A7006C5BBC /* UserNotificationResponse.swift in Sources */,
18241845
2FE44CAD25B7995400554E82 /* NSApplication+AppController.swift in Sources */,
18251846
F6CE47131E7E3DCB0045EAD7 /* ActivityItem.m in Sources */,
18261847
43502897165DE9E00018EDB7 /* AppController.m in Sources */,
@@ -1831,9 +1852,11 @@
18311852
F67E7828285DDAB200D1CECE /* Toolbar.swift in Sources */,
18321853
2FE44CB525B79EDE00554E82 /* WebKitContextMenuCustomizer.swift in Sources */,
18331854
F61CEA661F039E57009C878E /* ButtonToolbarItem.swift in Sources */,
1855+
F69743EB2ADAC49D006C5BBC /* UserNotificationCenterDelegate.swift in Sources */,
18341856
F6B059E42961D0DD00F6E31B /* JSONFeedParser.swift in Sources */,
18351857
F68792872700A678009B7BB5 /* SecurityScopedBookmark.swift in Sources */,
18361858
F6094E331F10107800677D3B /* ExportAccessoryViewController.swift in Sources */,
1859+
F69743ED2ADAC4A2006C5BBC /* UserNotificationRequest.swift in Sources */,
18371860
2FCD69302260B3E7005BAA75 /* CustomWKWebView.swift in Sources */,
18381861
F6A464BC272F47BE0071E3F6 /* NSKeyedUnarchiver+Compatibility.m in Sources */,
18391862
2FDF6FC0218A266A002F77E9 /* BrowserTab.swift in Sources */,
@@ -1902,6 +1925,7 @@
19021925
F69140E41E71B94200D51BFF /* AppController+Notifications.m in Sources */,
19031926
3A4DBEC71733C207006DD2AB /* ArticleCellView.m in Sources */,
19041927
F6A464BD272F47BE0071E3F6 /* NSKeyedArchiver+Compatibility.m in Sources */,
1928+
F69743E92ADAC497006C5BBC /* UserNotificationCenter.swift in Sources */,
19051929
37C650801816EBAD3C0D9DBF /* NSURL+CaminoExtensions.m in Sources */,
19061930
);
19071931
runOnlyForDeploymentPostprocessing = 0;

Vienna/Sources/Application/AppController+Notifications.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
#import "AppController.h"
2121

22-
@interface AppController (Notifications) <NSUserNotificationCenterDelegate>
22+
#import "Vienna-Swift.h"
23+
24+
@interface AppController (Notifications) <VNAUserNotificationCenterDelegate>
2325

2426
// Notification keys
2527
extern NSString *const UserNotificationContextKey;

Vienna/Sources/Application/AppController+Notifications.m

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
#import "Database.h"
2323
#import "Folder.h"
24-
#import "Vienna-Swift.h"
2524

2625
@implementation AppController (Notifications)
2726

@@ -36,9 +35,10 @@ @implementation AppController (Notifications)
3635

3736
// MARK: Delegate methods
3837

39-
- (void)userNotificationCenter:(NSUserNotificationCenter *)center
40-
didActivateNotification:(NSUserNotification *)notification {
41-
NSDictionary<NSString *, NSString *> *userInfo = notification.userInfo;
38+
- (void)userNotificationCenter:(VNAUserNotificationCenter *)center
39+
didReceiveResponse:(VNAUserNotificationResponse *)response
40+
{
41+
NSDictionary<NSString *, NSString *> *userInfo = response.userInfo;
4242
if ([userInfo[UserNotificationContextKey] isEqual:UserNotificationContextFetchCompleted]) {
4343
[self openWindowAndShowUnreadArticles];
4444
} else if ([userInfo[UserNotificationContextKey] isEqual:UserNotificationContextFileDownloadCompleted]) {
@@ -76,7 +76,7 @@ - (void)openWindowAndShowUnreadArticles {
7676
*/
7777
- (void)showFileInFinderAtPath:(NSString *)filePath {
7878
[[NSWorkspace sharedWorkspace] selectFile:filePath
79-
inFileViewerRootedAtPath:@""];
79+
inFileViewerRootedAtPath:[filePath stringByDeletingLastPathComponent]];
8080
}
8181

8282
@end

Vienna/Sources/Application/AppController.m

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,8 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNot
351351
// Add the app to the status bar if needed.
352352
[self showAppInStatusBar];
353353

354-
// Notification Center delegate
355-
NSUserNotificationCenter.defaultUserNotificationCenter.delegate = self;
354+
// User Notification Center delegate
355+
VNAUserNotificationCenter.current.delegate = self;
356356

357357
// Register to be notified when the scripts folder changes.
358358
if (!hasOSScriptsMenu()) {
@@ -1634,11 +1634,15 @@ -(void)handleFolderSelection:(NSNotification *)nc
16341634
NSString *predicateFormat = unreadCriteria.predicate.predicateFormat;
16351635
Folder *smartFolder = [db folderForPredicateFormat:predicateFormat];
16361636
if ([smartFolder isEqual:treeNode.folder]) {
1637-
NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
1638-
[center.deliveredNotifications enumerateObjectsUsingBlock:^(NSUserNotification *notification, NSUInteger idx, BOOL *stop) {
1639-
if ([notification.userInfo[UserNotificationContextKey] isEqualToString:UserNotificationContextFetchCompleted]) {
1640-
[center removeDeliveredNotification:notification];
1637+
VNAUserNotificationCenter *center = VNAUserNotificationCenter.current;
1638+
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<VNAUserNotificationResponse *> *responses) {
1639+
NSMutableArray<NSString *> *identifiers = [NSMutableArray array];
1640+
for (VNAUserNotificationResponse *response in responses) {
1641+
if ([response.userInfo[UserNotificationContextKey] isEqualToString:UserNotificationContextFetchCompleted]) {
1642+
[identifiers addObject:response.identifier];
1643+
}
16411644
}
1645+
[center removeDeliveredNotificationsWithIdentifiers:identifiers];
16421646
}];
16431647
}
16441648
}
@@ -1822,22 +1826,43 @@ -(void)handleRefreshStatusChange:(NSNotification *)nc
18221826

18231827
// User notification
18241828
if (newUnread > 0) {
1825-
NSUserNotification *notification = [NSUserNotification new];
1826-
notification.title = NSLocalizedString(@"New articles retrieved", @"Notification title");
1827-
notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"%d new unread articles retrieved", @"Notification body"), (int)newUnread];
1828-
notification.userInfo = @{UserNotificationContextKey: UserNotificationContextFetchCompleted};
1829-
notification.soundName = NSUserNotificationDefaultSoundName;
1830-
1831-
// Set a unique identifier to assure that this notifications cannot
1832-
// appear more than once.
1833-
notification.identifier = UserNotificationContextFetchCompleted;
1834-
1835-
// Remove the previous notification, if present, before sending a
1836-
// new one. This will assure that the user can receive an alert and
1837-
// and can see the updated notification in Notification Center.
1838-
NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
1839-
[center removeDeliveredNotification:notification];
1840-
[center deliverNotification:notification];
1829+
VNAUserNotificationCenter *center = VNAUserNotificationCenter.current;
1830+
[center getNotificationSettingsWithCompletionHandler:^(VNAUserNotificationSettings *settings) {
1831+
VNAUserNotificationAuthorizationStatus status = settings.authorizationStatus;
1832+
if (status == VNAUserNotificationAuthorizationStatusDenied) {
1833+
return;
1834+
}
1835+
1836+
void (^deliverNotification)(void) = ^{
1837+
NSString *identifier = UserNotificationContextFetchCompleted;
1838+
NSString *title = NSLocalizedString(@"New articles retrieved",
1839+
@"Notification title");
1840+
NSString *body = [NSString stringWithFormat:NSLocalizedString(@"%d new unread articles retrieved",
1841+
@"Notification body"),
1842+
(int)newUnread];
1843+
VNAUserNotificationRequest *request =
1844+
[[VNAUserNotificationRequest alloc] initWithIdentifier:identifier
1845+
title:title];
1846+
request.body = body;
1847+
request.playSound = settings.isSoundEnabled;
1848+
request.userInfo = @{
1849+
UserNotificationContextKey: UserNotificationContextFetchCompleted
1850+
};
1851+
[center addNotificationRequest:request
1852+
withCompletionHandler:nil];
1853+
};
1854+
1855+
if (status == VNAUserNotificationAuthorizationStatusProvisional ||
1856+
status == VNAUserNotificationAuthorizationStatusAuthorized) {
1857+
deliverNotification();
1858+
} else if (status == VNAUserNotificationAuthorizationStatusNotDetermined) {
1859+
[center requestAuthorizationWithCompletionHandler:^(BOOL granted) {
1860+
if (granted) {
1861+
deliverNotification();
1862+
}
1863+
}];
1864+
}
1865+
}];
18411866
}
18421867
}
18431868
}

Vienna/Sources/Download window/DownloadManager.m

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#import "Preferences.h"
3030
#import "Vienna-Swift.h"
3131

32+
static NSString * const VNAUserNotificationFileDownloadThreadIdentifier = @"FileDownloadThreadIdentifier";
33+
3234
@interface DownloadManager ()
3335

3436
// Private properties
@@ -275,26 +277,63 @@ - (void)deliverNotificationForDownloadItem:(DownloadItem *)item {
275277
[self notifyDownloadItemChange:item];
276278
[self archiveDownloadsList];
277279

278-
NSString *fileName = item.filename.lastPathComponent;
279-
280-
NSUserNotification *notification = [NSUserNotification new];
281-
notification.soundName = NSUserNotificationDefaultSoundName;
280+
NSString *filename = item.filename.lastPathComponent;
282281

283282
if (item.state == DownloadStateCompleted) {
284-
notification.title = NSLocalizedString(@"Download completed", @"Notification title");
285-
notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"File %@ downloaded", @"Notification body"), fileName];
286-
notification.userInfo = @{UserNotificationContextKey: UserNotificationContextFileDownloadCompleted,
287-
UserNotificationFilePathKey: fileName};
288-
289-
[NSNotificationCenter.defaultCenter postNotificationName:MA_Notify_DownloadCompleted object:fileName];
290-
} else if (item.state == DownloadStateFailed) {
291-
notification.title = NSLocalizedString(@"Download failed", @"Notification title");
292-
notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"File %@ failed to download", @"Notification body"), fileName];
293-
notification.userInfo = @{UserNotificationContextKey: UserNotificationContextFileDownloadFailed,
294-
UserNotificationFilePathKey: fileName};
283+
[NSNotificationCenter.defaultCenter postNotificationName:MA_Notify_DownloadCompleted
284+
object:filename];
295285
}
296286

297-
[NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification];
287+
VNAUserNotificationCenter *center = VNAUserNotificationCenter.current;
288+
[center getNotificationSettingsWithCompletionHandler:^(VNAUserNotificationSettings *settings) {
289+
VNAUserNotificationAuthorizationStatus status = settings.authorizationStatus;
290+
if (status == VNAUserNotificationAuthorizationStatusDenied) {
291+
return;
292+
}
293+
294+
void (^deliverNotification)(void) = ^{
295+
NSString *title;
296+
NSString *body;
297+
NSDictionary<NSString *, id> *userInfo;
298+
if (item.state == DownloadStateCompleted) {
299+
title = NSLocalizedString(@"Download completed", @"Notification title");
300+
body = [NSString stringWithFormat:NSLocalizedString(@"File %@ downloaded", @"Notification body"), filename];
301+
userInfo = @{
302+
UserNotificationContextKey: UserNotificationContextFileDownloadCompleted,
303+
UserNotificationFilePathKey: item.fileURL.path
304+
};
305+
} else if (item.state == DownloadStateFailed) {
306+
title = NSLocalizedString(@"Download failed", @"Notification title");
307+
body = [NSString stringWithFormat:NSLocalizedString(@"File %@ failed to download", @"Notification body"), filename];
308+
userInfo = @{
309+
UserNotificationContextKey: UserNotificationContextFileDownloadFailed,
310+
UserNotificationFilePathKey: item.fileURL.path
311+
};
312+
}
313+
VNAUserNotificationRequest *request =
314+
[[VNAUserNotificationRequest alloc] initWithIdentifier:item.fileURL.absoluteString
315+
title:title];
316+
// Use a thread identifier to group all file download notifications
317+
// (this can be disabled by the user in System Settings).
318+
request.threadIdentifier = VNAUserNotificationFileDownloadThreadIdentifier;
319+
request.body = body;
320+
request.playSound = settings.isSoundEnabled;
321+
request.userInfo = userInfo;
322+
[center addNotificationRequest:request
323+
withCompletionHandler:nil];
324+
};
325+
326+
if (status == VNAUserNotificationAuthorizationStatusProvisional ||
327+
status == VNAUserNotificationAuthorizationStatusAuthorized) {
328+
deliverNotification();
329+
} else if (status == VNAUserNotificationAuthorizationStatusNotDetermined) {
330+
[center requestAuthorizationWithCompletionHandler:^(BOOL granted) {
331+
if (granted) {
332+
deliverNotification();
333+
}
334+
}];
335+
}
336+
}];
298337
}
299338

300339
// MARK: - NSURLSessionDownloadDelegate

Vienna/Sources/Download window/DownloadWindow.m

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#import "DownloadManager.h"
2828
#import "HelperFunctions.h"
2929
#import "NSWorkspace+OpenWithMenu.h"
30+
#import "Vienna-Swift.h"
3031

3132
@implementation DownloadWindow {
3233
IBOutlet NSWindow *downloadWindow;
@@ -116,15 +117,18 @@ - (void)menuWillOpen:(NSMenu *)menu
116117
- (void)windowDidBecomeKey:(NSNotification *)notification
117118
{
118119
// Clear relevant notifications when the user views this window.
119-
NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
120-
[center.deliveredNotifications enumerateObjectsUsingBlock:^(NSUserNotification *notification, NSUInteger idx, BOOL *stop) {
121-
BOOL completed = [notification.userInfo[UserNotificationContextKey] isEqualToString:UserNotificationContextFileDownloadCompleted];
122-
BOOL failed = [notification.userInfo[UserNotificationContextKey] isEqualToString:UserNotificationContextFileDownloadFailed];
123-
124-
if (completed || failed) {
125-
[center removeDeliveredNotification: notification];
126-
}
127-
}];
120+
VNAUserNotificationCenter *center = VNAUserNotificationCenter.current;
121+
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<VNAUserNotificationResponse *> *responses) {
122+
NSMutableArray *identifiers = [NSMutableArray array];
123+
for (VNAUserNotificationResponse *response in responses) {
124+
NSString *context = response.userInfo[UserNotificationContextKey];
125+
if ([context isEqualToString:UserNotificationContextFileDownloadCompleted] ||
126+
[context isEqualToString:UserNotificationContextFileDownloadFailed]) {
127+
[identifiers addObject:response.identifier];
128+
}
129+
}
130+
[center removeDeliveredNotificationsWithIdentifiers:identifiers];
131+
}];
128132
}
129133

130134
/* clearList

0 commit comments

Comments
 (0)