From 7f4eeaca0e35d158e8d519e565d3ffa534a12658 Mon Sep 17 00:00:00 2001 From: David Alarcon Date: Fri, 27 Nov 2020 23:41:23 +0100 Subject: [PATCH] MOS-10 Create DownloadNotificationManager for os version from 10.14. --- .../project.pbxproj | 4 + .../pEpNotifications/AppDelegate.swift | 67 ++++++-- .../DownloadNotificationManager.swift | 148 ++++++++++++++++++ .../DownloadStateNotifier.swift | 2 +- 4 files changed, 203 insertions(+), 18 deletions(-) create mode 100644 Submodules/pEpNotifications/pEpNotifications/DownloadNotificationManager.swift diff --git a/Submodules/pEpNotifications/pEpNotifications.xcodeproj/project.pbxproj b/Submodules/pEpNotifications/pEpNotifications.xcodeproj/project.pbxproj index b67eb6d..34357a2 100644 --- a/Submodules/pEpNotifications/pEpNotifications.xcodeproj/project.pbxproj +++ b/Submodules/pEpNotifications/pEpNotifications.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 3527B2B924802F87007A6276 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3527B2B824802F87007A6276 /* Preview Assets.xcassets */; }; 3594303F2483011000DCBD65 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3594303D2483011000DCBD65 /* Main.storyboard */; }; 359430482483264F00DCBD65 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3594304A2483264F00DCBD65 /* Localizable.strings */; }; + 4E0FD9EB25715E1500A9DD77 /* DownloadNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FD9EA25715E1500A9DD77 /* DownloadNotificationManager.swift */; }; 4E1107AF256FE89900EB1711 /* DownloadStateNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1107AD256FCF2900EB1711 /* DownloadStateNotifier.swift */; }; 4E1107B5256FFEC000EB1711 /* PEPNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1107B4256FFEC000EB1711 /* PEPNotification.swift */; }; /* End PBXBuildFile section */ @@ -29,6 +30,7 @@ 35943044248309BE00DCBD65 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 359430492483264F00DCBD65 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 3594304B2483266200DCBD65 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 4E0FD9EA25715E1500A9DD77 /* DownloadNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadNotificationManager.swift; sourceTree = ""; }; 4E1107AD256FCF2900EB1711 /* DownloadStateNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadStateNotifier.swift; sourceTree = ""; }; 4E1107B4256FFEC000EB1711 /* PEPNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PEPNotification.swift; sourceTree = ""; }; 4E4A391A2575452700BF0A15 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; @@ -70,6 +72,7 @@ 3527B2B124802F84007A6276 /* AppDelegate.swift */, 4E1107AD256FCF2900EB1711 /* DownloadStateNotifier.swift */, 4E1107B4256FFEC000EB1711 /* PEPNotification.swift */, + 4E0FD9EA25715E1500A9DD77 /* DownloadNotificationManager.swift */, 3527B2B524802F87007A6276 /* Assets.xcassets */, 3527B2BD24802F87007A6276 /* Info.plist */, 3527B2BE24802F87007A6276 /* pEpNotifications.entitlements */, @@ -192,6 +195,7 @@ files = ( 4E1107AF256FE89900EB1711 /* DownloadStateNotifier.swift in Sources */, 3527B2B224802F84007A6276 /* AppDelegate.swift in Sources */, + 4E0FD9EB25715E1500A9DD77 /* DownloadNotificationManager.swift in Sources */, 4E1107B5256FFEC000EB1711 /* PEPNotification.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Submodules/pEpNotifications/pEpNotifications/AppDelegate.swift b/Submodules/pEpNotifications/pEpNotifications/AppDelegate.swift index 207970c..1ee6b2b 100644 --- a/Submodules/pEpNotifications/pEpNotifications/AppDelegate.swift +++ b/Submodules/pEpNotifications/pEpNotifications/AppDelegate.swift @@ -9,6 +9,7 @@ import Cocoa import SwiftUI +import UserNotifications enum DNType : Int { case ready = 0, downloading, downloadArrived, noDownloadAvailable } @@ -40,6 +41,9 @@ class AppDelegate: NSObject { private lazy var clientListener: NSXPCListener = NSXPCListener.anonymous() private var receiver: PEPNotification? private lazy var downloadStateNotifier = DownloadStateNotifier(at: statusText) + @available(OSX 10.14, *) + //private lazy var notificationCenter = UNUserNotificationCenter.current() + private lazy var downloadNotificationManager = DownloadNotificationManager() // MARK: - Action methods @@ -124,8 +128,12 @@ extension AppDelegate { } private func initNotificationCenter() { - nc.removeAllDeliveredNotifications() - nc.delegate = self + if #available(OSX 10.14, *) { + downloadNotificationManager.delegate = self + } else { + nc.removeAllDeliveredNotifications() + nc.delegate = self + } } private func proxyErrorHandler(err: Error) -> Void { @@ -170,6 +178,11 @@ extension AppDelegate { NSStatusBar.system.removeStatusItem(statusBarItem!) statusBarItem = nil } +} + +// MARK: - UNUserNotificationCenterDelegate + +extension AppDelegate: UNUserNotificationCenterDelegate { } @@ -191,21 +204,24 @@ extension AppDelegate: PEPNotificationProtocol { } case .downloadArrived: - removeDeliveredNotifications(with: withName) - - let un = NSUserNotification() - un.title = NSLocalizedString("Update available", comment: "") - un.informativeText = String(format: NSLocalizedString("A new update for %@ is ready to be installed.", comment: ""), withName) - un.actionButtonTitle = NSLocalizedString("Install", comment: "") - un.hasActionButton = true - un.otherButtonTitle = NSLocalizedString("Not now", comment: "") - un.soundName = NSUserNotificationDefaultSoundName - let installTitle : String = NSLocalizedString("Install", comment: "") - un.additionalActions = [NSUserNotificationAction(identifier: "install", title: installTitle)] - un.setValue(true, forKey: "_showsButtons") - un.userInfo = ["name": withName, "filename": withFilename] - nc.deliver(un) - let product = ["name": withName, "filename": withFilename, "notification": un] + let product = ["name": withName, "filename": withFilename] + + if #available(OSX 10.14, *) { + downloadNotificationManager.scheduleDownloadNotification(with: product) + } else { + removeDeliveredNotifications(with: withName) + let un = NSUserNotification() + un.title = NSLocalizedString("Update available", comment: "") + un.informativeText = String(format: NSLocalizedString("A new update for %@ is ready to be installed.", comment: ""), withName) + un.actionButtonTitle = NSLocalizedString("Install", comment: "") + un.hasActionButton = true + un.otherButtonTitle = NSLocalizedString("Not now", comment: "") + un.soundName = NSUserNotificationDefaultSoundName + un.setValue(true, forKey: "_showsButtons") + un.userInfo = ["name": withName, "filename": withFilename] + nc.deliver(un) + } + //let product = ["name": withName, "filename": withFilename, "notification": un] downloadStateNotifier.notify(.NewVersionAvailable(String(withName)), with: product) case .noDownloadAvailable: @@ -282,3 +298,20 @@ extension AppDelegate: NSXPCListenerDelegate { return true } } + +// MARK: - DownloadNotificationManagerDelegate + +extension AppDelegate: DownloadNotificationManagerDelegate { + func installSelected(with product: Product) { + defer { + uninstallMenuExtra() + } + guard let filename = product["filename"] as? String else { + return + } + + NSLog("pEpNotifications: actionButtonClicked for %@", filename) + NSWorkspace.shared.openFile(filename) + downloadStateNotifier.notify(.Connected) + } +} diff --git a/Submodules/pEpNotifications/pEpNotifications/DownloadNotificationManager.swift b/Submodules/pEpNotifications/pEpNotifications/DownloadNotificationManager.swift new file mode 100644 index 0000000..52b598e --- /dev/null +++ b/Submodules/pEpNotifications/pEpNotifications/DownloadNotificationManager.swift @@ -0,0 +1,148 @@ +// +// DownloadNotificationManager.swift +// p≡p updates +// +// Created by David Alarcon on 27/11/20. +// Copyright © 2020 p≡p foundation. All rights reserved. +// + +import UserNotifications + +fileprivate struct DownloadNotification { + var id: String + var product: Product +} + +public protocol DownloadNotificationManagerDelegate: class { + func installSelected(with product: Product) + func notNowSelected() +} + +extension DownloadNotificationManagerDelegate { + func notNowSelected() { } +} + +@available(OSX 10.14, *) +public class DownloadNotificationManager: NSObject { + + public weak var delegate: DownloadNotificationManagerDelegate? + + private var notifications = [DownloadNotification]() + private lazy var notificationCenter = UNUserNotificationCenter.current() + + override init() { + super.init() + notificationCenter.removeDeliveredNotifications(withIdentifiers: ["p≡p updates"]) + notificationCenter.delegate = self + setupNotificationCategories() + } + + public func addDownloadNotification(with product: Product) -> Void { + notifications.append(DownloadNotification(id: UUID().uuidString, product: product)) + } + + public func scheduleDownloadNotifications() { + notificationCenter.getNotificationSettings { settings in + switch settings.authorizationStatus { + case .notDetermined: + self.requestNotificationAuthorization() + case .authorized, .provisional: + self.scheduleNotifications() + default: + break + } + } + } + + public func scheduleDownloadNotification(with product: Product) { + addDownloadNotification(with: product) + scheduleDownloadNotifications() + } +} + +// MARK: - Private + +@available(OSX 10.14, *) +extension DownloadNotificationManager { + + private func requestNotificationAuthorization() -> Void { + notificationCenter + .requestAuthorization(options: [.alert, .badge, .alert]) { [weak self] granted, error in + guard let me = self else { + return + } + guard granted else { + if let error = error { + print("pEpNotifications[Error]: ", error) + } + + return + } + + me.scheduleNotifications() + } + } + + private func setupNotificationCategories() { + let installAction = UNNotificationAction(identifier: "installAction", title: NSLocalizedString("Install", comment: ""), options: .init(rawValue: 0)) + let notNowAction = UNNotificationAction(identifier: "notNowAction", title: NSLocalizedString("Not now", comment: ""), options: .init(rawValue: 0)) + let category = UNNotificationCategory(identifier: "p≡p", + actions: [installAction, notNowAction], + intentIdentifiers: [], + hiddenPreviewsBodyPlaceholder: "", + options: .customDismissAction) + self.notificationCenter.setNotificationCategories([category]) + } + + private func scheduleNotifications() -> Void { + notifications.forEach { notification in + sendNotification(with: notification) + } + } + + private func sendNotification(with notification: DownloadNotification) { + guard let name = notification.product["name"] as? String else { + return + } + + let notificationContent = UNMutableNotificationContent() + notificationContent.title = NSLocalizedString("Update available", comment: "") + notificationContent.body = String(format: NSLocalizedString("A new update for %@ is ready to be installed.", comment: ""), name) + notificationContent.sound = UNNotificationSound.default + notificationContent.categoryIdentifier = "p≡p" + notificationContent.userInfo = notification.product + + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: Double.leastNonzeroMagnitude, + repeats: false) + let request = UNNotificationRequest(identifier: "p≡p updates", + content: notificationContent, + trigger: trigger) + + notificationCenter.add(request) { error in + if let error = error { + print("pEpNotifications[Error]: ", error) + } + } + } +} + +// MARK: - UNUserNotificationCenterDelegate + +@available(OSX 10.14, *) +extension DownloadNotificationManager: UNUserNotificationCenterDelegate { + public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + + switch response.actionIdentifier { + case "installAction": + if let product = response.notification.request.content.userInfo as? Product { + delegate?.installSelected(with: product) + } + case "notNowAction": + delegate?.notNowSelected() + default: + break + } + + } +} diff --git a/Submodules/pEpNotifications/pEpNotifications/DownloadStateNotifier.swift b/Submodules/pEpNotifications/pEpNotifications/DownloadStateNotifier.swift index e0ed198..c91d34a 100644 --- a/Submodules/pEpNotifications/pEpNotifications/DownloadStateNotifier.swift +++ b/Submodules/pEpNotifications/pEpNotifications/DownloadStateNotifier.swift @@ -8,7 +8,7 @@ import SwiftUI -typealias Product = Dictionary +public typealias Product = Dictionary struct DownloadStateNotifier { var menuItem: NSMenuItem