2017-05-22 22:00:45 +02:00
//
// A p p D e l e g a t e . s w i f t
2018-08-29 07:18:24 +02:00
// N e t N e w s W i r e
2017-05-22 22:00:45 +02:00
//
2017-05-27 19:43:27 +02:00
// C r e a t e d b y B r e n t S i m m o n s o n 7 / 1 1 / 1 5 .
// C o p y r i g h t © 2 0 1 5 R a n c h e r o S o f t w a r e , L L C . A l l r i g h t s r e s e r v e d .
2017-05-22 22:00:45 +02:00
//
2018-02-03 07:51:32 +01:00
import AppKit
2019-10-03 02:42:16 +02:00
import UserNotifications
2018-07-24 03:29:08 +02:00
import Articles
2017-05-27 19:43:27 +02:00
import RSTree
2017-05-27 22:37:50 +02:00
import RSWeb
2017-09-18 02:12:42 +02:00
import Account
2017-11-14 03:33:23 +01:00
import RSCore
2020-07-29 12:11:57 +02:00
import RSCoreResources
2020-07-31 00:40:45 +02:00
import Secrets
2020-12-02 14:56:22 +01:00
import OSLog
2020-12-12 01:09:36 +01:00
import CrashReporter
2019-10-23 01:44:06 +02:00
// I f w e ' r e n o t g o i n g t o i m p o r t S p a r k l e , p r o v i d e d u m m y p r o t o c o l s t o m a k e i t e a s y
// f o r A p p D e l e g a t e t o c o m p l y
#if MAC_APP_STORE || TEST
protocol SPUStandardUserDriverDelegate { }
protocol SPUUpdaterDelegate { }
#else
2019-10-22 23:42:17 +02:00
import Sparkle
#endif
2017-05-27 19:43:27 +02:00
2017-11-15 06:31:17 +01:00
var appDelegate : AppDelegate !
2017-05-22 22:00:45 +02:00
@NSApplicationMain
2019-10-23 01:44:06 +02:00
class AppDelegate : NSObject , NSApplicationDelegate , NSUserInterfaceValidations , UNUserNotificationCenterDelegate , UnreadCountProvider , SPUStandardUserDriverDelegate , SPUUpdaterDelegate
{
2017-05-22 22:00:45 +02:00
2020-03-03 02:46:31 +01:00
private struct WindowRestorationIdentifiers {
static let mainWindow = " mainWindow "
}
2019-10-03 02:42:16 +02:00
var userNotificationManager : UserNotificationManager !
2017-11-25 06:39:59 +01:00
var faviconDownloader : FaviconDownloader !
2017-11-26 05:12:53 +01:00
var imageDownloader : ImageDownloader !
2017-11-26 22:16:32 +01:00
var authorAvatarDownloader : AuthorAvatarDownloader !
2019-11-15 03:11:41 +01:00
var webFeedIconDownloader : WebFeedIconDownloader !
2020-08-14 01:03:39 +02:00
var extensionContainersFile : ExtensionContainersFile !
var extensionFeedAddRequestFile : ExtensionFeedAddRequestFile !
2017-11-25 06:39:59 +01:00
var appName : String !
2019-05-16 00:21:58 +02:00
var refreshTimer : AccountRefreshTimer ?
var syncTimer : ArticleStatusSyncTimer ?
2021-03-25 19:51:40 +01:00
var lastRefreshInterval = AppDefaults . shared . refreshInterval
2019-05-16 00:21:58 +02:00
2019-01-28 03:00:09 +01:00
var shuttingDown = false {
didSet {
if shuttingDown {
2019-04-23 18:20:44 +02:00
refreshTimer ? . shuttingDown = shuttingDown
refreshTimer ? . invalidate ( )
2019-05-16 00:21:58 +02:00
syncTimer ? . shuttingDown = shuttingDown
syncTimer ? . invalidate ( )
2019-01-28 03:00:09 +01:00
}
}
}
2021-01-10 00:58:51 +01:00
var isShutDownSyncDone = false
2018-01-27 20:32:59 +01:00
@IBOutlet var debugMenuItem : NSMenuItem !
2018-01-28 00:11:02 +01:00
@IBOutlet var sortByOldestArticleOnTopMenuItem : NSMenuItem !
@IBOutlet var sortByNewestArticleOnTopMenuItem : NSMenuItem !
2019-09-09 00:09:26 +02:00
@IBOutlet var groupArticlesByFeedMenuItem : NSMenuItem !
2018-12-09 21:12:55 +01:00
@IBOutlet var checkForUpdatesMenuItem : NSMenuItem !
2019-09-19 17:38:17 +02:00
2017-05-27 19:43:27 +02:00
var unreadCount = 0 {
didSet {
2017-10-06 03:12:58 +02:00
if unreadCount != oldValue {
2018-12-28 06:19:19 +01:00
CoalescingQueue . standard . add ( self , #selector ( updateDockBadge ) )
2017-11-19 22:57:42 +01:00
postUnreadCountDidChangeNotification ( )
2017-10-06 03:12:58 +02:00
}
2017-05-27 19:43:27 +02:00
}
}
2020-03-03 02:46:31 +01:00
private var mainWindowController : MainWindowController ? {
var bestController : MainWindowController ?
for candidateController in mainWindowControllers {
if let bestWindow = bestController ? . window , let candidateWindow = candidateController . window {
if bestWindow . orderedIndex > candidateWindow . orderedIndex {
bestController = candidateController
}
} else {
bestController = candidateController
}
}
return bestController
}
private var mainWindowControllers = [ MainWindowController ] ( )
2017-11-25 06:39:59 +01:00
private var preferencesWindowController : NSWindowController ?
private var addFeedController : AddFeedController ?
private var addFolderWindowController : AddFolderWindowController ?
2019-05-01 23:04:56 +02:00
private var importOPMLController : ImportOPMLWindowController ?
private var exportOPMLController : ExportOPMLWindowController ?
2017-11-25 06:39:59 +01:00
private var keyboardShortcutsWindowController : WebViewWindowController ?
private var inspectorWindowController : InspectorWindowController ?
2018-12-29 21:31:39 +01:00
private var crashReportWindowController : CrashReportWindowController ? // F o r t e s t i n g o n l y
2019-09-06 15:13:09 +02:00
private let appMovementMonitor = RSAppMovementMonitor ( )
2019-10-23 01:33:00 +02:00
#if ! MAC_APP_STORE && ! TEST
private var softwareUpdater : SPUUpdater !
2020-12-12 01:09:36 +01:00
private var crashReporter : PLCrashReporter !
2019-10-23 01:33:00 +02:00
#endif
2021-09-23 14:12:35 +02:00
private var themeImportPath : String ?
2017-11-25 06:39:59 +01:00
2017-05-27 19:43:27 +02:00
override init ( ) {
NSWindow . allowsAutomaticWindowTabbing = false
super . init ( )
2017-10-19 03:37:45 +02:00
2020-12-12 01:09:36 +01:00
#if ! MAC_APP_STORE
let crashReporterConfig = PLCrashReporterConfig . defaultConfiguration ( )
crashReporter = PLCrashReporter ( configuration : crashReporterConfig )
crashReporter . enable ( )
#endif
2020-07-31 00:40:45 +02:00
SecretsManager . provider = Secrets ( )
2020-01-10 21:00:22 +01:00
AccountManager . shared = AccountManager ( accountsFolder : Platform . dataSubfolder ( forApplication : nil , folderName : " Accounts " ) ! )
2021-09-07 23:58:06 +02:00
ArticleThemesManager . shared = ArticleThemesManager ( folderPath : Platform . dataSubfolder ( forApplication : nil , folderName : " Themes " ) ! )
2020-04-16 22:06:56 +02:00
FeedProviderManager . shared . delegate = ExtensionPointManager . shared
2017-10-19 03:37:45 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( unreadCountDidChange ( _ : ) ) , name : . UnreadCountDidChange , object : nil )
2019-02-18 03:46:28 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( inspectableObjectsDidChange ( _ : ) ) , name : . InspectableObjectsDidChange , object : nil )
2021-09-20 13:34:25 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( importDownloadedTheme ( _ : ) ) , name : . didEndDownloadingTheme , object : nil )
2021-09-21 03:22:45 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( themeImportError ( _ : ) ) , name : . didFailToImportThemeWithError , object : nil )
2020-02-26 20:29:59 +01:00
NSWorkspace . shared . notificationCenter . addObserver ( self , selector : #selector ( didWakeNotification ( _ : ) ) , name : NSWorkspace . didWakeNotification , object : nil )
2018-01-21 21:46:22 +01:00
2017-11-15 06:31:17 +01:00
appDelegate = self
}
2017-11-15 22:13:40 +01:00
// MARK: - A P I
func showAddFolderSheetOnWindow ( _ window : NSWindow ) {
addFolderWindowController = AddFolderWindowController ( )
addFolderWindowController ! . runSheetOnWindow ( window )
}
2020-04-22 04:25:45 +02:00
func showAddWebFeedSheetOnWindow ( _ window : NSWindow , urlString : String ? , name : String ? , account : Account ? , folder : Folder ? ) {
2018-02-04 06:30:30 +01:00
addFeedController = AddFeedController ( hostWindow : window )
2020-04-22 04:25:45 +02:00
addFeedController ? . showAddFeedSheet ( . webFeed , urlString , name , account , folder )
2018-02-04 06:30:30 +01:00
}
2018-09-13 17:04:20 +02:00
2017-09-19 07:00:35 +02:00
// MARK: - N S A p p l i c a t i o n D e l e g a t e
2019-10-18 07:11:35 +02:00
2018-11-27 04:22:05 +01:00
func applicationWillFinishLaunching ( _ notification : Notification ) {
installAppleEventHandlers ( )
2020-03-03 02:46:31 +01:00
CacheCleaner . purgeIfNecessary ( )
// T r y t o e s t a b l i s h a c a c h e i n t h e C a c h e s f o l d e r , b u t i f i t f a i l s f o r s o m e r e a s o n f a l l b a c k t o a t e m p o r a r y d i r
let cacheFolder : String
if let userCacheFolder = try ? FileManager . default . url ( for : . cachesDirectory , in : . userDomainMask , appropriateFor : nil , create : false ) . path {
cacheFolder = userCacheFolder
}
else {
let bundleIdentifier = ( Bundle . main . infoDictionary ! [ " CFBundleIdentifier " ] ! as ! String )
cacheFolder = ( NSTemporaryDirectory ( ) as NSString ) . appendingPathComponent ( bundleIdentifier )
}
let faviconsFolder = ( cacheFolder as NSString ) . appendingPathComponent ( " Favicons " )
let faviconsFolderURL = URL ( fileURLWithPath : faviconsFolder )
try ! FileManager . default . createDirectory ( at : faviconsFolderURL , withIntermediateDirectories : true , attributes : nil )
faviconDownloader = FaviconDownloader ( folder : faviconsFolder )
let imagesFolder = ( cacheFolder as NSString ) . appendingPathComponent ( " Images " )
let imagesFolderURL = URL ( fileURLWithPath : imagesFolder )
try ! FileManager . default . createDirectory ( at : imagesFolderURL , withIntermediateDirectories : true , attributes : nil )
imageDownloader = ImageDownloader ( folder : imagesFolder )
authorAvatarDownloader = AuthorAvatarDownloader ( imageDownloader : imageDownloader )
webFeedIconDownloader = WebFeedIconDownloader ( imageDownloader : imageDownloader , folder : cacheFolder )
2020-03-04 23:40:40 +01:00
appName = ( Bundle . main . infoDictionary ! [ " CFBundleExecutable " ] ! as ! String )
2018-11-27 04:22:05 +01:00
}
2017-05-27 19:43:27 +02:00
func applicationDidFinishLaunching ( _ note : Notification ) {
2019-10-23 01:33:00 +02:00
#if MAC_APP_STORE || TEST
2018-12-09 21:12:55 +01:00
checkForUpdatesMenuItem . isHidden = true
2019-10-23 01:33:00 +02:00
#else
// I n i t i a l i z e S p a r k l e . . .
let hostBundle = Bundle . main
let updateDriver = SPUStandardUserDriver ( hostBundle : hostBundle , delegate : self )
self . softwareUpdater = SPUUpdater ( hostBundle : hostBundle , applicationBundle : hostBundle , userDriver : updateDriver , delegate : self )
do {
try self . softwareUpdater . start ( )
}
catch {
NSLog ( " Failed to start software updater with error: \( error ) " )
}
2018-12-09 21:12:55 +01:00
#endif
2020-07-02 05:17:38 +02:00
AppDefaults . shared . registerDefaults ( )
let isFirstRun = AppDefaults . shared . isFirstRun
2017-11-26 01:10:19 +01:00
if isFirstRun {
2020-12-02 14:56:22 +01:00
os_log ( . debug , " Is first run. " )
2017-11-26 01:10:19 +01:00
}
2019-05-01 12:53:18 +02:00
let localAccount = AccountManager . shared . defaultAccount
2019-10-17 22:25:11 +02:00
if isFirstRun && ! AccountManager . shared . anyAccountHasAtLeastOneFeed ( ) {
// I m p o r t f e e d s . E i t h e r o l d N N W 3 f e e d s o r t h e d e f a u l t f e e d s .
if ! NNW3ImportController . importSubscriptionsIfFileExists ( account : localAccount ) {
DefaultFeedsImporter . importDefaultFeeds ( account : localAccount )
}
}
2017-05-27 19:43:27 +02:00
2018-01-28 00:24:33 +01:00
updateSortMenuItems ( )
2019-09-09 00:09:26 +02:00
updateGroupByFeedMenuItem ( )
2020-03-06 01:42:17 +01:00
if mainWindowController = = nil {
let mainWindowController = createAndShowMainWindow ( )
mainWindowController . restoreStateFromUserDefaults ( )
}
2019-08-24 03:30:28 +02:00
if isFirstRun {
mainWindowController ? . window ? . center ( )
}
2017-10-19 03:37:45 +02:00
2019-11-15 03:11:41 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( webFeedSettingDidChange ( _ : ) ) , name : . WebFeedSettingDidChange , object : nil )
2018-01-28 00:24:33 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( userDefaultsDidChange ( _ : ) ) , name : UserDefaults . didChangeNotification , object : nil )
2017-11-26 22:16:32 +01:00
2017-10-19 03:37:45 +02:00
DispatchQueue . main . async {
self . unreadCount = AccountManager . shared . unreadCount
}
2017-12-25 19:23:12 +01:00
2018-01-21 22:27:17 +01:00
if InspectorWindowController . shouldOpenAtStartup {
2018-01-21 22:30:26 +01:00
self . toggleInspectorWindow ( self )
2018-01-21 22:27:17 +01:00
}
2020-08-14 01:03:39 +02:00
extensionContainersFile = ExtensionContainersFile ( )
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile ( )
2019-05-16 00:21:58 +02:00
refreshTimer = AccountRefreshTimer ( )
syncTimer = ArticleStatusSyncTimer ( )
2019-04-24 21:46:01 +02:00
2020-08-21 19:51:53 +02:00
UNUserNotificationCenter . current ( ) . requestAuthorization ( options : [ . badge ] ) { ( granted , error ) in }
2020-07-27 19:54:34 +02:00
UNUserNotificationCenter . current ( ) . getNotificationSettings { ( settings ) in
if settings . authorizationStatus = = . authorized {
DispatchQueue . main . async {
NSApplication . shared . registerForRemoteNotifications ( )
}
}
}
2019-10-03 02:42:16 +02:00
UNUserNotificationCenter . current ( ) . delegate = self
userNotificationManager = UserNotificationManager ( )
2021-03-22 23:20:40 +01:00
#if DEBUG
refreshTimer ! . update ( )
syncTimer ! . update ( )
#else
2021-06-17 03:20:32 +02:00
if AppDefaults . shared . suppressSyncOnLaunch {
refreshTimer ! . update ( )
syncTimer ! . update ( )
} else {
DispatchQueue . main . async {
self . refreshTimer ! . timedRefresh ( nil )
self . syncTimer ! . timedRefresh ( nil )
}
2021-03-22 23:20:40 +01:00
}
#endif
2020-07-02 05:17:38 +02:00
if AppDefaults . shared . showDebugMenu {
2019-11-28 21:59:45 +01:00
// T h e W e b I n s p e c t o r u s e s S P I a n d c a n n e v e r a p p e a r i n a M A C _ A P P _ S T O R E b u i l d .
#if MAC_APP_STORE
let debugMenu = debugMenuItem . submenu !
let toggleWebInspectorItemIndex = debugMenu . indexOfItem ( withTarget : self , andAction : #selector ( toggleWebInspectorEnabled ( _ : ) ) )
if toggleWebInspectorItemIndex != - 1 {
debugMenu . removeItem ( at : toggleWebInspectorItemIndex )
}
#endif
} else {
2018-01-27 20:32:59 +01:00
debugMenuItem . menu ? . removeItem ( debugMenuItem )
2019-11-28 21:59:45 +01:00
}
2019-01-28 03:00:09 +01:00
2019-01-12 08:18:48 +01:00
#if ! MAC_APP_STORE
2020-12-12 01:09:36 +01:00
DispatchQueue . main . async {
CrashReporter . check ( crashReporter : self . crashReporter )
}
2019-01-12 08:18:48 +01:00
#endif
2020-03-30 09:48:25 +02:00
2017-05-27 19:43:27 +02:00
}
2019-10-03 22:49:27 +02:00
func application ( _ application : NSApplication , continue userActivity : NSUserActivity , restorationHandler : @ escaping ( [ NSUserActivityRestoring ] ) -> Void ) -> Bool {
guard let mainWindowController = mainWindowController else {
return false
}
mainWindowController . handle ( userActivity )
return true
}
2017-05-27 19:43:27 +02:00
func applicationShouldHandleReopen ( _ sender : NSApplication , hasVisibleWindows flag : Bool ) -> Bool {
2019-01-28 05:25:09 +01:00
// h t t p s : / / g i t h u b . c o m / b r e n t s i m m o n s / N e t N e w s W i r e / i s s u e s / 5 2 2
// I c o u l d n ’ t r e p r o d u c e t h e c r a s h i n g b u g , b u t i t a p p e a r s t o h a p p e n o n c r e a t i n g a m a i n w i n d o w
// a n d i t s v i e w s a n d v i e w c o n t r o l l e r s . T h e c h e c k b e l o w i s s o t h a t t h e a p p d o e s n o t h i n g
// i f t h e w i n d o w d o e s n ’ t a l r e a d y e x i s t — b e c a u s e i t a b s o l u t e l y * s h o u l d * e x i s t a l r e a d y .
// A n d i f t h e w i n d o w e x i s t s , t h e n m a y b e t h e v i e w s a n d v i e w c o n t r o l l e r s a r e a l s o a l r e a d y l o a d e d ?
// W e ’ l l t r y t h i s , a n d t h e n s e e i f w e g e t m o r e c r a s h l o g s l i k e t h i s o r n o t .
guard let mainWindowController = mainWindowController , mainWindowController . isWindowLoaded else {
return false
}
mainWindowController . showWindow ( self )
2017-05-27 19:43:27 +02:00
return false
}
2019-01-28 03:00:09 +01:00
func applicationDidBecomeActive ( _ notification : Notification ) {
2020-02-26 20:29:59 +01:00
fireOldTimers ( )
2019-01-28 03:00:09 +01:00
}
2017-05-27 19:43:27 +02:00
func applicationDidResignActive ( _ notification : Notification ) {
2019-10-20 09:28:00 +02:00
ArticleStringFormatter . emptyCaches ( )
2018-01-21 22:27:17 +01:00
saveState ( )
}
2020-03-30 09:48:25 +02:00
func application ( _ application : NSApplication , didReceiveRemoteNotification userInfo : [ String : Any ] ) {
AccountManager . shared . receiveRemoteNotification ( userInfo : userInfo )
}
2021-09-08 07:28:13 +02:00
func application ( _ sender : NSApplication , openFile filename : String ) -> Bool {
2021-09-12 20:30:15 +02:00
guard filename . hasSuffix ( ArticleTheme . nnwThemeSuffix ) else { return false }
2021-09-21 04:43:12 +02:00
importTheme ( filename : filename )
2021-09-08 07:28:13 +02:00
return true
}
2018-01-21 22:27:17 +01:00
func applicationWillTerminate ( _ notification : Notification ) {
2019-01-28 03:00:09 +01:00
shuttingDown = true
2018-01-21 22:27:17 +01:00
saveState ( )
2021-01-10 00:58:51 +01:00
2021-09-21 03:10:56 +02:00
ArticleThemeDownloader . shared . cleanUp ( )
2021-01-19 00:48:07 +01:00
AccountManager . shared . sendArticleStatusAll ( ) {
2021-01-10 00:58:51 +01:00
self . isShutDownSyncDone = true
}
2021-01-19 00:48:07 +01:00
let timeout = Date ( ) . addingTimeInterval ( 2 )
2021-09-06 21:52:41 +02:00
while ! isShutDownSyncDone && RunLoop . current . run ( mode : . default , before : timeout ) && timeout > Date ( ) { }
2017-05-27 19:43:27 +02:00
}
// MARK: N o t i f i c a t i o n s
2017-10-19 03:37:45 +02:00
@objc func unreadCountDidChange ( _ note : Notification ) {
if note . object is AccountManager {
unreadCount = AccountManager . shared . unreadCount
}
2017-05-27 19:43:27 +02:00
}
2019-11-15 03:11:41 +01:00
@objc func webFeedSettingDidChange ( _ note : Notification ) {
guard let feed = note . object as ? WebFeed , let key = note . userInfo ? [ WebFeed . WebFeedSettingUserInfoKey ] as ? String else {
2017-11-26 22:16:32 +01:00
return
}
2019-11-15 03:11:41 +01:00
if key = = WebFeed . WebFeedSettingKey . homePageURL || key = = WebFeed . WebFeedSettingKey . faviconURL {
2019-03-17 21:54:30 +01:00
let _ = faviconDownloader . favicon ( for : feed )
}
2017-11-26 22:16:32 +01:00
}
2019-02-18 03:46:28 +01:00
@objc func inspectableObjectsDidChange ( _ note : Notification ) {
2018-01-21 21:46:22 +01:00
guard let inspectorWindowController = inspectorWindowController , inspectorWindowController . isOpen else {
return
}
inspectorWindowController . objects = objectsForInspector ( )
}
2018-01-28 00:24:33 +01:00
@objc func userDefaultsDidChange ( _ note : Notification ) {
updateSortMenuItems ( )
2019-09-09 00:09:26 +02:00
updateGroupByFeedMenuItem ( )
2021-03-25 19:51:40 +01:00
if lastRefreshInterval != AppDefaults . shared . refreshInterval {
refreshTimer ? . update ( )
lastRefreshInterval = AppDefaults . shared . refreshInterval
}
2019-09-04 07:17:31 +02:00
updateDockBadge ( )
2018-01-28 00:24:33 +01:00
}
2020-02-26 20:29:59 +01:00
@objc func didWakeNotification ( _ note : Notification ) {
fireOldTimers ( )
}
2021-09-20 13:34:25 +02:00
@objc func importDownloadedTheme ( _ note : Notification ) {
guard let userInfo = note . userInfo ,
let url = userInfo [ " url " ] as ? URL else {
return
}
DispatchQueue . main . async {
2021-09-21 04:43:12 +02:00
self . importTheme ( filename : url . path )
2021-09-20 13:34:25 +02:00
}
}
2018-01-28 00:24:33 +01:00
2017-05-27 19:43:27 +02:00
// MARK: M a i n W i n d o w
2020-03-03 02:46:31 +01:00
func createMainWindowController ( ) -> MainWindowController {
2020-08-09 03:49:22 +02:00
let controller : MainWindowController
2020-08-10 18:43:18 +02:00
if #available ( macOS 11.0 , * ) {
2020-12-06 22:21:30 +01:00
controller = windowControllerWithName ( " UnifiedWindow " ) as ! MainWindowController
2020-08-09 03:49:22 +02:00
} else {
controller = windowControllerWithName ( " MainWindow " ) as ! MainWindowController
}
2020-03-06 01:42:17 +01:00
if ! ( mainWindowController ? . isOpen ? ? false ) {
mainWindowControllers . removeAll ( )
}
2020-03-03 02:46:31 +01:00
mainWindowControllers . append ( controller )
return controller
}
2017-05-27 19:43:27 +02:00
2020-03-03 02:46:31 +01:00
func windowControllerWithName ( _ storyboardName : String ) -> NSWindowController {
2018-12-09 21:32:33 +01:00
let storyboard = NSStoryboard ( name : NSStoryboard . Name ( storyboardName ) , bundle : nil )
2017-05-27 19:43:27 +02:00
return storyboard . instantiateInitialController ( ) ! as ! NSWindowController
}
2020-03-03 02:46:31 +01:00
@ discardableResult
2020-03-06 01:42:17 +01:00
func createAndShowMainWindow ( ) -> MainWindowController {
2020-03-03 02:46:31 +01:00
let controller = createMainWindowController ( )
controller . showWindow ( self )
if let window = controller . window {
window . restorationClass = Self . self
window . identifier = NSUserInterfaceItemIdentifier ( rawValue : WindowRestorationIdentifiers . mainWindow )
}
2020-03-06 01:42:17 +01:00
return controller
2020-03-03 02:46:31 +01:00
}
2017-05-27 19:43:27 +02:00
2020-03-03 02:46:31 +01:00
func createAndShowMainWindowIfNecessary ( ) {
2017-05-27 19:43:27 +02:00
if mainWindowController = = nil {
2020-03-03 02:46:31 +01:00
createAndShowMainWindow ( )
2020-03-06 01:42:17 +01:00
} else {
mainWindowController ? . showWindow ( self )
2017-05-27 19:43:27 +02:00
}
}
2020-03-03 03:06:55 +01:00
func removeMainWindow ( _ windowController : MainWindowController ) {
2020-03-06 01:42:17 +01:00
guard mainWindowControllers . count > 1 else { return }
2020-03-03 03:06:55 +01:00
if let index = mainWindowControllers . firstIndex ( of : windowController ) {
mainWindowControllers . remove ( at : index )
}
}
2017-05-27 19:43:27 +02:00
// MARK: N S U s e r I n t e r f a c e V a l i d a t i o n s
func validateUserInterfaceItem ( _ item : NSValidatedUserInterfaceItem ) -> Bool {
2019-01-28 03:00:09 +01:00
if shuttingDown {
return false
}
2017-05-27 19:43:27 +02:00
2018-02-03 19:56:12 +01:00
let isDisplayingSheet = mainWindowController ? . isDisplayingSheet ? ? false
2020-08-12 03:19:17 +02:00
let isSpecialAccountAvailable = AccountManager . shared . activeAccounts . contains ( where : { $0 . type = = . onMyMac || $0 . type = = . cloudKit } )
2018-02-03 19:56:12 +01:00
2017-05-27 19:43:27 +02:00
if item . action = = #selector ( refreshAll ( _ : ) ) {
2019-05-19 21:23:54 +02:00
return ! AccountManager . shared . refreshInProgress && ! AccountManager . shared . activeAccounts . isEmpty
2017-05-27 19:43:27 +02:00
}
2020-10-19 03:32:10 +02:00
if item . action = = #selector ( importOPMLFromFile ( _ : ) ) {
return AccountManager . shared . activeAccounts . contains ( where : { ! $0 . behaviors . contains ( where : { $0 = = . disallowOPMLImports } ) } )
}
2017-05-27 19:43:27 +02:00
if item . action = = #selector ( addAppNews ( _ : ) ) {
2020-10-29 23:52:58 +01:00
return ! isDisplayingSheet && ! AccountManager . shared . anyAccountHasNetNewsWireNewsSubscription ( ) && ! AccountManager . shared . activeAccounts . isEmpty
2017-05-27 19:43:27 +02:00
}
2020-10-19 03:32:10 +02:00
2018-01-28 00:13:45 +01:00
if item . action = = #selector ( sortByNewestArticleOnTop ( _ : ) ) || item . action = = #selector ( sortByOldestArticleOnTop ( _ : ) ) {
return mainWindowController ? . isOpen ? ? false
}
2020-10-19 03:32:10 +02:00
2020-04-22 04:25:45 +02:00
if item . action = = #selector ( showAddWebFeedWindow ( _ : ) ) || item . action = = #selector ( showAddFolderWindow ( _ : ) ) {
2019-05-19 17:21:42 +02:00
return ! isDisplayingSheet && ! AccountManager . shared . activeAccounts . isEmpty
2018-02-03 19:56:12 +01:00
}
2020-10-19 03:32:10 +02:00
2020-05-10 18:44:30 +02:00
if item . action = = #selector ( showAddRedditFeedWindow ( _ : ) ) {
2020-08-12 17:27:58 +02:00
guard ! isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager . shared . isRedditEnabled else {
2020-05-10 18:44:30 +02:00
return false
}
return ExtensionPointManager . shared . isRedditEnabled
}
2020-10-19 03:32:10 +02:00
2020-04-22 18:36:07 +02:00
if item . action = = #selector ( showAddTwitterFeedWindow ( _ : ) ) {
2020-08-12 17:27:58 +02:00
guard ! isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager . shared . isTwitterEnabled else {
2020-05-02 22:06:59 +02:00
return false
}
2020-05-03 00:21:01 +02:00
return ExtensionPointManager . shared . isTwitterEnabled
2020-04-22 18:36:07 +02:00
}
2020-10-19 03:32:10 +02:00
2019-09-19 18:17:58 +02:00
#if ! MAC_APP_STORE
2019-09-17 04:06:35 +02:00
if item . action = = #selector ( toggleWebInspectorEnabled ( _ : ) ) {
2020-07-02 05:17:38 +02:00
( item as ! NSMenuItem ) . state = AppDefaults . shared . webInspectorEnabled ? . on : . off
2019-09-17 04:06:35 +02:00
}
2019-09-19 17:38:17 +02:00
#endif
2020-10-19 03:32:10 +02:00
2017-05-27 19:43:27 +02:00
return true
}
2019-10-03 02:42:16 +02:00
// MARK: U N U s e r N o t i f i c a t i o n C e n t e r D e l e g a t e
func userNotificationCenter ( _ center : UNUserNotificationCenter , willPresent notification : UNNotification , withCompletionHandler completionHandler : @ escaping ( UNNotificationPresentationOptions ) -> Void ) {
completionHandler ( [ . alert , . badge , . sound ] )
}
2019-10-03 18:39:48 +02:00
func userNotificationCenter ( _ center : UNUserNotificationCenter , didReceive response : UNNotificationResponse , withCompletionHandler completionHandler : @ escaping ( ) -> Void ) {
2020-12-23 14:44:45 +01:00
let userInfo = response . notification . request . content . userInfo
switch response . actionIdentifier {
case " MARK_AS_READ " :
handleMarkAsRead ( userInfo : userInfo )
case " MARK_AS_STARRED " :
handleMarkAsStarred ( userInfo : userInfo )
default :
mainWindowController ? . handle ( response )
}
2019-10-03 18:39:48 +02:00
completionHandler ( )
}
2017-05-27 19:43:27 +02:00
// MARK: A d d F e e d
2020-04-22 04:25:45 +02:00
func addWebFeed ( _ urlString : String ? , name : String ? = nil , account : Account ? = nil , folder : Folder ? = nil ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2018-02-03 19:56:12 +01:00
if mainWindowController ! . isDisplayingSheet {
return
}
2017-05-27 19:43:27 +02:00
2020-04-22 04:25:45 +02:00
showAddWebFeedSheetOnWindow ( mainWindowController ! . window ! , urlString : urlString , name : name , account : account , folder : folder )
2017-05-27 19:43:27 +02:00
}
2018-12-28 06:19:19 +01:00
// MARK: - D o c k B a d g e
@objc func updateDockBadge ( ) {
2021-03-14 20:06:09 +01:00
let label = unreadCount > 0 ? " \( unreadCount ) " : " "
2018-12-28 06:19:19 +01:00
NSApplication . shared . dockTile . badgeLabel = label
}
2017-09-23 21:17:14 +02:00
// MARK: - A c t i o n s
2018-02-12 03:58:50 +01:00
@IBAction func showPreferences ( _ sender : Any ? ) {
2017-05-27 19:43:27 +02:00
if preferencesWindowController = = nil {
preferencesWindowController = windowControllerWithName ( " Preferences " )
}
preferencesWindowController ! . showWindow ( self )
}
2020-03-03 02:46:31 +01:00
@IBAction func newMainWindow ( _ sender : Any ? ) {
2017-05-27 19:43:27 +02:00
createAndShowMainWindow ( )
}
2020-03-03 02:46:31 +01:00
@IBAction func showMainWindow ( _ sender : Any ? ) {
createAndShowMainWindowIfNecessary ( )
2020-03-05 02:22:15 +01:00
mainWindowController ? . window ? . makeKey ( )
2020-03-03 02:46:31 +01:00
}
2017-05-27 19:43:27 +02:00
2020-03-03 02:46:31 +01:00
@IBAction func refreshAll ( _ sender : Any ? ) {
2019-05-26 18:54:32 +02:00
AccountManager . shared . refreshAll ( errorHandler : ErrorHandler . present )
2017-05-27 19:43:27 +02:00
}
2020-04-22 04:25:45 +02:00
@IBAction func showAddWebFeedWindow ( _ sender : Any ? ) {
addWebFeed ( nil )
}
2020-05-10 18:44:30 +02:00
@IBAction func showAddRedditFeedWindow ( _ sender : Any ? ) {
createAndShowMainWindowIfNecessary ( )
addFeedController = AddFeedController ( hostWindow : mainWindowController ! . window ! )
addFeedController ? . showAddFeedSheet ( . redditFeed )
}
2020-04-22 04:25:45 +02:00
@IBAction func showAddTwitterFeedWindow ( _ sender : Any ? ) {
createAndShowMainWindowIfNecessary ( )
addFeedController = AddFeedController ( hostWindow : mainWindowController ! . window ! )
addFeedController ? . showAddFeedSheet ( . twitterFeed )
2017-05-27 19:43:27 +02:00
}
2018-02-12 03:58:50 +01:00
@IBAction func showAddFolderWindow ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2017-11-15 22:13:40 +01:00
showAddFolderSheetOnWindow ( mainWindowController ! . window ! )
2017-05-27 19:43:27 +02:00
}
2017-11-14 03:33:23 +01:00
@IBAction func showKeyboardShortcutsWindow ( _ sender : Any ? ) {
if keyboardShortcutsWindowController = = nil {
2020-03-03 02:46:31 +01:00
2017-11-14 03:33:23 +01:00
keyboardShortcutsWindowController = WebViewWindowController ( title : NSLocalizedString ( " Keyboard Shortcuts " , comment : " window title " ) )
let htmlFile = Bundle ( for : type ( of : self ) ) . path ( forResource : " KeyboardShortcuts " , ofType : " html " ) !
keyboardShortcutsWindowController ? . displayContents ( of : htmlFile )
2017-12-21 02:23:46 +01:00
2017-12-22 20:13:20 +01:00
if let window = keyboardShortcutsWindowController ? . window {
let point = NSPoint ( x : 128 , y : 64 )
2019-03-04 21:39:30 +01:00
let size = NSSize ( width : 620 , height : 1100 )
2017-12-22 20:13:20 +01:00
let minSize = NSSize ( width : 400 , height : 400 )
window . setPointAndSizeAdjustingForScreen ( point : point , size : size , minimumSize : minSize )
2017-12-21 02:23:46 +01:00
}
2020-03-03 02:46:31 +01:00
2017-11-14 03:33:23 +01:00
}
2017-12-21 02:23:46 +01:00
2017-11-14 03:33:23 +01:00
keyboardShortcutsWindowController ! . showWindow ( self )
}
2017-11-16 07:33:35 +01:00
@IBAction func toggleInspectorWindow ( _ sender : Any ? ) {
if inspectorWindowController = = nil {
2018-01-21 04:06:07 +01:00
inspectorWindowController = ( windowControllerWithName ( " Inspector " ) as ! InspectorWindowController )
2017-11-16 07:33:35 +01:00
}
if inspectorWindowController ! . isOpen {
inspectorWindowController ! . window ! . performClose ( self )
}
else {
2018-01-21 21:46:22 +01:00
inspectorWindowController ! . objects = objectsForInspector ( )
2017-11-16 07:33:35 +01:00
inspectorWindowController ! . showWindow ( self )
}
}
2018-02-12 03:58:50 +01:00
@IBAction func importOPMLFromFile ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2019-05-01 23:04:56 +02:00
if mainWindowController ! . isDisplayingSheet {
return
2017-05-27 19:43:27 +02:00
}
2019-05-01 23:04:56 +02:00
importOPMLController = ImportOPMLWindowController ( )
importOPMLController ? . runSheetOnWindow ( mainWindowController ! . window ! )
2019-10-15 03:45:58 +02:00
}
@IBAction func importNNW3FromFile ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2019-10-15 03:45:58 +02:00
if mainWindowController ! . isDisplayingSheet {
return
}
2019-10-17 22:25:11 +02:00
NNW3ImportController . askUserToImportNNW3Subscriptions ( window : mainWindowController ! . window ! )
2017-05-27 19:43:27 +02:00
}
2017-09-23 21:17:14 +02:00
2018-02-12 03:58:50 +01:00
@IBAction func exportOPML ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2019-05-01 23:04:56 +02:00
if mainWindowController ! . isDisplayingSheet {
return
2017-05-27 19:43:27 +02:00
}
2019-05-01 23:04:56 +02:00
exportOPMLController = ExportOPMLWindowController ( )
exportOPMLController ? . runSheetOnWindow ( mainWindowController ! . window ! )
2017-05-27 19:43:27 +02:00
}
2017-09-23 21:17:14 +02:00
2018-02-12 03:58:50 +01:00
@IBAction func addAppNews ( _ sender : Any ? ) {
2020-10-29 23:52:58 +01:00
if AccountManager . shared . anyAccountHasNetNewsWireNewsSubscription ( ) {
2017-05-27 19:43:27 +02:00
return
}
2020-10-29 23:52:58 +01:00
addWebFeed ( AccountManager . netNewsWireNewsURL , name : " NetNewsWire News " )
2017-05-27 19:43:27 +02:00
}
2017-05-27 22:37:50 +02:00
2018-02-12 03:58:50 +01:00
@IBAction func openWebsite ( _ sender : Any ? ) {
2017-05-27 22:37:50 +02:00
2021-02-22 03:00:40 +01:00
Browser . open ( " https://netnewswire.com/ " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
2020-08-15 09:01:00 +02:00
@IBAction func openReleaseNotes ( _ sender : Any ? ) {
Browser . open ( URL . releaseNotes . absoluteString , inBackground : false )
}
2017-05-27 22:37:50 +02:00
2019-06-15 00:38:00 +02:00
@IBAction func openHowToSupport ( _ sender : Any ? ) {
2020-07-26 11:46:48 +02:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown " , inBackground : false )
2019-06-15 00:38:00 +02:00
}
2018-02-12 03:58:50 +01:00
@IBAction func openRepository ( _ sender : Any ? ) {
2017-05-27 22:37:50 +02:00
2018-08-29 07:18:24 +02:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
2018-02-12 03:58:50 +01:00
@IBAction func openBugTracker ( _ sender : Any ? ) {
2017-05-27 22:37:50 +02:00
2018-08-29 07:18:24 +02:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/issues " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
2019-05-25 19:21:25 +02:00
@IBAction func openSlackGroup ( _ sender : Any ? ) {
2021-02-22 03:00:40 +01:00
Browser . open ( " https://netnewswire.com/slack " , inBackground : false )
2019-05-25 19:21:25 +02:00
}
2017-12-21 06:23:48 +01:00
@IBAction func openTechnotes ( _ sender : Any ? ) {
2020-07-26 11:46:48 +02:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes " , inBackground : false )
2017-12-21 06:23:48 +01:00
}
2018-02-12 03:58:50 +01:00
@IBAction func showHelp ( _ sender : Any ? ) {
2017-05-27 22:37:50 +02:00
2021-03-26 01:17:43 +01:00
Browser . open ( " https://netnewswire.com/help/mac/6.0/en/ " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
2017-11-17 03:23:07 +01:00
2018-02-12 22:10:13 +01:00
@IBAction func donateToAppCampForGirls ( _ sender : Any ? ) {
Browser . open ( " https://appcamp4girls.com/contribute/ " , inBackground : false )
}
2018-12-29 22:59:14 +01:00
@IBAction func showPrivacyPolicy ( _ sender : Any ? ) {
2021-02-22 03:00:40 +01:00
Browser . open ( " https://netnewswire.com/privacypolicy " , inBackground : false )
2018-12-29 22:59:14 +01:00
}
2018-01-27 21:39:07 +01:00
@IBAction func gotoToday ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 21:39:07 +01:00
mainWindowController ! . gotoToday ( sender )
}
@IBAction func gotoAllUnread ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 21:39:07 +01:00
mainWindowController ! . gotoAllUnread ( sender )
}
@IBAction func gotoStarred ( _ sender : Any ? ) {
2020-03-03 02:46:31 +01:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 21:39:07 +01:00
mainWindowController ! . gotoStarred ( sender )
}
2018-01-28 00:11:02 +01:00
@IBAction func sortByOldestArticleOnTop ( _ sender : Any ? ) {
2020-07-02 05:17:38 +02:00
AppDefaults . shared . timelineSortDirection = . orderedAscending
2018-01-28 00:11:02 +01:00
}
@IBAction func sortByNewestArticleOnTop ( _ sender : Any ? ) {
2020-07-02 05:17:38 +02:00
AppDefaults . shared . timelineSortDirection = . orderedDescending
2018-01-28 00:11:02 +01:00
}
2019-09-09 00:09:26 +02:00
@IBAction func groupByFeedToggled ( _ sender : NSMenuItem ) {
2020-07-02 05:17:38 +02:00
AppDefaults . shared . timelineGroupByFeed . toggle ( )
2019-09-09 00:09:26 +02:00
}
2019-10-23 01:33:00 +02:00
@IBAction func checkForUpdates ( _ sender : Any ? ) {
#if ! MAC_APP_STORE && ! TEST
self . softwareUpdater . checkForUpdates ( )
#endif
}
2017-05-27 19:43:27 +02:00
}
2019-02-19 07:29:43 +01:00
// MARK: - D e b u g M e n u
extension AppDelegate {
@IBAction func debugSearch ( _ sender : Any ? ) {
2019-05-01 12:53:18 +02:00
AccountManager . shared . defaultAccount . debugRunSearch ( )
2019-02-19 07:29:43 +01:00
}
2019-09-17 04:06:35 +02:00
2020-05-16 00:29:24 +02:00
@IBAction func debugDropConditionalGetInfo ( _ sender : Any ? ) {
#if DEBUG
AccountManager . shared . activeAccounts . forEach { $0 . debugDropConditionalGetInfo ( ) }
#endif
}
@IBAction func debugTestCrashReporterWindow ( _ sender : Any ? ) {
#if DEBUG
crashReportWindowController = CrashReportWindowController ( crashLogText : " This is a test crash log. " )
crashReportWindowController ! . testing = true
crashReportWindowController ! . showWindow ( self )
#endif
}
@IBAction func debugTestCrashReportSending ( _ sender : Any ? ) {
2021-03-06 23:44:58 +01:00
CrashReporter . sendCrashLogText ( " This is a test. Hi, Brent. " )
}
@IBAction func forceCrash ( _ sender : Any ? ) {
fatalError ( " This is a deliberate crash. " )
2020-05-16 00:29:24 +02:00
}
@IBAction func openApplicationSupportFolder ( _ sender : Any ? ) {
#if DEBUG
guard let appSupport = Platform . dataSubfolder ( forApplication : nil , folderName : " " ) else { return }
NSWorkspace . shared . open ( URL ( fileURLWithPath : appSupport ) )
#endif
}
2019-09-17 04:06:35 +02:00
@IBAction func toggleWebInspectorEnabled ( _ sender : Any ? ) {
2019-09-19 18:17:58 +02:00
#if ! MAC_APP_STORE
2020-07-02 05:17:38 +02:00
let newValue = ! AppDefaults . shared . webInspectorEnabled
AppDefaults . shared . webInspectorEnabled = newValue
2019-09-17 20:58:45 +02:00
2019-09-19 17:38:17 +02:00
// A n a t t a c h e d i n s p e c t o r c a n d i s p l a y i n c o r r e c t l y o n c e r t a i n s e t u p s ( l i k e m i n e ) ; d e f a u l t t o d i s p l a y i n g i n a s e p a r a t e w i n d o w ,
// a n d r e s e t t h e d e f a u l t t o a s e p a r a t e w i n d o w w h e n t h e p r e f e r e n c e i s t o g g l e d o f f a n d o n a g a i n i n c a s e t h e i n s p e c t o r i s
// a c c i d e n t a l l y r e a t t a c h e d .
2020-07-02 05:17:38 +02:00
AppDefaults . shared . webInspectorStartsAttached = false
2019-09-19 17:38:17 +02:00
NotificationCenter . default . post ( name : . WebInspectorEnabledDidChange , object : newValue )
#endif
2019-09-17 04:06:35 +02:00
}
2020-05-16 00:29:24 +02:00
2019-02-19 07:29:43 +01:00
}
2021-09-19 15:18:23 +02:00
internal extension AppDelegate {
2017-05-22 22:00:45 +02:00
2020-02-26 20:29:59 +01:00
func fireOldTimers ( ) {
// I t ’ s p o s s i b l e t h e r e ’ s a r e f r e s h t i m e r s e t t o g o o f f i n t h e p a s t .
// I n t h a t c a s e , r e f r e s h n o w a n d u p d a t e t h e t i m e r .
refreshTimer ? . fireOldTimer ( )
syncTimer ? . fireOldTimer ( )
}
2018-01-21 21:46:22 +01:00
func objectsForInspector ( ) -> [ Any ] ? {
guard let window = NSApplication . shared . mainWindow , let windowController = window . windowController as ? MainWindowController else {
return nil
}
return windowController . selectedObjectsInSidebar ( )
}
2018-01-21 22:27:17 +01:00
func saveState ( ) {
2020-03-06 01:42:17 +01:00
mainWindowController ? . saveStateToUserDefaults ( )
2018-02-12 22:31:43 +01:00
inspectorWindowController ? . saveState ( )
2018-01-21 22:27:17 +01:00
}
2018-01-28 00:24:33 +01:00
func updateSortMenuItems ( ) {
2020-07-02 05:17:38 +02:00
let sortByNewestOnTop = AppDefaults . shared . timelineSortDirection = = . orderedDescending
2018-01-28 00:24:33 +01:00
sortByNewestArticleOnTopMenuItem . state = sortByNewestOnTop ? . on : . off
sortByOldestArticleOnTopMenuItem . state = sortByNewestOnTop ? . off : . on
}
2019-09-09 00:09:26 +02:00
func updateGroupByFeedMenuItem ( ) {
2020-07-02 05:17:38 +02:00
let groupByFeedEnabled = AppDefaults . shared . timelineGroupByFeed
2019-09-09 00:09:26 +02:00
groupArticlesByFeedMenuItem . state = groupByFeedEnabled ? . on : . off
}
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
func importTheme ( filename : String ) {
2021-09-08 07:28:13 +02:00
guard let window = mainWindowController ? . window else { return }
2021-09-21 03:22:45 +02:00
do {
let theme = try ArticleTheme ( path : filename )
let alert = NSAlert ( )
alert . alertStyle = . informational
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
let localizedMessageText = NSLocalizedString ( " Install theme “%@” by %@? " , comment : " Theme message text " )
alert . messageText = NSString . localizedStringWithFormat ( localizedMessageText as NSString , theme . name , theme . creatorName ) as String
var attrs = [ NSAttributedString . Key : Any ] ( )
attrs [ . font ] = NSFont . systemFont ( ofSize : NSFont . smallSystemFontSize )
attrs [ . foregroundColor ] = NSColor . textColor
if #available ( macOS 11.0 , * ) {
let titleParagraphStyle = NSMutableParagraphStyle ( )
titleParagraphStyle . alignment = . center
attrs [ . paragraphStyle ] = titleParagraphStyle
}
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
let websiteText = NSMutableAttributedString ( )
websiteText . append ( NSAttributedString ( string : NSLocalizedString ( " Author's Website " , comment : " Author's Website " ) , attributes : attrs ) )
2021-09-09 08:53:40 +02:00
2021-09-21 03:22:45 +02:00
if #available ( macOS 11.0 , * ) {
websiteText . append ( NSAttributedString ( string : " \n " ) )
} else {
websiteText . append ( NSAttributedString ( string : " " ) )
}
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
attrs [ . link ] = theme . creatorHomePage
websiteText . append ( NSAttributedString ( string : theme . creatorHomePage , attributes : attrs ) )
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
let textViewWidth : CGFloat
if #available ( macOS 11.0 , * ) {
textViewWidth = 200
} else {
textViewWidth = 400
}
2021-09-09 09:08:47 +02:00
2021-09-21 03:22:45 +02:00
let textView = NSTextView ( frame : CGRect ( x : 0 , y : 0 , width : textViewWidth , height : 15 ) )
textView . isEditable = false
textView . drawsBackground = false
textView . textStorage ? . setAttributedString ( websiteText )
alert . accessoryView = textView
2021-09-08 07:28:13 +02:00
2021-09-21 03:22:45 +02:00
alert . addButton ( withTitle : NSLocalizedString ( " Install Theme " , comment : " Install Theme " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Cancel " , comment : " Cancel Install Theme " ) )
func importTheme ( ) {
do {
try ArticleThemesManager . shared . importTheme ( filename : filename )
confirmImportSuccess ( themeName : theme . name )
} catch {
NSApplication . shared . presentError ( error )
}
2021-09-09 13:03:28 +02:00
}
2021-09-21 03:22:45 +02:00
alert . beginSheetModal ( for : window ) { result in
if result = = NSApplication . ModalResponse . alertFirstButtonReturn {
if ArticleThemesManager . shared . themeExists ( filename : filename ) {
let alert = NSAlert ( )
alert . alertStyle = . warning
let localizedMessageText = NSLocalizedString ( " The theme “%@” already exists. Overwrite it? " , comment : " Overwrite theme " )
alert . messageText = NSString . localizedStringWithFormat ( localizedMessageText as NSString , theme . name ) as String
alert . addButton ( withTitle : NSLocalizedString ( " Overwrite " , comment : " Overwrite " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Cancel " , comment : " Cancel Install Theme " ) )
alert . beginSheetModal ( for : window ) { result in
if result = = NSApplication . ModalResponse . alertFirstButtonReturn {
importTheme ( )
}
2021-09-09 13:03:28 +02:00
}
2021-09-21 03:22:45 +02:00
} else {
importTheme ( )
2021-09-09 13:03:28 +02:00
}
2021-09-21 03:22:45 +02:00
}
2021-09-08 07:28:13 +02:00
}
2021-09-21 03:22:45 +02:00
} catch {
2021-09-23 14:12:35 +02:00
NotificationCenter . default . post ( name : . didFailToImportThemeWithError , object : nil , userInfo : [ " error " : error , " path " : filename ] )
2021-09-08 07:28:13 +02:00
}
}
2021-09-08 12:47:57 +02:00
func confirmImportSuccess ( themeName : String ) {
guard let window = mainWindowController ? . window else { return }
let alert = NSAlert ( )
alert . alertStyle = . informational
alert . messageText = NSLocalizedString ( " Theme installed " , comment : " Theme installed " )
2021-09-08 12:49:02 +02:00
let localizedInformativeText = NSLocalizedString ( " The theme “%@” has been installed. " , comment : " Theme installed " )
2021-09-08 12:47:57 +02:00
alert . informativeText = NSString . localizedStringWithFormat ( localizedInformativeText as NSString , themeName ) as String
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
2021-09-24 03:28:32 +02:00
2021-09-08 12:47:57 +02:00
alert . beginSheetModal ( for : window )
}
2021-09-21 03:10:56 +02:00
@objc func themeImportError ( _ note : Notification ) {
guard let userInfo = note . userInfo ,
2021-09-24 03:28:32 +02:00
let error = userInfo [ " error " ] as ? Error else {
2021-09-21 03:10:56 +02:00
return
}
2021-09-23 14:12:35 +02:00
themeImportPath = userInfo [ " path " ] as ? String
2021-09-21 04:43:12 +02:00
var informativeText : String = " "
if let decodingError = error as ? DecodingError {
switch decodingError {
case . typeMismatch ( let type , _ ) :
2021-09-24 03:28:32 +02:00
let localizedError = NSLocalizedString ( " This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist " , comment : " Type mismatch " )
informativeText = NSString . localizedStringWithFormat ( localizedError as NSString , type as ! CVarArg ) as String
2021-09-21 04:43:12 +02:00
case . valueNotFound ( let value , _ ) :
2021-09-24 03:28:32 +02:00
let localizedError = NSLocalizedString ( " This theme cannot be used because the the value—“%@”—is not found in the Info.plist. " , comment : " Decoding value missing " )
informativeText = NSString . localizedStringWithFormat ( localizedError as NSString , value as ! CVarArg ) as String
2021-09-21 04:43:12 +02:00
case . keyNotFound ( let codingKey , _ ) :
2021-09-24 03:28:32 +02:00
let localizedError = NSLocalizedString ( " This theme cannot be used because the the key—“%@”—is not found in the Info.plist. " , comment : " Decoding key missing " )
informativeText = NSString . localizedStringWithFormat ( localizedError as NSString , codingKey . stringValue ) as String
case . dataCorrupted ( let context ) :
guard let error = context . underlyingError as NSError ? ,
let debugDescription = error . userInfo [ " NSDebugDescription " ] as ? String else {
informativeText = error . localizedDescription
break
}
let localizedError = NSLocalizedString ( " This theme cannot be used because of data corruption in the Info.plist: %@. " , comment : " Decoding key missing " )
informativeText = NSString . localizedStringWithFormat ( localizedError as NSString , debugDescription ) as String
2021-09-21 04:43:12 +02:00
default :
informativeText = error . localizedDescription
}
} else {
informativeText = error . localizedDescription
}
2021-09-21 03:10:56 +02:00
DispatchQueue . main . async {
let alert = NSAlert ( )
alert . alertStyle = . warning
2021-09-21 03:22:45 +02:00
alert . messageText = NSLocalizedString ( " Theme Error " , comment : " Theme download error " )
2021-09-24 03:28:32 +02:00
alert . informativeText = informativeText
2021-09-23 14:12:35 +02:00
alert . addButton ( withTitle : NSLocalizedString ( " Open Theme Folder " , comment : " Open Theme Folder " ) )
2021-09-21 03:10:56 +02:00
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
2021-09-23 14:12:35 +02:00
let button = alert . buttons . first
button ? . target = self
button ? . action = #selector ( self . openThemesFolder ( _ : ) )
2021-09-24 03:28:32 +02:00
alert . buttons [ 0 ] . keyEquivalent = " \033 "
alert . buttons [ 1 ] . keyEquivalent = " \r "
2021-09-23 14:12:35 +02:00
alert . runModal ( )
}
}
@objc func openThemesFolder ( _ sender : Any ) {
if themeImportPath = = nil {
let url = URL ( fileURLWithPath : ArticleThemesManager . shared . folderPath )
NSWorkspace . shared . open ( url )
} else {
let url = URL ( fileURLWithPath : themeImportPath ! )
NSWorkspace . shared . open ( url . deletingLastPathComponent ( ) )
2021-09-21 03:10:56 +02:00
}
}
2017-11-14 22:18:25 +01:00
}
2018-02-08 09:11:52 +01:00
/*
the ScriptingAppDelegate protocol exposes a narrow set of accessors with
internal visibility which are very similar to some private vars .
These would be unnecessary if the similar accessors were marked internal rather than private ,
but for now , we ' ll keep the stratification of visibility
*/
extension AppDelegate : ScriptingAppDelegate {
internal var scriptingMainWindowController : ScriptingMainWindowController ? {
return mainWindowController
}
internal var scriptingCurrentArticle : Article ? {
return self . scriptingMainWindowController ? . scriptingCurrentArticle
}
internal var scriptingSelectedArticles : [ Article ] {
return self . scriptingMainWindowController ? . scriptingSelectedArticles ? ? [ ]
}
2019-10-20 09:28:00 +02:00
}
2020-03-03 02:46:31 +01:00
extension AppDelegate : NSWindowRestoration {
@objc static func restoreWindow ( withIdentifier identifier : NSUserInterfaceItemIdentifier , state : NSCoder , completionHandler : @ escaping ( NSWindow ? , Error ? ) -> Void ) {
var mainWindow : NSWindow ? = nil
if identifier . rawValue = = WindowRestorationIdentifiers . mainWindow {
2020-03-06 01:42:17 +01:00
mainWindow = appDelegate . createAndShowMainWindow ( ) . window
2020-03-03 02:46:31 +01:00
}
completionHandler ( mainWindow , nil )
}
}
2020-12-23 14:44:45 +01:00
// H a n d l e N o t i f i c a t i o n A c t i o n s
private extension AppDelegate {
func handleMarkAsRead ( userInfo : [ AnyHashable : Any ] ) {
guard let articlePathUserInfo = userInfo [ UserInfoKey . articlePath ] as ? [ AnyHashable : Any ] ,
let accountID = articlePathUserInfo [ ArticlePathKey . accountID ] as ? String ,
let articleID = articlePathUserInfo [ ArticlePathKey . articleID ] as ? String else {
return
}
let account = AccountManager . shared . existingAccount ( with : accountID )
guard account != nil else {
os_log ( . debug , " No account found from notification. " )
return
}
let article = try ? account ! . fetchArticles ( . articleIDs ( [ articleID ] ) )
guard article != nil else {
os_log ( . debug , " No article found from search using %@ " , articleID )
return
}
2021-04-13 02:46:59 +02:00
account ! . markArticles ( article ! , statusKey : . read , flag : true ) { _ in }
2020-12-23 14:44:45 +01:00
}
func handleMarkAsStarred ( userInfo : [ AnyHashable : Any ] ) {
guard let articlePathUserInfo = userInfo [ UserInfoKey . articlePath ] as ? [ AnyHashable : Any ] ,
let accountID = articlePathUserInfo [ ArticlePathKey . accountID ] as ? String ,
let articleID = articlePathUserInfo [ ArticlePathKey . articleID ] as ? String else {
return
}
let account = AccountManager . shared . existingAccount ( with : accountID )
guard account != nil else {
os_log ( . debug , " No account found from notification. " )
return
}
let article = try ? account ! . fetchArticles ( . articleIDs ( [ articleID ] ) )
guard article != nil else {
os_log ( . debug , " No article found from search using %@ " , articleID )
return
}
2021-04-13 02:46:59 +02:00
account ! . markArticles ( article ! , statusKey : . starred , flag : true ) { _ in }
2020-12-23 14:44:45 +01:00
}
}