2017-05-22 13:00:45 -07:00
//
// A p p D e l e g a t e . s w i f t
2018-08-28 22:18:24 -07:00
// N e t N e w s W i r e
2017-05-22 13:00:45 -07:00
//
2017-05-27 10:43:27 -07: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 13:00:45 -07:00
//
2018-02-02 22:51:32 -08:00
import AppKit
2019-10-02 19:42:16 -05:00
import UserNotifications
2018-07-23 18:29:08 -07:00
import Articles
2017-05-27 10:43:27 -07:00
import RSTree
2017-05-27 13:37:50 -07:00
import RSWeb
2017-09-17 17:12:42 -07:00
import Account
2017-11-13 18:33:23 -08:00
import RSCore
2020-07-29 05:11:57 -05:00
import RSCoreResources
2020-07-30 17:40:45 -05:00
import Secrets
2020-12-02 07:56:22 -06:00
import OSLog
2020-12-11 18:09:36 -06:00
import CrashReporter
2019-10-22 19:44:06 -04: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 16:42:17 -05:00
import Sparkle
#endif
2017-05-27 10:43:27 -07:00
2017-11-14 21:31:17 -08:00
var appDelegate : AppDelegate !
2017-05-22 13:00:45 -07:00
@NSApplicationMain
2019-10-22 19:44:06 -04:00
class AppDelegate : NSObject , NSApplicationDelegate , NSUserInterfaceValidations , UNUserNotificationCenterDelegate , UnreadCountProvider , SPUStandardUserDriverDelegate , SPUUpdaterDelegate
{
2017-05-22 13:00:45 -07:00
2020-03-02 17:46:31 -08:00
private struct WindowRestorationIdentifiers {
static let mainWindow = " mainWindow "
}
2019-10-02 19:42:16 -05:00
var userNotificationManager : UserNotificationManager !
2017-11-24 21:39:59 -08:00
var faviconDownloader : FaviconDownloader !
2017-11-25 20:12:53 -08:00
var imageDownloader : ImageDownloader !
2017-11-26 13:16:32 -08:00
var authorAvatarDownloader : AuthorAvatarDownloader !
2019-11-14 20:11:41 -06:00
var webFeedIconDownloader : WebFeedIconDownloader !
2020-08-13 18:03:39 -05:00
var extensionContainersFile : ExtensionContainersFile !
var extensionFeedAddRequestFile : ExtensionFeedAddRequestFile !
2017-11-24 21:39:59 -08:00
var appName : String !
2019-05-15 17:21:58 -05:00
var refreshTimer : AccountRefreshTimer ?
var syncTimer : ArticleStatusSyncTimer ?
2021-03-25 13:51:40 -05:00
var lastRefreshInterval = AppDefaults . shared . refreshInterval
2019-05-15 17:21:58 -05:00
2019-01-27 18:00:09 -08:00
var shuttingDown = false {
didSet {
if shuttingDown {
2019-04-23 11:20:44 -05:00
refreshTimer ? . shuttingDown = shuttingDown
refreshTimer ? . invalidate ( )
2019-05-15 17:21:58 -05:00
syncTimer ? . shuttingDown = shuttingDown
syncTimer ? . invalidate ( )
2019-01-27 18:00:09 -08:00
}
}
}
2021-01-09 17:58:51 -06:00
var isShutDownSyncDone = false
2018-01-27 11:32:59 -08:00
@IBOutlet var debugMenuItem : NSMenuItem !
2018-01-27 15:11:02 -08:00
@IBOutlet var sortByOldestArticleOnTopMenuItem : NSMenuItem !
@IBOutlet var sortByNewestArticleOnTopMenuItem : NSMenuItem !
2019-09-08 17:09:26 -05:00
@IBOutlet var groupArticlesByFeedMenuItem : NSMenuItem !
2018-12-09 12:12:55 -08:00
@IBOutlet var checkForUpdatesMenuItem : NSMenuItem !
2019-09-19 10:38:17 -05:00
2017-05-27 10:43:27 -07:00
var unreadCount = 0 {
didSet {
2017-10-05 18:12:58 -07:00
if unreadCount != oldValue {
2018-12-27 21:19:19 -08:00
CoalescingQueue . standard . add ( self , #selector ( updateDockBadge ) )
2017-11-19 13:57:42 -08:00
postUnreadCountDidChangeNotification ( )
2017-10-05 18:12:58 -07:00
}
2017-05-27 10:43:27 -07:00
}
}
2020-03-02 17:46:31 -08: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-24 21:39:59 -08:00
private var preferencesWindowController : NSWindowController ?
private var addFeedController : AddFeedController ?
private var addFolderWindowController : AddFolderWindowController ?
2019-05-01 16:04:56 -05:00
private var importOPMLController : ImportOPMLWindowController ?
private var exportOPMLController : ExportOPMLWindowController ?
2017-11-24 21:39:59 -08:00
private var keyboardShortcutsWindowController : WebViewWindowController ?
private var inspectorWindowController : InspectorWindowController ?
2018-12-29 12:31:39 -08:00
private var crashReportWindowController : CrashReportWindowController ? // F o r t e s t i n g o n l y
2019-09-06 09:13:09 -04:00
private let appMovementMonitor = RSAppMovementMonitor ( )
2019-10-22 19:33:00 -04:00
#if ! MAC_APP_STORE && ! TEST
private var softwareUpdater : SPUUpdater !
2020-12-11 18:09:36 -06:00
private var crashReporter : PLCrashReporter !
2019-10-22 19:33:00 -04:00
#endif
2021-09-23 20:12:35 +08:00
private var themeImportPath : String ?
2017-11-24 21:39:59 -08:00
2017-05-27 10:43:27 -07:00
override init ( ) {
NSWindow . allowsAutomaticWindowTabbing = false
super . init ( )
2017-10-18 18:37:45 -07:00
2020-12-11 18:09:36 -06:00
#if ! MAC_APP_STORE
let crashReporterConfig = PLCrashReporterConfig . defaultConfiguration ( )
crashReporter = PLCrashReporter ( configuration : crashReporterConfig )
crashReporter . enable ( )
#endif
2020-07-30 17:40:45 -05:00
SecretsManager . provider = Secrets ( )
2020-01-10 14:00:22 -06:00
AccountManager . shared = AccountManager ( accountsFolder : Platform . dataSubfolder ( forApplication : nil , folderName : " Accounts " ) ! )
2021-09-07 16:58:06 -05:00
ArticleThemesManager . shared = ArticleThemesManager ( folderPath : Platform . dataSubfolder ( forApplication : nil , folderName : " Themes " ) ! )
2020-04-16 15:06:56 -05:00
FeedProviderManager . shared . delegate = ExtensionPointManager . shared
2017-10-18 18:37:45 -07:00
NotificationCenter . default . addObserver ( self , selector : #selector ( unreadCountDidChange ( _ : ) ) , name : . UnreadCountDidChange , object : nil )
2019-02-17 18:46:28 -08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( inspectableObjectsDidChange ( _ : ) ) , name : . InspectableObjectsDidChange , object : nil )
2021-09-20 19:34:25 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( importDownloadedTheme ( _ : ) ) , name : . didEndDownloadingTheme , object : nil )
2021-09-21 09:22:45 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( themeImportError ( _ : ) ) , name : . didFailToImportThemeWithError , object : nil )
2020-02-26 11:29:59 -08:00
NSWorkspace . shared . notificationCenter . addObserver ( self , selector : #selector ( didWakeNotification ( _ : ) ) , name : NSWorkspace . didWakeNotification , object : nil )
2018-01-21 12:46:22 -08:00
2017-11-14 21:31:17 -08:00
appDelegate = self
2023-06-25 15:01:53 -07:00
if shouldShowTwitterDeprecationAlert ( ) {
showTwitterDeprecationAlert ( )
}
else if shouldShowRedditDeprecationAlert ( ) {
showRedditDeprecationAlert ( )
}
2017-11-14 21:31:17 -08:00
}
2017-11-15 13:13:40 -08:00
// MARK: - A P I
func showAddFolderSheetOnWindow ( _ window : NSWindow ) {
addFolderWindowController = AddFolderWindowController ( )
addFolderWindowController ! . runSheetOnWindow ( window )
}
2020-04-21 21:25:45 -05:00
func showAddWebFeedSheetOnWindow ( _ window : NSWindow , urlString : String ? , name : String ? , account : Account ? , folder : Folder ? ) {
2018-02-03 21:30:30 -08:00
addFeedController = AddFeedController ( hostWindow : window )
2020-04-21 21:25:45 -05:00
addFeedController ? . showAddFeedSheet ( . webFeed , urlString , name , account , folder )
2018-02-03 21:30:30 -08:00
}
2018-09-13 10:04:20 -05:00
2017-09-18 22:00:35 -07: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-17 22:11:35 -07:00
2018-11-26 19:22:05 -08:00
func applicationWillFinishLaunching ( _ notification : Notification ) {
installAppleEventHandlers ( )
2020-03-02 17:46:31 -08: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 15:40:40 -07:00
appName = ( Bundle . main . infoDictionary ! [ " CFBundleExecutable " ] ! as ! String )
2018-11-26 19:22:05 -08:00
}
2017-05-27 10:43:27 -07:00
func applicationDidFinishLaunching ( _ note : Notification ) {
2019-10-22 19:33:00 -04:00
#if MAC_APP_STORE || TEST
2018-12-09 12:12:55 -08:00
checkForUpdatesMenuItem . isHidden = true
2019-10-22 19:33:00 -04: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 12:12:55 -08:00
#endif
2020-07-02 11:17:38 +08:00
AppDefaults . shared . registerDefaults ( )
let isFirstRun = AppDefaults . shared . isFirstRun
2017-11-25 16:10:19 -08:00
if isFirstRun {
2020-12-02 07:56:22 -06:00
os_log ( . debug , " Is first run. " )
2017-11-25 16:10:19 -08:00
}
2019-05-01 05:53:18 -05:00
let localAccount = AccountManager . shared . defaultAccount
2019-10-17 13:25:11 -07: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 10:43:27 -07:00
2018-01-27 15:24:33 -08:00
updateSortMenuItems ( )
2019-09-08 17:09:26 -05:00
updateGroupByFeedMenuItem ( )
2020-03-05 17:42:17 -07:00
if mainWindowController = = nil {
let mainWindowController = createAndShowMainWindow ( )
mainWindowController . restoreStateFromUserDefaults ( )
}
2019-08-23 18:30:28 -07:00
if isFirstRun {
mainWindowController ? . window ? . center ( )
}
2017-10-18 18:37:45 -07:00
2019-11-14 20:11:41 -06:00
NotificationCenter . default . addObserver ( self , selector : #selector ( webFeedSettingDidChange ( _ : ) ) , name : . WebFeedSettingDidChange , object : nil )
2018-01-27 15:24:33 -08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( userDefaultsDidChange ( _ : ) ) , name : UserDefaults . didChangeNotification , object : nil )
2017-11-26 13:16:32 -08:00
2017-10-18 18:37:45 -07:00
DispatchQueue . main . async {
self . unreadCount = AccountManager . shared . unreadCount
}
2017-12-25 10:23:12 -08:00
2018-01-21 13:27:17 -08:00
if InspectorWindowController . shouldOpenAtStartup {
2018-01-21 13:30:26 -08:00
self . toggleInspectorWindow ( self )
2018-01-21 13:27:17 -08:00
}
2020-08-13 18:03:39 -05:00
extensionContainersFile = ExtensionContainersFile ( )
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile ( )
2019-05-15 17:21:58 -05:00
refreshTimer = AccountRefreshTimer ( )
syncTimer = ArticleStatusSyncTimer ( )
2019-04-24 14:46:01 -05:00
2020-08-21 12:51:53 -05:00
UNUserNotificationCenter . current ( ) . requestAuthorization ( options : [ . badge ] ) { ( granted , error ) in }
2020-07-27 13:54:34 -04:00
UNUserNotificationCenter . current ( ) . getNotificationSettings { ( settings ) in
if settings . authorizationStatus = = . authorized {
DispatchQueue . main . async {
NSApplication . shared . registerForRemoteNotifications ( )
}
}
}
2019-10-02 19:42:16 -05:00
UNUserNotificationCenter . current ( ) . delegate = self
userNotificationManager = UserNotificationManager ( )
2021-03-22 17:20:40 -05:00
#if DEBUG
refreshTimer ! . update ( )
syncTimer ! . update ( )
#else
2021-06-16 20:20:32 -05:00
if AppDefaults . shared . suppressSyncOnLaunch {
refreshTimer ! . update ( )
syncTimer ! . update ( )
} else {
DispatchQueue . main . async {
self . refreshTimer ! . timedRefresh ( nil )
self . syncTimer ! . timedRefresh ( nil )
}
2021-03-22 17:20:40 -05:00
}
#endif
2020-07-02 11:17:38 +08:00
if AppDefaults . shared . showDebugMenu {
2019-11-28 14:59:45 -06: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 11:32:59 -08:00
debugMenuItem . menu ? . removeItem ( debugMenuItem )
2019-11-28 14:59:45 -06:00
}
2019-01-27 18:00:09 -08:00
2019-01-11 23:18:48 -08:00
#if ! MAC_APP_STORE
2020-12-11 18:09:36 -06:00
DispatchQueue . main . async {
CrashReporter . check ( crashReporter : self . crashReporter )
}
2019-01-11 23:18:48 -08:00
#endif
2020-03-30 02:48:25 -05:00
2017-05-27 10:43:27 -07:00
}
2019-10-03 15:49:27 -05: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 10:43:27 -07:00
func applicationShouldHandleReopen ( _ sender : NSApplication , hasVisibleWindows flag : Bool ) -> Bool {
2019-01-27 20:25:09 -08: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 10:43:27 -07:00
return false
}
2019-01-27 18:00:09 -08:00
func applicationDidBecomeActive ( _ notification : Notification ) {
2020-02-26 11:29:59 -08:00
fireOldTimers ( )
2019-01-27 18:00:09 -08:00
}
2017-05-27 10:43:27 -07:00
func applicationDidResignActive ( _ notification : Notification ) {
2019-10-20 02:28:00 -05:00
ArticleStringFormatter . emptyCaches ( )
2018-01-21 13:27:17 -08:00
saveState ( )
}
2020-03-30 02:48:25 -05:00
func application ( _ application : NSApplication , didReceiveRemoteNotification userInfo : [ String : Any ] ) {
AccountManager . shared . receiveRemoteNotification ( userInfo : userInfo )
}
2021-09-08 00:28:13 -05:00
func application ( _ sender : NSApplication , openFile filename : String ) -> Bool {
2021-09-12 13:30:15 -05:00
guard filename . hasSuffix ( ArticleTheme . nnwThemeSuffix ) else { return false }
2021-09-21 10:43:12 +08:00
importTheme ( filename : filename )
2021-09-08 00:28:13 -05:00
return true
}
2018-01-21 13:27:17 -08:00
func applicationWillTerminate ( _ notification : Notification ) {
2019-01-27 18:00:09 -08:00
shuttingDown = true
2018-01-21 13:27:17 -08:00
saveState ( )
2021-01-09 17:58:51 -06:00
2021-09-21 09:10:56 +08:00
ArticleThemeDownloader . shared . cleanUp ( )
2021-01-18 17:48:07 -06:00
AccountManager . shared . sendArticleStatusAll ( ) {
2021-01-09 17:58:51 -06:00
self . isShutDownSyncDone = true
}
2021-01-18 17:48:07 -06:00
let timeout = Date ( ) . addingTimeInterval ( 2 )
2021-09-06 14:52:41 -05:00
while ! isShutDownSyncDone && RunLoop . current . run ( mode : . default , before : timeout ) && timeout > Date ( ) { }
2017-05-27 10:43:27 -07:00
}
// MARK: N o t i f i c a t i o n s
2017-10-18 18:37:45 -07:00
@objc func unreadCountDidChange ( _ note : Notification ) {
if note . object is AccountManager {
unreadCount = AccountManager . shared . unreadCount
}
2017-05-27 10:43:27 -07:00
}
2019-11-14 20:11:41 -06: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 13:16:32 -08:00
return
}
2019-11-14 20:11:41 -06:00
if key = = WebFeed . WebFeedSettingKey . homePageURL || key = = WebFeed . WebFeedSettingKey . faviconURL {
2019-03-17 13:54:30 -07:00
let _ = faviconDownloader . favicon ( for : feed )
}
2017-11-26 13:16:32 -08:00
}
2019-02-17 18:46:28 -08:00
@objc func inspectableObjectsDidChange ( _ note : Notification ) {
2018-01-21 12:46:22 -08:00
guard let inspectorWindowController = inspectorWindowController , inspectorWindowController . isOpen else {
return
}
inspectorWindowController . objects = objectsForInspector ( )
}
2018-01-27 15:24:33 -08:00
@objc func userDefaultsDidChange ( _ note : Notification ) {
updateSortMenuItems ( )
2019-09-08 17:09:26 -05:00
updateGroupByFeedMenuItem ( )
2021-03-25 13:51:40 -05:00
if lastRefreshInterval != AppDefaults . shared . refreshInterval {
refreshTimer ? . update ( )
lastRefreshInterval = AppDefaults . shared . refreshInterval
}
2019-09-03 22:17:31 -07:00
updateDockBadge ( )
2018-01-27 15:24:33 -08:00
}
2020-02-26 11:29:59 -08:00
@objc func didWakeNotification ( _ note : Notification ) {
fireOldTimers ( )
}
2021-09-20 19:34:25 +08: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 10:43:12 +08:00
self . importTheme ( filename : url . path )
2021-09-20 19:34:25 +08:00
}
}
2018-01-27 15:24:33 -08:00
2017-05-27 10:43:27 -07:00
// MARK: M a i n W i n d o w
2020-03-02 17:46:31 -08:00
func createMainWindowController ( ) -> MainWindowController {
2020-08-08 20:49:22 -05:00
let controller : MainWindowController
2020-08-10 11:43:18 -05:00
if #available ( macOS 11.0 , * ) {
2020-12-06 15:21:30 -06:00
controller = windowControllerWithName ( " UnifiedWindow " ) as ! MainWindowController
2020-08-08 20:49:22 -05:00
} else {
controller = windowControllerWithName ( " MainWindow " ) as ! MainWindowController
}
2020-03-05 17:42:17 -07:00
if ! ( mainWindowController ? . isOpen ? ? false ) {
mainWindowControllers . removeAll ( )
}
2020-03-02 17:46:31 -08:00
mainWindowControllers . append ( controller )
return controller
}
2017-05-27 10:43:27 -07:00
2020-03-02 17:46:31 -08:00
func windowControllerWithName ( _ storyboardName : String ) -> NSWindowController {
2018-12-09 12:32:33 -08:00
let storyboard = NSStoryboard ( name : NSStoryboard . Name ( storyboardName ) , bundle : nil )
2017-05-27 10:43:27 -07:00
return storyboard . instantiateInitialController ( ) ! as ! NSWindowController
}
2020-03-02 17:46:31 -08:00
@ discardableResult
2020-03-05 17:42:17 -07:00
func createAndShowMainWindow ( ) -> MainWindowController {
2020-03-02 17:46:31 -08:00
let controller = createMainWindowController ( )
controller . showWindow ( self )
if let window = controller . window {
window . restorationClass = Self . self
window . identifier = NSUserInterfaceItemIdentifier ( rawValue : WindowRestorationIdentifiers . mainWindow )
}
2020-03-05 17:42:17 -07:00
return controller
2020-03-02 17:46:31 -08:00
}
2017-05-27 10:43:27 -07:00
2020-03-02 17:46:31 -08:00
func createAndShowMainWindowIfNecessary ( ) {
2017-05-27 10:43:27 -07:00
if mainWindowController = = nil {
2020-03-02 17:46:31 -08:00
createAndShowMainWindow ( )
2020-03-05 17:42:17 -07:00
} else {
mainWindowController ? . showWindow ( self )
2017-05-27 10:43:27 -07:00
}
}
2020-03-02 18:06:55 -08:00
func removeMainWindow ( _ windowController : MainWindowController ) {
2020-03-05 17:42:17 -07:00
guard mainWindowControllers . count > 1 else { return }
2020-03-02 18:06:55 -08:00
if let index = mainWindowControllers . firstIndex ( of : windowController ) {
mainWindowControllers . remove ( at : index )
}
}
2017-05-27 10:43:27 -07: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-27 18:00:09 -08:00
if shuttingDown {
return false
}
2017-05-27 10:43:27 -07:00
2018-02-03 10:56:12 -08:00
let isDisplayingSheet = mainWindowController ? . isDisplayingSheet ? ? false
2020-08-11 20:19:17 -05:00
let isSpecialAccountAvailable = AccountManager . shared . activeAccounts . contains ( where : { $0 . type = = . onMyMac || $0 . type = = . cloudKit } )
2018-02-03 10:56:12 -08:00
2017-05-27 10:43:27 -07:00
if item . action = = #selector ( refreshAll ( _ : ) ) {
2019-05-19 14:23:54 -05:00
return ! AccountManager . shared . refreshInProgress && ! AccountManager . shared . activeAccounts . isEmpty
2017-05-27 10:43:27 -07:00
}
2020-10-18 20:32:10 -05:00
if item . action = = #selector ( importOPMLFromFile ( _ : ) ) {
return AccountManager . shared . activeAccounts . contains ( where : { ! $0 . behaviors . contains ( where : { $0 = = . disallowOPMLImports } ) } )
}
2017-05-27 10:43:27 -07:00
if item . action = = #selector ( addAppNews ( _ : ) ) {
2020-10-29 17:52:58 -05:00
return ! isDisplayingSheet && ! AccountManager . shared . anyAccountHasNetNewsWireNewsSubscription ( ) && ! AccountManager . shared . activeAccounts . isEmpty
2017-05-27 10:43:27 -07:00
}
2020-10-18 20:32:10 -05:00
2018-01-27 15:13:45 -08:00
if item . action = = #selector ( sortByNewestArticleOnTop ( _ : ) ) || item . action = = #selector ( sortByOldestArticleOnTop ( _ : ) ) {
return mainWindowController ? . isOpen ? ? false
}
2020-10-18 20:32:10 -05:00
2020-04-21 21:25:45 -05:00
if item . action = = #selector ( showAddWebFeedWindow ( _ : ) ) || item . action = = #selector ( showAddFolderWindow ( _ : ) ) {
2019-05-19 10:21:42 -05:00
return ! isDisplayingSheet && ! AccountManager . shared . activeAccounts . isEmpty
2018-02-03 10:56:12 -08:00
}
2020-10-18 20:32:10 -05:00
2019-09-19 11:17:58 -05:00
#if ! MAC_APP_STORE
2019-09-16 21:06:35 -05:00
if item . action = = #selector ( toggleWebInspectorEnabled ( _ : ) ) {
2020-07-02 11:17:38 +08:00
( item as ! NSMenuItem ) . state = AppDefaults . shared . webInspectorEnabled ? . on : . off
2019-09-16 21:06:35 -05:00
}
2019-09-19 10:38:17 -05:00
#endif
2020-10-18 20:32:10 -05:00
2017-05-27 10:43:27 -07:00
return true
}
2019-10-02 19:42:16 -05: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 11:39:48 -05:00
func userNotificationCenter ( _ center : UNUserNotificationCenter , didReceive response : UNNotificationResponse , withCompletionHandler completionHandler : @ escaping ( ) -> Void ) {
2020-12-23 21:44:45 +08: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 11:39:48 -05:00
completionHandler ( )
}
2017-05-27 10:43:27 -07:00
// MARK: A d d F e e d
2020-04-21 21:25:45 -05:00
func addWebFeed ( _ urlString : String ? , name : String ? = nil , account : Account ? = nil , folder : Folder ? = nil ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2018-02-03 10:56:12 -08:00
if mainWindowController ! . isDisplayingSheet {
return
}
2017-05-27 10:43:27 -07:00
2020-04-21 21:25:45 -05:00
showAddWebFeedSheetOnWindow ( mainWindowController ! . window ! , urlString : urlString , name : name , account : account , folder : folder )
2017-05-27 10:43:27 -07:00
}
2018-12-27 21:19:19 -08:00
// MARK: - D o c k B a d g e
@objc func updateDockBadge ( ) {
2021-03-14 14:06:09 -05:00
let label = unreadCount > 0 ? " \( unreadCount ) " : " "
2018-12-27 21:19:19 -08:00
NSApplication . shared . dockTile . badgeLabel = label
}
2017-09-23 12:17:14 -07:00
// MARK: - A c t i o n s
2018-02-11 18:58:50 -08:00
@IBAction func showPreferences ( _ sender : Any ? ) {
2017-05-27 10:43:27 -07:00
if preferencesWindowController = = nil {
preferencesWindowController = windowControllerWithName ( " Preferences " )
}
preferencesWindowController ! . showWindow ( self )
}
2020-03-02 17:46:31 -08:00
@IBAction func newMainWindow ( _ sender : Any ? ) {
2017-05-27 10:43:27 -07:00
createAndShowMainWindow ( )
}
2020-03-02 17:46:31 -08:00
@IBAction func showMainWindow ( _ sender : Any ? ) {
createAndShowMainWindowIfNecessary ( )
2020-03-04 18:22:15 -07:00
mainWindowController ? . window ? . makeKey ( )
2020-03-02 17:46:31 -08:00
}
2017-05-27 10:43:27 -07:00
2020-03-02 17:46:31 -08:00
@IBAction func refreshAll ( _ sender : Any ? ) {
2019-05-26 11:54:32 -05:00
AccountManager . shared . refreshAll ( errorHandler : ErrorHandler . present )
2017-05-27 10:43:27 -07:00
}
2020-04-21 21:25:45 -05:00
@IBAction func showAddWebFeedWindow ( _ sender : Any ? ) {
addWebFeed ( nil )
}
2018-02-11 18:58:50 -08:00
@IBAction func showAddFolderWindow ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2017-11-15 13:13:40 -08:00
showAddFolderSheetOnWindow ( mainWindowController ! . window ! )
2017-05-27 10:43:27 -07:00
}
2017-11-13 18:33:23 -08:00
@IBAction func showKeyboardShortcutsWindow ( _ sender : Any ? ) {
if keyboardShortcutsWindowController = = nil {
2020-03-02 17:46:31 -08:00
2017-11-13 18:33:23 -08: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-20 17:23:46 -08:00
2017-12-22 11:13:20 -08:00
if let window = keyboardShortcutsWindowController ? . window {
let point = NSPoint ( x : 128 , y : 64 )
2019-03-04 14:39:30 -06:00
let size = NSSize ( width : 620 , height : 1100 )
2017-12-22 11:13:20 -08:00
let minSize = NSSize ( width : 400 , height : 400 )
window . setPointAndSizeAdjustingForScreen ( point : point , size : size , minimumSize : minSize )
2017-12-20 17:23:46 -08:00
}
2020-03-02 17:46:31 -08:00
2017-11-13 18:33:23 -08:00
}
2017-12-20 17:23:46 -08:00
2017-11-13 18:33:23 -08:00
keyboardShortcutsWindowController ! . showWindow ( self )
}
2017-11-15 22:33:35 -08:00
@IBAction func toggleInspectorWindow ( _ sender : Any ? ) {
if inspectorWindowController = = nil {
2018-01-20 19:06:07 -08:00
inspectorWindowController = ( windowControllerWithName ( " Inspector " ) as ! InspectorWindowController )
2017-11-15 22:33:35 -08:00
}
if inspectorWindowController ! . isOpen {
inspectorWindowController ! . window ! . performClose ( self )
}
else {
2018-01-21 12:46:22 -08:00
inspectorWindowController ! . objects = objectsForInspector ( )
2017-11-15 22:33:35 -08:00
inspectorWindowController ! . showWindow ( self )
}
}
2018-02-11 18:58:50 -08:00
@IBAction func importOPMLFromFile ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2019-05-01 16:04:56 -05:00
if mainWindowController ! . isDisplayingSheet {
return
2017-05-27 10:43:27 -07:00
}
2019-05-01 16:04:56 -05:00
importOPMLController = ImportOPMLWindowController ( )
importOPMLController ? . runSheetOnWindow ( mainWindowController ! . window ! )
2019-10-14 20:45:58 -05:00
}
@IBAction func importNNW3FromFile ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2019-10-14 20:45:58 -05:00
if mainWindowController ! . isDisplayingSheet {
return
}
2019-10-17 13:25:11 -07:00
NNW3ImportController . askUserToImportNNW3Subscriptions ( window : mainWindowController ! . window ! )
2017-05-27 10:43:27 -07:00
}
2017-09-23 12:17:14 -07:00
2018-02-11 18:58:50 -08:00
@IBAction func exportOPML ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2019-05-01 16:04:56 -05:00
if mainWindowController ! . isDisplayingSheet {
return
2017-05-27 10:43:27 -07:00
}
2019-05-01 16:04:56 -05:00
exportOPMLController = ExportOPMLWindowController ( )
exportOPMLController ? . runSheetOnWindow ( mainWindowController ! . window ! )
2017-05-27 10:43:27 -07:00
}
2017-09-23 12:17:14 -07:00
2018-02-11 18:58:50 -08:00
@IBAction func addAppNews ( _ sender : Any ? ) {
2020-10-29 17:52:58 -05:00
if AccountManager . shared . anyAccountHasNetNewsWireNewsSubscription ( ) {
2017-05-27 10:43:27 -07:00
return
}
2020-10-29 17:52:58 -05:00
addWebFeed ( AccountManager . netNewsWireNewsURL , name : " NetNewsWire News " )
2017-05-27 10:43:27 -07:00
}
2017-05-27 13:37:50 -07:00
2018-02-11 18:58:50 -08:00
@IBAction func openWebsite ( _ sender : Any ? ) {
2017-05-27 13:37:50 -07:00
2021-02-21 18:00:40 -08:00
Browser . open ( " https://netnewswire.com/ " , inBackground : false )
2017-05-27 13:37:50 -07:00
}
2020-08-15 15:01:00 +08:00
@IBAction func openReleaseNotes ( _ sender : Any ? ) {
Browser . open ( URL . releaseNotes . absoluteString , inBackground : false )
}
2017-05-27 13:37:50 -07:00
2019-06-14 17:38:00 -05:00
@IBAction func openHowToSupport ( _ sender : Any ? ) {
2020-07-26 05:46:48 -04:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown " , inBackground : false )
2019-06-14 17:38:00 -05:00
}
2018-02-11 18:58:50 -08:00
@IBAction func openRepository ( _ sender : Any ? ) {
2017-05-27 13:37:50 -07:00
2018-08-28 22:18:24 -07:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire " , inBackground : false )
2017-05-27 13:37:50 -07:00
}
2018-02-11 18:58:50 -08:00
@IBAction func openBugTracker ( _ sender : Any ? ) {
2017-05-27 13:37:50 -07:00
2018-08-28 22:18:24 -07:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/issues " , inBackground : false )
2017-05-27 13:37:50 -07:00
}
2019-05-25 10:21:25 -07:00
@IBAction func openSlackGroup ( _ sender : Any ? ) {
2021-02-21 18:00:40 -08:00
Browser . open ( " https://netnewswire.com/slack " , inBackground : false )
2019-05-25 10:21:25 -07:00
}
2017-12-20 21:23:48 -08:00
@IBAction func openTechnotes ( _ sender : Any ? ) {
2020-07-26 05:46:48 -04:00
Browser . open ( " https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes " , inBackground : false )
2017-12-20 21:23:48 -08:00
}
2018-02-11 18:58:50 -08:00
@IBAction func showHelp ( _ sender : Any ? ) {
2017-05-27 13:37:50 -07:00
2023-06-25 15:01:53 -07:00
Browser . open ( " https://netnewswire.com/help/mac/6.1/en/ " , inBackground : false )
2017-05-27 13:37:50 -07:00
}
2017-11-16 18:23:07 -08:00
2018-02-12 13:10:13 -08:00
@IBAction func donateToAppCampForGirls ( _ sender : Any ? ) {
Browser . open ( " https://appcamp4girls.com/contribute/ " , inBackground : false )
}
2018-12-29 13:59:14 -08:00
@IBAction func showPrivacyPolicy ( _ sender : Any ? ) {
2021-02-21 18:00:40 -08:00
Browser . open ( " https://netnewswire.com/privacypolicy " , inBackground : false )
2018-12-29 13:59:14 -08:00
}
2018-01-27 12:39:07 -08:00
@IBAction func gotoToday ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 12:39:07 -08:00
mainWindowController ! . gotoToday ( sender )
}
@IBAction func gotoAllUnread ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 12:39:07 -08:00
mainWindowController ! . gotoAllUnread ( sender )
}
@IBAction func gotoStarred ( _ sender : Any ? ) {
2020-03-02 17:46:31 -08:00
createAndShowMainWindowIfNecessary ( )
2018-01-27 12:39:07 -08:00
mainWindowController ! . gotoStarred ( sender )
}
2018-01-27 15:11:02 -08:00
@IBAction func sortByOldestArticleOnTop ( _ sender : Any ? ) {
2020-07-02 11:17:38 +08:00
AppDefaults . shared . timelineSortDirection = . orderedAscending
2018-01-27 15:11:02 -08:00
}
@IBAction func sortByNewestArticleOnTop ( _ sender : Any ? ) {
2020-07-02 11:17:38 +08:00
AppDefaults . shared . timelineSortDirection = . orderedDescending
2018-01-27 15:11:02 -08:00
}
2019-09-08 17:09:26 -05:00
@IBAction func groupByFeedToggled ( _ sender : NSMenuItem ) {
2020-07-02 11:17:38 +08:00
AppDefaults . shared . timelineGroupByFeed . toggle ( )
2019-09-08 17:09:26 -05:00
}
2019-10-22 19:33:00 -04:00
@IBAction func checkForUpdates ( _ sender : Any ? ) {
#if ! MAC_APP_STORE && ! TEST
self . softwareUpdater . checkForUpdates ( )
#endif
}
2017-05-27 10:43:27 -07:00
}
2019-02-18 22:29:43 -08:00
// MARK: - D e b u g M e n u
extension AppDelegate {
@IBAction func debugSearch ( _ sender : Any ? ) {
2019-05-01 05:53:18 -05:00
AccountManager . shared . defaultAccount . debugRunSearch ( )
2019-02-18 22:29:43 -08:00
}
2019-09-16 21:06:35 -05:00
2020-05-15 17:29:24 -05: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 14:44:58 -08:00
CrashReporter . sendCrashLogText ( " This is a test. Hi, Brent. " )
}
@IBAction func forceCrash ( _ sender : Any ? ) {
fatalError ( " This is a deliberate crash. " )
2020-05-15 17:29:24 -05: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-16 21:06:35 -05:00
@IBAction func toggleWebInspectorEnabled ( _ sender : Any ? ) {
2019-09-19 11:17:58 -05:00
#if ! MAC_APP_STORE
2020-07-02 11:17:38 +08:00
let newValue = ! AppDefaults . shared . webInspectorEnabled
AppDefaults . shared . webInspectorEnabled = newValue
2019-09-17 13:58:45 -05:00
2019-09-19 10:38:17 -05: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 11:17:38 +08:00
AppDefaults . shared . webInspectorStartsAttached = false
2019-09-19 10:38:17 -05:00
NotificationCenter . default . post ( name : . WebInspectorEnabledDidChange , object : newValue )
#endif
2019-09-16 21:06:35 -05:00
}
2020-05-15 17:29:24 -05:00
2019-02-18 22:29:43 -08:00
}
2021-09-19 21:18:23 +08:00
internal extension AppDelegate {
2017-05-22 13:00:45 -07:00
2020-02-26 11:29:59 -08: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 12:46:22 -08: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 13:27:17 -08:00
func saveState ( ) {
2020-03-05 17:42:17 -07:00
mainWindowController ? . saveStateToUserDefaults ( )
2018-02-12 13:31:43 -08:00
inspectorWindowController ? . saveState ( )
2018-01-21 13:27:17 -08:00
}
2018-01-27 15:24:33 -08:00
func updateSortMenuItems ( ) {
2020-07-02 11:17:38 +08:00
let sortByNewestOnTop = AppDefaults . shared . timelineSortDirection = = . orderedDescending
2018-01-27 15:24:33 -08:00
sortByNewestArticleOnTopMenuItem . state = sortByNewestOnTop ? . on : . off
sortByOldestArticleOnTopMenuItem . state = sortByNewestOnTop ? . off : . on
}
2019-09-08 17:09:26 -05:00
func updateGroupByFeedMenuItem ( ) {
2020-07-02 11:17:38 +08:00
let groupByFeedEnabled = AppDefaults . shared . timelineGroupByFeed
2019-09-08 17:09:26 -05:00
groupArticlesByFeedMenuItem . state = groupByFeedEnabled ? . on : . off
}
2021-09-08 00:28:13 -05:00
2021-09-21 09:22:45 +08:00
func importTheme ( filename : String ) {
2021-09-08 00:28:13 -05:00
guard let window = mainWindowController ? . window else { return }
2021-09-21 09:22:45 +08:00
do {
2022-02-07 16:23:08 -08:00
let theme = try ArticleTheme ( path : filename , isAppTheme : false )
2021-09-21 09:22:45 +08:00
let alert = NSAlert ( )
alert . alertStyle = . informational
2021-09-08 00:28:13 -05:00
2021-09-21 09:22:45 +08: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 00:28:13 -05:00
2021-09-21 09:22:45 +08:00
let websiteText = NSMutableAttributedString ( )
2023-06-25 15:01:53 -07:00
websiteText . append ( NSAttributedString ( string : NSLocalizedString ( " Author‘ s website: " , comment : " Author's Website " ) , attributes : attrs ) )
2021-09-09 01:53:40 -05:00
2021-09-21 09:22:45 +08:00
if #available ( macOS 11.0 , * ) {
websiteText . append ( NSAttributedString ( string : " \n " ) )
} else {
websiteText . append ( NSAttributedString ( string : " " ) )
}
2021-09-08 00:28:13 -05:00
2021-09-21 09:22:45 +08:00
attrs [ . link ] = theme . creatorHomePage
websiteText . append ( NSAttributedString ( string : theme . creatorHomePage , attributes : attrs ) )
2021-09-08 00:28:13 -05:00
2021-09-21 09:22:45 +08:00
let textViewWidth : CGFloat
if #available ( macOS 11.0 , * ) {
textViewWidth = 200
} else {
textViewWidth = 400
}
2021-09-09 02:08:47 -05:00
2021-09-21 09:22:45 +08: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 00:28:13 -05:00
2021-09-21 09:22:45 +08: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 06:03:28 -05:00
}
2021-09-21 09:22:45 +08: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 06:03:28 -05:00
}
2021-09-21 09:22:45 +08:00
} else {
importTheme ( )
2021-09-09 06:03:28 -05:00
}
2021-09-21 09:22:45 +08:00
}
2021-09-08 00:28:13 -05:00
}
2021-09-21 09:22:45 +08:00
} catch {
2021-09-23 20:12:35 +08:00
NotificationCenter . default . post ( name : . didFailToImportThemeWithError , object : nil , userInfo : [ " error " : error , " path " : filename ] )
2021-09-08 00:28:13 -05:00
}
}
2021-09-08 05:47:57 -05: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 05:49:02 -05:00
let localizedInformativeText = NSLocalizedString ( " The theme “%@” has been installed. " , comment : " Theme installed " )
2021-09-08 05:47:57 -05:00
alert . informativeText = NSString . localizedStringWithFormat ( localizedInformativeText as NSString , themeName ) as String
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
2021-09-24 09:28:32 +08:00
2021-09-08 05:47:57 -05:00
alert . beginSheetModal ( for : window )
}
2021-09-21 09:10:56 +08:00
@objc func themeImportError ( _ note : Notification ) {
guard let userInfo = note . userInfo ,
2021-09-24 09:28:32 +08:00
let error = userInfo [ " error " ] as ? Error else {
2021-09-21 09:10:56 +08:00
return
}
2021-09-23 20:12:35 +08:00
themeImportPath = userInfo [ " path " ] as ? String
2021-09-21 10:43:12 +08:00
var informativeText : String = " "
if let decodingError = error as ? DecodingError {
switch decodingError {
case . typeMismatch ( let type , _ ) :
2021-09-24 09:28:32 +08: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 10:43:12 +08:00
case . valueNotFound ( let value , _ ) :
2021-09-24 09:28:32 +08: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 10:43:12 +08:00
case . keyNotFound ( let codingKey , _ ) :
2021-09-24 09:28:32 +08: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 ) :
2021-10-08 22:32:56 -05:00
guard let underlyingError = context . underlyingError as NSError ? ,
let debugDescription = underlyingError . userInfo [ " NSDebugDescription " ] as ? String else {
2021-09-24 09:28:32 +08:00
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 10:43:12 +08:00
default :
informativeText = error . localizedDescription
}
} else {
informativeText = error . localizedDescription
}
2021-09-21 09:10:56 +08:00
DispatchQueue . main . async {
let alert = NSAlert ( )
alert . alertStyle = . warning
2021-09-21 09:22:45 +08:00
alert . messageText = NSLocalizedString ( " Theme Error " , comment : " Theme download error " )
2021-09-24 09:28:32 +08:00
alert . informativeText = informativeText
2021-09-23 20:12:35 +08:00
alert . addButton ( withTitle : NSLocalizedString ( " Open Theme Folder " , comment : " Open Theme Folder " ) )
2021-09-21 09:10:56 +08:00
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
2021-09-23 20:12:35 +08:00
let button = alert . buttons . first
button ? . target = self
button ? . action = #selector ( self . openThemesFolder ( _ : ) )
2021-09-24 09:28:32 +08:00
alert . buttons [ 0 ] . keyEquivalent = " \033 "
alert . buttons [ 1 ] . keyEquivalent = " \r "
2021-09-23 20:12:35 +08:00
alert . runModal ( )
}
}
2023-06-25 15:01:53 -07:00
private func shouldShowTwitterDeprecationAlert ( ) -> Bool {
if AppDefaults . shared . twitterDeprecationAlertShown { return false }
let expiryDate = Date ( timeIntervalSince1970 : 1691539200 ) // A u g u s t 9 t h 2 0 2 3 , 0 0 : 0 0 U T C
let currentDate = Date ( )
if currentDate > expiryDate {
return false // I f a f t e r A u g u s t 9 t h , d o n ' t s h o w
}
return AccountManager . shared . anyLocalOriCloudAccountHasAtLeastOneTwitterFeed ( )
}
private func showTwitterDeprecationAlert ( ) {
assert ( shouldShowTwitterDeprecationAlert ( ) )
AppDefaults . shared . twitterDeprecationAlertShown = true
DispatchQueue . main . async {
let alert = NSAlert ( )
alert . alertStyle = . warning
alert . messageText = NSLocalizedString ( " Twitter Integration Removed " , comment : " Twitter Integration Removed " )
alert . informativeText = NSLocalizedString ( " Twitter has ended free access to the parts of the Twitter API that we need. \n \n Since Twitter does not provide RSS feeds, we’ ve had to use the Twitter API. Without free access to that API, we can’ t read feeds from Twitter. \n \n We’ ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’ t delete those feeds. \n \n You can still read whatever you have already downloaded. However, those feeds will no longer update. " , comment : " Twitter deprecation informative text. " )
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
alert . buttons [ 0 ] . keyEquivalent = " \r "
alert . runModal ( )
}
}
private func shouldShowRedditDeprecationAlert ( ) -> Bool {
if AppDefaults . shared . redditDeprecationAlertShown { return false }
let expiryDate = Date ( timeIntervalSince1970 : 1701331200 ) // T h u N o v 3 0 2 0 2 3 0 0 : 0 0 : 0 0 G M T - 0 8 0 0 ( P a c i f i c S t a n d a r d T i m e )
let currentDate = Date ( )
if currentDate > expiryDate {
return false
}
return AccountManager . shared . anyLocalOriCloudAccountHasAtLeastOneRedditAPIFeed ( )
}
private func showRedditDeprecationAlert ( ) {
assert ( shouldShowRedditDeprecationAlert ( ) )
AppDefaults . shared . redditDeprecationAlertShown = true
DispatchQueue . main . async {
let alert = NSAlert ( )
alert . alertStyle = . warning
alert . messageText = NSLocalizedString ( " Reddit API Integration Removed " , comment : " Reddit API Integration Removed " )
alert . informativeText = NSLocalizedString ( " Reddit has ended free access to their API. \n \n Though Reddit does provide RSS feeds, we used the Reddit API to get more and better data. But, without free access to that API, we have had to stop using it. \n \n We’ ve left your Reddit feeds intact. If you have any starred items from those feeds, they will remain as long as you don’ t delete those feeds. \n \n You can still read whatever you have already downloaded. \n \n Also, importantly — Reddit still provides RSS feeds, and you can follow Reddit activity through RSS. " , comment : " Reddit deprecation message " )
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " OK " ) )
alert . buttons [ 0 ] . keyEquivalent = " \r "
alert . runModal ( )
}
}
2021-09-23 20:12:35 +08:00
@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 09:10:56 +08:00
}
}
2017-11-14 13:18:25 -08:00
}
2018-02-08 00:11:52 -08: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 02:28:00 -05:00
}
2020-03-02 17:46:31 -08: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-05 17:42:17 -07:00
mainWindow = appDelegate . createAndShowMainWindow ( ) . window
2020-03-02 17:46:31 -08:00
}
completionHandler ( mainWindow , nil )
}
}
2020-12-23 21:44:45 +08: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-12 19:46:59 -05:00
account ! . markArticles ( article ! , statusKey : . read , flag : true ) { _ in }
2020-12-23 21:44:45 +08: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-12 19:46:59 -05:00
account ! . markArticles ( article ! , statusKey : . starred , flag : true ) { _ in }
2020-12-23 21:44:45 +08:00
}
}