2018-09-04 07:33:00 +02:00
//
// G e n e r a l P r e f e n c e s V i e w C o n t r o l l e r . s w i f t
// N e t N e w s W i r e
//
// C r e a t e d b y B r e n t S i m m o n s o n 9 / 3 / 1 8 .
// C o p y r i g h t © 2 0 1 8 R a n c h e r o S o f t w a r e . A l l r i g h t s r e s e r v e d .
//
import AppKit
import RSCore
2020-05-19 04:29:53 +02:00
import RSWeb
2020-09-06 23:15:46 +02:00
import UserNotifications
2018-09-04 07:33:00 +02:00
final class GeneralPreferencesViewController : NSViewController {
2020-09-06 23:15:46 +02:00
private var userNotificationSettings : UNNotificationSettings ?
2020-09-23 02:27:36 +02:00
@IBOutlet var defaultRSSReaderPopup : NSPopUpButton !
2020-05-19 04:29:53 +02:00
@IBOutlet var defaultBrowserPopup : NSPopUpButton !
2020-09-23 02:27:36 +02:00
@IBOutlet weak var showUnreadCountCheckbox : NSButton !
private var rssReaderInfo = RSSReaderInfo ( )
2018-11-26 22:17:16 +01:00
public override init ( nibName nibNameOrNil : NSNib . Name ? , bundle nibBundleOrNil : Bundle ? ) {
super . init ( nibName : nibNameOrNil , bundle : nibBundleOrNil )
commonInit ( )
}
public required init ? ( coder : NSCoder ) {
super . init ( coder : coder )
commonInit ( )
}
2018-09-04 07:33:00 +02:00
override func viewWillAppear ( ) {
2018-11-26 22:17:16 +01:00
super . viewWillAppear ( )
updateUI ( )
2020-09-06 23:15:46 +02:00
updateNotificationSettings ( )
2018-11-26 22:17:16 +01:00
}
// MARK: - N o t i f i c a t i o n s
@objc func applicationWillBecomeActive ( _ note : Notification ) {
updateUI ( )
}
// MARK: - A c t i o n s
2020-09-23 02:27:36 +02:00
@IBAction func rssReaderPopupDidChangeValue ( _ sender : Any ? ) {
guard let menuItem = defaultRSSReaderPopup . selectedItem else {
return
}
guard let bundleID = menuItem . representedObject as ? String else {
return
}
registerAppWithBundleID ( bundleID )
updateUI ( )
}
2020-05-19 04:29:53 +02:00
@IBAction func browserPopUpDidChangeValue ( _ sender : Any ? ) {
guard let menuItem = defaultBrowserPopup . selectedItem else {
return
}
let bundleID = menuItem . representedObject as ? String
2020-07-02 05:17:38 +02:00
AppDefaults . shared . defaultBrowserID = bundleID
2020-05-19 04:29:53 +02:00
updateUI ( )
}
2020-09-06 19:22:35 +02:00
@IBAction func toggleShowingUnreadCount ( _ sender : Any ) {
guard let checkbox = sender as ? NSButton else { return }
2020-09-06 23:15:46 +02:00
guard userNotificationSettings != nil else {
DispatchQueue . main . async {
self . showUnreadCountCheckbox . setNextState ( )
}
return
}
UNUserNotificationCenter . current ( ) . getNotificationSettings { ( settings ) in
self . updateNotificationSettings ( )
if settings . authorizationStatus = = . denied {
DispatchQueue . main . async {
self . showUnreadCountCheckbox . setNextState ( )
self . showNotificationsDeniedError ( )
}
} else if settings . authorizationStatus = = . authorized {
DispatchQueue . main . async {
AppDefaults . shared . hideDockUnreadCount = ( checkbox . state . rawValue = = 0 )
}
} else {
UNUserNotificationCenter . current ( ) . requestAuthorization ( options : [ . badge ] ) { ( granted , error ) in
self . updateNotificationSettings ( )
if granted {
DispatchQueue . main . async {
AppDefaults . shared . hideDockUnreadCount = checkbox . state . rawValue = = 0
NSApplication . shared . registerForRemoteNotifications ( )
}
} else {
DispatchQueue . main . async {
self . showUnreadCountCheckbox . setNextState ( )
}
}
}
}
}
2020-09-06 19:22:35 +02:00
}
2018-09-04 07:33:00 +02:00
}
2018-11-26 22:17:16 +01:00
// MARK: - P r i v a t e
2018-09-04 07:33:00 +02:00
private extension GeneralPreferencesViewController {
2018-11-26 22:17:16 +01:00
func commonInit ( ) {
NotificationCenter . default . addObserver ( self , selector : #selector ( applicationWillBecomeActive ( _ : ) ) , name : NSApplication . willBecomeActiveNotification , object : nil )
}
func updateUI ( ) {
2020-09-23 02:27:36 +02:00
rssReaderInfo = RSSReaderInfo ( )
2020-05-19 04:29:53 +02:00
updateBrowserPopup ( )
2020-09-23 02:27:36 +02:00
updateRSSReaderPopup ( )
2020-09-06 19:22:35 +02:00
updateHideUnreadCountCheckbox ( )
2018-11-26 22:17:16 +01:00
}
2020-09-23 02:27:36 +02:00
func updateRSSReaderPopup ( ) {
// T o p i t e m s h o u l d a l w a y s b e : N e t N e w s W i r e ( t h i s a p p )
// A d d i t i o n a l i t e m s s h o u l d b e s o r t e d a l p h a b e t i c a l l y .
// A n y o l d e r v e r s i o n s o f N e t N e w s W i r e s h o u l d b e l i s t e d a s : N e t N e w s W i r e ( o l d v e r s i o n )
let menu = NSMenu ( title : " RSS Readers " )
let netNewsWireBundleID = Bundle . main . bundleIdentifier !
let thisAppParentheticalComment = NSLocalizedString ( " (this app) " , comment : " Preferences default RSS Reader popup " )
let thisAppName = " NetNewsWire \( thisAppParentheticalComment ) "
let netNewsWireMenuItem = NSMenuItem ( title : thisAppName , action : nil , keyEquivalent : " " )
netNewsWireMenuItem . representedObject = netNewsWireBundleID
menu . addItem ( netNewsWireMenuItem )
let readersToList = rssReaderInfo . rssReaders . filter { $0 . bundleID != netNewsWireBundleID }
let sortedReaders = readersToList . sorted { ( reader1 , reader2 ) -> Bool in
return reader1 . nameMinusAppSuffix . localizedStandardCompare ( reader2 . nameMinusAppSuffix ) = = . orderedAscending
}
let oldVersionParentheticalComment = NSLocalizedString ( " (old version) " , comment : " Preferences default RSS Reader popup " )
for rssReader in sortedReaders {
var appName = rssReader . nameMinusAppSuffix
if appName . contains ( " NetNewsWire " ) {
appName = " \( appName ) \( oldVersionParentheticalComment ) "
}
let menuItem = NSMenuItem ( title : appName , action : nil , keyEquivalent : " " )
menuItem . representedObject = rssReader . bundleID
menu . addItem ( menuItem )
}
defaultRSSReaderPopup . menu = menu
func insertAndSelectNoneMenuItem ( ) {
let noneTitle = NSLocalizedString ( " None " , comment : " Preferences default RSS Reader popup " )
let menuItem = NSMenuItem ( title : noneTitle , action : nil , keyEquivalent : " " )
defaultRSSReaderPopup . menu ! . insertItem ( menuItem , at : 0 )
defaultRSSReaderPopup . selectItem ( at : 0 )
}
guard let defaultRSSReaderBundleID = rssReaderInfo . defaultRSSReaderBundleID else {
insertAndSelectNoneMenuItem ( )
return
}
for menuItem in defaultRSSReaderPopup . menu ! . items {
guard let bundleID = menuItem . representedObject as ? String else {
continue
}
if bundleID = = defaultRSSReaderBundleID {
defaultRSSReaderPopup . select ( menuItem )
return
}
}
insertAndSelectNoneMenuItem ( )
}
func registerAppWithBundleID ( _ bundleID : String ) {
NSWorkspace . shared . setDefaultAppBundleID ( forURLScheme : " feed " , to : bundleID )
NSWorkspace . shared . setDefaultAppBundleID ( forURLScheme : " feeds " , to : bundleID )
}
2020-05-19 04:29:53 +02:00
func updateBrowserPopup ( ) {
let menu = defaultBrowserPopup . menu !
let allBrowsers = MacWebBrowser . sortedBrowsers ( )
menu . removeAllItems ( )
let defaultBrowser = MacWebBrowser . default
let defaultBrowserFormat = NSLocalizedString ( " System Default (%@) " , comment : " Default browser item title format " )
let defaultBrowserTitle = String ( format : defaultBrowserFormat , defaultBrowser . name ! )
let item = NSMenuItem ( title : defaultBrowserTitle , action : nil , keyEquivalent : " " )
let icon = defaultBrowser . icon !
icon . size = NSSize ( width : 16.0 , height : 16.0 )
item . image = icon
menu . addItem ( item )
menu . addItem ( NSMenuItem . separator ( ) )
for browser in allBrowsers {
2020-10-20 19:02:27 +02:00
guard let name = browser . name else { continue }
let item = NSMenuItem ( title : name , action : nil , keyEquivalent : " " )
2020-05-19 04:29:53 +02:00
item . representedObject = browser . bundleIdentifier
2020-10-20 19:02:27 +02:00
let icon = browser . icon ? ? NSWorkspace . shared . icon ( forFileType : kUTTypeApplicationBundle as String )
2020-05-19 04:29:53 +02:00
icon . size = NSSize ( width : 16.0 , height : 16.0 )
item . image = browser . icon
menu . addItem ( item )
}
2020-07-02 05:17:38 +02:00
defaultBrowserPopup . selectItem ( at : defaultBrowserPopup . indexOfItem ( withRepresentedObject : AppDefaults . shared . defaultBrowserID ) )
2020-05-19 04:29:53 +02:00
}
2020-09-06 19:22:35 +02:00
func updateHideUnreadCountCheckbox ( ) {
showUnreadCountCheckbox . state = AppDefaults . shared . hideDockUnreadCount ? . off : . on
}
2020-09-06 23:15:46 +02:00
func updateNotificationSettings ( ) {
UNUserNotificationCenter . current ( ) . getNotificationSettings { ( settings ) in
self . userNotificationSettings = settings
if settings . authorizationStatus = = . authorized {
DispatchQueue . main . async {
NSApplication . shared . registerForRemoteNotifications ( )
}
}
}
}
func showNotificationsDeniedError ( ) {
let updateAlert = NSAlert ( )
updateAlert . alertStyle = . informational
updateAlert . messageText = NSLocalizedString ( " Enable Notifications " , comment : " Notifications " )
updateAlert . informativeText = NSLocalizedString ( " To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list. " , comment : " To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list. " )
updateAlert . addButton ( withTitle : NSLocalizedString ( " Open System Preferences " , comment : " Open System Preferences " ) )
updateAlert . addButton ( withTitle : NSLocalizedString ( " Close " , comment : " Close " ) )
let modalResponse = updateAlert . runModal ( )
if modalResponse = = . alertFirstButtonReturn {
2020-09-23 02:27:36 +02:00
NSWorkspace . shared . open ( URL ( string : " x-apple.systempreferences:com.apple.preference.notifications " ) ! )
2020-09-06 23:15:46 +02:00
}
}
2018-11-26 22:17:16 +01:00
}
2020-09-23 02:27:36 +02:00
// MARK: - R S S R e a d e r I n f o
private struct RSSReaderInfo {
let defaultRSSReaderBundleID : String ?
let rssReaders : Set < RSSReader >
static let feedURLScheme = " feed: "
init ( ) {
let defaultRSSReaderBundleID = NSWorkspace . shared . defaultAppBundleID ( forURLScheme : RSSReaderInfo . feedURLScheme )
self . defaultRSSReaderBundleID = defaultRSSReaderBundleID
self . rssReaders = RSSReaderInfo . fetchRSSReaders ( defaultRSSReaderBundleID )
}
static func fetchRSSReaders ( _ defaultRSSReaderBundleID : String ? ) -> Set < RSSReader > {
let rssReaderBundleIDs = NSWorkspace . shared . bundleIDsForApps ( forURLScheme : feedURLScheme )
var rssReaders = Set < RSSReader > ( )
if let defaultRSSReaderBundleID = defaultRSSReaderBundleID , let defaultReader = RSSReader ( bundleID : defaultRSSReaderBundleID ) {
rssReaders . insert ( defaultReader )
}
rssReaderBundleIDs . forEach { ( bundleID ) in
if let reader = RSSReader ( bundleID : bundleID ) {
rssReaders . insert ( reader )
}
}
return rssReaders
}
}
// MARK: - R S S R e a d e r
private struct RSSReader : Hashable {
let bundleID : String
let name : String
let nameMinusAppSuffix : String
let path : String
init ? ( bundleID : String ) {
guard let path = NSWorkspace . shared . absolutePathForApplication ( withBundleIdentifier : bundleID ) else {
return nil
}
self . path = path
self . bundleID = bundleID
let name = ( path as NSString ) . lastPathComponent
self . name = name
if name . hasSuffix ( " .app " ) {
self . nameMinusAppSuffix = name . stripping ( suffix : " .app " )
}
else {
self . nameMinusAppSuffix = name
}
}
// MARK: - H a s h a b l e
func hash ( into hasher : inout Hasher ) {
hasher . combine ( bundleID )
}
// MARK: - E q u a t a b l e
static func = = ( lhs : RSSReader , rhs : RSSReader ) -> Bool {
return lhs . bundleID = = rhs . bundleID
}
}