From d168658e05c308fe5379acb6cd58bc8b71c59127 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 08:25:40 -0500 Subject: [PATCH] Add Extension Point maintenance to the iOS app. --- .../Twitter/TwitterFeedProvider.swift | 5 + NetNewsWire.xcodeproj/project.pbxproj | 38 ++- Shared/ExtensionPoints/ExtensionPoint.swift | 14 +- .../ExtensionPointIdentifer.swift | 10 + .../ExtensionPointManager.swift | 4 + .../TwitterFeedProvider-Extensions.swift | 8 +- Shared/Extensions/CacheCleaner.swift | 3 +- iOS/Account/Account.storyboard | 2 +- .../FeedWranglerAccountViewController.swift | 1 + .../FeedbinAccountViewController.swift | 1 + .../NewsBlurAccountViewController.swift | 1 + iOS/AppAssets.swift | 4 + iOS/AppDefaults.swift | 10 + .../ExtensionInspectorViewController.swift | 63 +++++ iOS/Inspector/Inspector.storyboard | 103 +++++++- .../Contents.json | 15 ++ .../twitter.pdf | Bin 0 -> 4237 bytes iOS/Settings/AddAccountViewController.swift | 26 +- .../AddExtensionPointViewController.swift | 59 +++++ iOS/Settings/AddExtensionViewContrller.swift | 59 +++++ .../EnableExtensionPointViewController.swift | 132 ++++++++++ .../EnableExtensionViewController.swift | 132 ++++++++++ iOS/Settings/Settings.storyboard | 236 ++++++++++++++++-- ...swift => SettingsComboTableViewCell.swift} | 10 +- ...ell.xib => SettingsComboTableViewCell.xib} | 10 +- iOS/Settings/SettingsViewController.swift | 48 +++- 26 files changed, 906 insertions(+), 88 deletions(-) create mode 100644 iOS/Inspector/ExtensionInspectorViewController.swift create mode 100644 iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json create mode 100644 iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf create mode 100644 iOS/Settings/AddExtensionPointViewController.swift create mode 100644 iOS/Settings/AddExtensionViewContrller.swift create mode 100644 iOS/Settings/EnableExtensionPointViewController.swift create mode 100644 iOS/Settings/EnableExtensionViewController.swift rename iOS/Settings/{SettingsAccountTableViewCell.swift => SettingsComboTableViewCell.swift} (61%) rename iOS/Settings/{SettingsAccountTableViewCell.xib => SettingsComboTableViewCell.xib} (90%) diff --git a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift index 1905eae83..f71d43f87 100644 --- a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -132,6 +132,11 @@ private extension TwitterFeedProvider { // TODO: Update to retrieve the full user func fetchIconURL(screenName: String, completion: @escaping (Result) -> Void) { + guard screenName != "search" else { + completion(.failure(TwitterFeedProviderError.unknown)) + return + } + let url = "\(Self.apiBase)users/show.json" let parameters = ["screen_name": screenName] diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 4602adc46..dc73b5941 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -140,8 +140,6 @@ 515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; 515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 515A517E243E90260089E588 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; - 515A517F243E90260089E588 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; 515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; @@ -152,8 +150,8 @@ 51627A6923861DED007B3B4B /* MasterFeedViewController+Drop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */; }; 51627A6B238629D8007B3B4B /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */; }; 51627A93238A3836007B3B4B /* CroppingPreviewParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A92238A3836007B3B4B /* CroppingPreviewParameters.swift */; }; - 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */; }; - 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */; }; + 516A093723609A3600EAE89B /* SettingsComboTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A091D23609A3600EAE89B /* SettingsComboTableViewCell.xib */; }; + 516A09392360A2AE00EAE89B /* SettingsComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516A09382360A2AE00EAE89B /* SettingsComboTableViewCell.swift */; }; 516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */; }; 516A09402361240900EAE89B /* Account.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A093F2361240900EAE89B /* Account.storyboard */; }; 516A09422361248000EAE89B /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A09412361248000EAE89B /* Inspector.storyboard */; }; @@ -189,6 +187,11 @@ 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; + 519ED43F24482629007F8E94 /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; + 519ED44024482629007F8E94 /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; }; + 519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; }; + 519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; }; 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */; }; @@ -1277,6 +1280,7 @@ 51C451F92264C83E00C03939 /* Account.framework in Embed Frameworks */, 51C451F12264C83100C03939 /* ArticlesDatabase.framework in Embed Frameworks */, 517A757C24451C1500B553B9 /* OAuthSwift.framework in Embed Frameworks */, + 519ED44024482629007F8E94 /* FeedProvider.framework in Embed Frameworks */, 51C451F52264C83900C03939 /* Articles.framework in Embed Frameworks */, 51C451E92264C81000C03939 /* RSDatabase.framework in Embed Frameworks */, 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */, @@ -1463,8 +1467,8 @@ 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drop.swift"; sourceTree = ""; }; 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; }; 51627A92238A3836007B3B4B /* CroppingPreviewParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CroppingPreviewParameters.swift; sourceTree = ""; }; - 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsAccountTableViewCell.xib; sourceTree = ""; }; - 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountTableViewCell.swift; sourceTree = ""; }; + 516A091D23609A3600EAE89B /* SettingsComboTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsComboTableViewCell.xib; sourceTree = ""; }; + 516A09382360A2AE00EAE89B /* SettingsComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsComboTableViewCell.swift; sourceTree = ""; }; 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; 516A093F2361240900EAE89B /* Account.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Account.storyboard; sourceTree = ""; }; 516A09412361248000EAE89B /* Inspector.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = ""; }; @@ -1490,6 +1494,9 @@ 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = ""; }; + 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointViewController.swift; sourceTree = ""; }; + 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInspectorViewController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; 51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = ""; }; @@ -1885,6 +1892,7 @@ 51C451E82264C81000C03939 /* RSDatabase.framework in Frameworks */, 51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */, 51C451EC2264C81B00C03939 /* RSCore.framework in Frameworks */, + 519ED43F24482629007F8E94 /* FeedProvider.framework in Frameworks */, 51554C30228B71A10055115A /* SyncDatabase.framework in Frameworks */, 51C451E42264C80600C03939 /* RSParser.framework in Frameworks */, ); @@ -1982,8 +1990,9 @@ children = ( 516A09412361248000EAE89B /* Inspector.storyboard */, 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */, - 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */, + 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */, 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */, + 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */, ); path = Inspector; sourceTree = ""; @@ -2111,9 +2120,11 @@ 51A16990235E10D600EB091F /* Settings.storyboard */, 51A16995235E10D600EB091F /* AboutViewController.swift */, 51A16992235E10D600EB091F /* AddAccountViewController.swift */, + 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */, + 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */, 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */, - 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */, - 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */, + 516A09382360A2AE00EAE89B /* SettingsComboTableViewCell.swift */, + 516A091D23609A3600EAE89B /* SettingsComboTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, 51A16993235E10D600EB091F /* SettingsViewController.swift */, 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */, @@ -3824,7 +3835,7 @@ 511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */, 84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */, 51BB7C312335ACDE008E8144 /* page.html in Resources */, - 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */, + 516A093723609A3600EAE89B /* SettingsComboTableViewCell.xib in Resources */, 51F85BF32272531500C787DC /* Dedication.rtf in Resources */, 516A09422361248000EAE89B /* Inspector.storyboard in Resources */, 5103A9B424216A4200410853 /* blank.html in Resources */, @@ -4254,6 +4265,7 @@ 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* WebViewProvider.swift in Sources */, 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */, + 519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, @@ -4261,7 +4273,7 @@ 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */, - 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */, + 516A09392360A2AE00EAE89B /* SettingsComboTableViewCell.swift in Sources */, 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */, @@ -4286,6 +4298,7 @@ 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, + 519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */, 51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */, @@ -4358,11 +4371,11 @@ 51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */, C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */, 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */, - 515A517E243E90260089E588 /* SendToMarsEditCommand.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */, + 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, @@ -4372,7 +4385,6 @@ 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */, 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, 51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */, - 515A517F243E90260089E588 /* SendToMicroBlogCommand.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */, 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */, diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift index 8d8355b30..86a84557d 100644 --- a/Shared/ExtensionPoints/ExtensionPoint.swift +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -6,7 +6,11 @@ // Copyright © 2020 Ranchero Software. All rights reserved. // -import Foundation +#if os(macOS) +import AppKit +#else +import UIKit +#endif import RSCore protocol ExtensionPoint { @@ -35,11 +39,19 @@ extension ExtensionPoint { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center + #if os(macOS) let attrs = [ NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: NSFont.systemFont(ofSize: NSFont.systemFontSize), NSAttributedString.Key.foregroundColor: NSColor.textColor ] + #else + let attrs = [ + NSAttributedString.Key.paragraphStyle: paragraphStyle, + NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body), + NSAttributedString.Key.foregroundColor: UIColor.label + ] + #endif return NSMutableAttributedString(string: text, attributes: attrs) } diff --git a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift index 97c471656..0ecbe5ff7 100644 --- a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift +++ b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift @@ -11,16 +11,20 @@ import FeedProvider import RSCore enum ExtensionPointIdentifer: Hashable { + #if os(macOS) case marsEdit case microblog + #endif case twitter(String, String) var extensionPointType: ExtensionPoint.Type { switch self { + #if os(macOS) case .marsEdit: return SendToMarsEditCommand.self case .microblog: return SendToMicroBlogCommand.self + #endif case .twitter: return TwitterFeedProvider.self } @@ -28,6 +32,7 @@ enum ExtensionPointIdentifer: Hashable { public var userInfo: [AnyHashable: AnyHashable] { switch self { + #if os(macOS) case .marsEdit: return [ "type": "marsEdit" @@ -36,6 +41,7 @@ enum ExtensionPointIdentifer: Hashable { return [ "type": "microblog" ] + #endif case .twitter(let userID, let screenName): return [ "type": "twitter", @@ -49,10 +55,12 @@ enum ExtensionPointIdentifer: Hashable { guard let type = userInfo["type"] as? String else { return nil } switch type { + #if os(macOS) case "marsEdit": self = ExtensionPointIdentifer.marsEdit case "microblog": self = ExtensionPointIdentifer.microblog + #endif case "twitter": guard let userID = userInfo["userID"] as? String, let screenName = userInfo["screenName"] as? String else { return nil } self = ExtensionPointIdentifer.twitter(userID, screenName) @@ -63,10 +71,12 @@ enum ExtensionPointIdentifer: Hashable { public func hash(into hasher: inout Hasher) { switch self { + #if os(macOS) case .marsEdit: hasher.combine("marsEdit") case .microblog: hasher.combine("microblog") + #endif case .twitter(let userID, let screenName): hasher.combine("twitter") hasher.combine(userID) diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index 6a9467365..4cc3b7a41 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -103,10 +103,12 @@ private extension ExtensionPointManager { func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?) -> ExtensionPoint? { switch extensionPointType { + #if os(macOS) case is SendToMarsEditCommand.Type: return SendToMarsEditCommand() case is SendToMicroBlogCommand.Type: return SendToMicroBlogCommand() + #endif case is TwitterFeedProvider.Type: if let tokenSuccess = tokenSuccess { return TwitterFeedProvider(tokenSuccess: tokenSuccess) @@ -121,10 +123,12 @@ private extension ExtensionPointManager { func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? { switch extensionPointID { + #if os(macOS) case .marsEdit: return SendToMarsEditCommand() case .microblog: return SendToMicroBlogCommand() + #endif case .twitter(let userID, let screenName): return TwitterFeedProvider(userID: userID, screenName: screenName) } diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift index 92699be06..eee2b444a 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift @@ -18,13 +18,7 @@ extension TwitterFeedProvider: ExtensionPoint { static var title = NSLocalizedString("Twitter", comment: "Twitter") static var templateImage = AppAssets.extensionPointTwitter static var description: NSAttributedString = { - let attrString = TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds.") - let range = NSRange(location: 43, length: 7) - attrString.beginEditing() - attrString.addAttribute(NSAttributedString.Key.link, value: "https://twitter.com", range: range) - attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range) - attrString.endEditing() - return attrString + return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds.") }() var extensionPointID: ExtensionPointIdentifer { diff --git a/Shared/Extensions/CacheCleaner.swift b/Shared/Extensions/CacheCleaner.swift index 9f36787cd..5c629052f 100644 --- a/Shared/Extensions/CacheCleaner.swift +++ b/Shared/Extensions/CacheCleaner.swift @@ -28,10 +28,11 @@ struct CacheCleaner { let tempDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let faviconsFolderURL = tempDir.appendingPathComponent("Favicons") let imagesFolderURL = tempDir.appendingPathComponent("Images") + let feedURLToIconURL = tempDir.appendingPathComponent("FeedURLToIconURLCache.plist") let homePageToIconURL = tempDir.appendingPathComponent("HomePageToIconURLCache.plist") let homePagesWithNoIconURL = tempDir.appendingPathComponent("HomePagesWithNoIconURLCache.plist") - for tempItem in [faviconsFolderURL, imagesFolderURL, homePageToIconURL, homePagesWithNoIconURL] { + for tempItem in [faviconsFolderURL, imagesFolderURL, feedURLToIconURL, homePageToIconURL, homePagesWithNoIconURL] { do { os_log(.info, log: self.log, "Removing cache file: %@", tempItem.absoluteString) try FileManager.default.removeItem(at: tempItem) diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index 9de54a8dd..b4f4a227d 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -2,7 +2,7 @@ - + diff --git a/iOS/Account/FeedWranglerAccountViewController.swift b/iOS/Account/FeedWranglerAccountViewController.swift index b6c297df8..90990343b 100644 --- a/iOS/Account/FeedWranglerAccountViewController.swift +++ b/iOS/Account/FeedWranglerAccountViewController.swift @@ -9,6 +9,7 @@ import UIKit import Account import RSWeb +import Secrets class FeedWranglerAccountViewController: UITableViewController { diff --git a/iOS/Account/FeedbinAccountViewController.swift b/iOS/Account/FeedbinAccountViewController.swift index df6110b64..eb271b0ed 100644 --- a/iOS/Account/FeedbinAccountViewController.swift +++ b/iOS/Account/FeedbinAccountViewController.swift @@ -8,6 +8,7 @@ import UIKit import Account +import Secrets import RSWeb class FeedbinAccountViewController: UITableViewController { diff --git a/iOS/Account/NewsBlurAccountViewController.swift b/iOS/Account/NewsBlurAccountViewController.swift index 9810f8196..0babecde3 100644 --- a/iOS/Account/NewsBlurAccountViewController.swift +++ b/iOS/Account/NewsBlurAccountViewController.swift @@ -8,6 +8,7 @@ import UIKit import Account +import Secrets import RSWeb class NewsBlurAccountViewController: UITableViewController { diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 58d600aef..ffb89d13e 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -101,6 +101,10 @@ struct AppAssets { UIImage(systemName: "square.and.pencil")! }() + static var extensionPointTwitter: UIImage = { + return UIImage(named: "extensionPointTwitter")! + }() + static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 986378b00..ad7833afb 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -36,6 +36,7 @@ struct AppDefaults { struct Key { static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let activeExtensionPointIDs = "activeExtensionPointIDs" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" static let timelineGroupByFeed = "timelineGroupByFeed" @@ -106,6 +107,15 @@ struct AppDefaults { } } + static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + get { + return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + } + set { + UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + } + } + static var lastImageCacheFlushDate: Date? { get { return date(for: Key.lastImageCacheFlushDate) diff --git a/iOS/Inspector/ExtensionInspectorViewController.swift b/iOS/Inspector/ExtensionInspectorViewController.swift new file mode 100644 index 000000000..f667bae29 --- /dev/null +++ b/iOS/Inspector/ExtensionInspectorViewController.swift @@ -0,0 +1,63 @@ +// +// ExtensionPointInspectorViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class ExtensionPointInspectorViewController: UITableViewController { + + @IBOutlet weak var extensionDescription: UILabel! + var extensionPoint: ExtensionPoint? + + override func viewDidLoad() { + super.viewDidLoad() + guard let extensionPoint = extensionPoint else { return } + navigationItem.title = extensionPoint.title + extensionDescription.attributedText = extensionPoint.description + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + @IBAction func disable(_ sender: Any) { + guard let extensionPoint = extensionPoint else { return } + ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint.extensionPointID) + self.navigationController?.popViewController(animated: true) + } +} + +// MARK: Table View + +extension ExtensionPointInspectorViewController { + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let extensionPoint = extensionPoint else { return nil } + + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = extensionPoint.templateImage + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + if indexPath.section > 0 { + return true + } + return false + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + } + +} + diff --git a/iOS/Inspector/Inspector.storyboard b/iOS/Inspector/Inspector.storyboard index 52533c98f..7603d0fab 100644 --- a/iOS/Inspector/Inspector.storyboard +++ b/iOS/Inspector/Inspector.storyboard @@ -1,8 +1,8 @@ - + - + @@ -25,10 +25,10 @@ - + - + @@ -176,7 +176,7 @@ - + @@ -377,10 +377,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json new file mode 100644 index 000000000..834100cda --- /dev/null +++ b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "twitter.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e50de4443827d9570161cdd229c8d6ce640981e4 GIT binary patch literal 4237 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 UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsAccountTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsComboTableViewCell switch addableAccountTypes[indexPath.row] { case .onMyMac: - cell.accountNameLabel?.text = Account.defaultLocalAccountName - cell.accountImage?.image = AppAssets.image(for: .onMyMac) + cell.comboNameLabel?.text = Account.defaultLocalAccountName + cell.comboImage?.image = AppAssets.image(for: .onMyMac) case .cloudKit: - cell.accountNameLabel?.text = NSLocalizedString("iCloud", comment: "iCloud") - cell.accountImage?.image = AppAssets.accountCloudKitImage + cell.comboNameLabel?.text = NSLocalizedString("iCloud", comment: "iCloud") + cell.comboImage?.image = AppAssets.accountCloudKitImage case .feedbin: - cell.accountNameLabel?.text = NSLocalizedString("Feedbin", comment: "Feedbin") - cell.accountImage?.image = AppAssets.accountFeedbinImage + cell.comboNameLabel?.text = NSLocalizedString("Feedbin", comment: "Feedbin") + cell.comboImage?.image = AppAssets.accountFeedbinImage case .feedWrangler: - cell.accountNameLabel?.text = NSLocalizedString("Feed Wrangler", comment: "Feed Wrangler") - cell.accountImage?.image = AppAssets.accountFeedWranglerImage + cell.comboNameLabel?.text = NSLocalizedString("Feed Wrangler", comment: "Feed Wrangler") + cell.comboImage?.image = AppAssets.accountFeedWranglerImage case .feedly: - cell.accountNameLabel?.text = NSLocalizedString("Feedly", comment: "Feedly") - cell.accountImage?.image = AppAssets.accountFeedlyImage + cell.comboNameLabel?.text = NSLocalizedString("Feedly", comment: "Feedly") + cell.comboImage?.image = AppAssets.accountFeedlyImage case .newsBlur: - cell.accountNameLabel?.text = NSLocalizedString("NewsBlur", comment: "NewsBlur") - cell.accountImage?.image = AppAssets.accountNewsBlurImage + cell.comboNameLabel?.text = NSLocalizedString("NewsBlur", comment: "NewsBlur") + cell.comboImage?.image = AppAssets.accountNewsBlurImage default: break } diff --git a/iOS/Settings/AddExtensionPointViewController.swift b/iOS/Settings/AddExtensionPointViewController.swift new file mode 100644 index 000000000..a9b44fce0 --- /dev/null +++ b/iOS/Settings/AddExtensionPointViewController.swift @@ -0,0 +1,59 @@ +// +// AddExtensionPointViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +protocol AddExtensionPointDismissDelegate: UIViewController { + func dismiss() +} + +class AddExtensionPointViewController: UITableViewController, AddExtensionPointDismissDelegate { + + private var availableExtensionPointTypes = [ExtensionPoint.Type]() + + override func viewDidLoad() { + super.viewDidLoad() + availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes + } + + override func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return availableExtensionPointTypes.count + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 52.0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsExtensionTableViewCell", for: indexPath) as! SettingsComboTableViewCell + + let extensionPointType = availableExtensionPointTypes[indexPath.row] + cell.comboNameLabel?.text = extensionPointType.title + cell.comboImage?.image = extensionPointType.templateImage + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensionPointNavigationViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let enableViewController = navController.topViewController as! EnableExtensionPointViewController + enableViewController.delegate = self + enableViewController.extensionPointType = availableExtensionPointTypes[indexPath.row] + present(navController, animated: true) + } + + func dismiss() { + navigationController?.popViewController(animated: false) + } + +} diff --git a/iOS/Settings/AddExtensionViewContrller.swift b/iOS/Settings/AddExtensionViewContrller.swift new file mode 100644 index 000000000..91125f159 --- /dev/null +++ b/iOS/Settings/AddExtensionViewContrller.swift @@ -0,0 +1,59 @@ +// +// AddExtensionViewContrller.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +protocol AddExtensionDismissDelegate: UIViewController { + func dismiss() +} + +class AddExtensionViewController: UITableViewController, AddExtensionDismissDelegate { + + private var availableExtensionPointTypes = [ExtensionPoint.Type]() + + override func viewDidLoad() { + super.viewDidLoad() + availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes + } + + override func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return availableExtensionPointTypes.count + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 52.0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsExtensionTableViewCell", for: indexPath) as! SettingsComboTableViewCell + + let extensionPointType = availableExtensionPointTypes[indexPath.row] + cell.comboNameLabel?.text = extensionPointType.title + cell.comboImage?.image = extensionPointType.templateImage + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensiontNavigationViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let enableViewController = navController.topViewController as! EnableExtensionViewController + enableViewController.delegate = self + enableViewController.extensionPointType = availableExtensionPointTypes[indexPath.row] + present(navController, animated: true) + } + + func dismiss() { + navigationController?.popViewController(animated: false) + } + +} diff --git a/iOS/Settings/EnableExtensionPointViewController.swift b/iOS/Settings/EnableExtensionPointViewController.swift new file mode 100644 index 000000000..a3099f3df --- /dev/null +++ b/iOS/Settings/EnableExtensionPointViewController.swift @@ -0,0 +1,132 @@ +// +// EnableExtensionPointViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import AuthenticationServices +import Account +import OAuthSwift +import Secrets + +class EnableExtensionPointViewController: UITableViewController { + + @IBOutlet weak var extensionDescription: UILabel! + + private let callbackURL = URL(string: "vincodennw://")! + private var oauth: OAuthSwift? + + weak var delegate: AddExtensionPointDismissDelegate? + var extensionPointType: ExtensionPoint.Type? + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = extensionPointType?.title + extensionDescription.attributedText = extensionPointType?.description + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + + @IBAction func enable(_ sender: Any) { + guard let extensionPointType = extensionPointType else { return } + + if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type { + enableOauth1(oauth1) + } else { + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = extensionPointType?.templateImage + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + +} + +extension EnableExtensionPointViewController: OAuthSwiftURLHandlerType { + + public func handle(_ url: URL) { + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL.scheme, completionHandler: { (url, error) in + if let callbackedURL = url { + OAuth1Swift.handle(url: callbackedURL) + } + + guard let error = error else { return } + + self.oauth?.cancel() + self.oauth = nil + + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + } + + if case ASWebAuthenticationSessionError.canceledLogin = error { + print("Login cancelled.") + } else { + self.presentError(error) + } + }) + + session.presentationContextProvider = self + if !session.start() { + print("Session failed to start!!!") + } + + } +} + +extension EnableExtensionPointViewController: ASWebAuthenticationPresentationContextProviding { + + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return view.window! + } + +} + +private extension EnableExtensionPointViewController { + + func enableOauth1(_ provider: OAuth1SwiftProvider.Type) { + + let oauth1 = provider.oauth1Swift + self.oauth = oauth1 + oauth1.authorizeURLHandler = self + + oauth1.authorize(withCallbackURL: callbackURL) { [weak self] result in + guard let self = self, let extensionPointType = self.extensionPointType else { return } + + switch result { + case .success(let tokenSuccess): + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + case .failure(let oauthSwiftError): + self.presentError(oauthSwiftError) + } + + self.oauth?.cancel() + self.oauth = nil + } + + } + +} diff --git a/iOS/Settings/EnableExtensionViewController.swift b/iOS/Settings/EnableExtensionViewController.swift new file mode 100644 index 000000000..0732c43a2 --- /dev/null +++ b/iOS/Settings/EnableExtensionViewController.swift @@ -0,0 +1,132 @@ +// +// EnableExtensionViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import AuthenticationServices +import Account +import OAuthSwift +import Secrets + +class EnableExtensionPointViewController: UITableViewController { + + @IBOutlet weak var extensionDescription: UILabel! + + private let callbackURL = URL(string: "vincodennw://")! + private var oauth: OAuthSwift? + + weak var delegate: AddExtensionPointDismissDelegate? + var extensionPointType: ExtensionPoint.Type? + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = extensionPointType?.title ?? "" + extensionDescription = extensionPointType?.extensionDescription ?? "" + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + + @IBAction func enable(_ sender: Any) { + guard let extensionPointType = extensionPointType else { return } + + if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type { + enableOauth1(oauth1) + } else { + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = extensionPointType?.templateImage + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + +} + +extension EnableExtensionPointViewController: OAuthSwiftURLHandlerType { + + public func handle(_ url: URL) { + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL.scheme, completionHandler: { (url, error) in + if let callbackedURL = url { + OAuth1Swift.handle(url: callbackedURL) + } + + guard let error = error else { return } + + self.oauth?.cancel() + self.oauth = nil + + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + } + + if case ASWebAuthenticationSessionError.canceledLogin = error { + print("Login cancelled.") + } else { + self.presentError(error) + } + }) + + session.presentationContextProvider = self + if !session.start() { + print("Session failed to start!!!") + } + + } +} + +extension EnableExtensionPointViewController: ASWebAuthenticationPresentationContextProviding { + + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return view.window! + } + +} + +private extension EnableExtensionPointViewController { + + func enableOauth1(_ provider: OAuth1SwiftProvider.Type) { + + let oauth1 = provider.oauth1Swift + self.oauth = oauth1 + oauth1.authorizeURLHandler = self + + oauth1.authorize(withCallbackURL: callbackURL) { [weak self] result in + guard let self = self, let extensionPointType = self.extensionPointType else { return } + + switch result { + case .success(let tokenSuccess): + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + case .failure(let oauthSwiftError): + self.presentError(oauthSwiftError) + } + + self.oauth?.cancel() + self.oauth = nil + } + + } + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index 7d7c0f0f6..04a998ca9 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -2,7 +2,7 @@ - + @@ -58,10 +58,31 @@ + + + + + + + + + + + + + + + - + @@ -78,7 +99,7 @@ - + @@ -95,7 +116,7 @@ - + @@ -116,7 +137,7 @@ - + @@ -149,7 +170,7 @@ - + @@ -182,7 +203,7 @@ - + @@ -215,7 +236,7 @@ - + @@ -242,7 +263,7 @@ - + @@ -275,7 +296,7 @@ - + @@ -320,7 +341,7 @@ - + @@ -356,14 +377,14 @@ - + - + - + @@ -407,7 +428,7 @@ - + @@ -424,7 +445,7 @@ - + @@ -441,7 +462,7 @@ - + @@ -458,7 +479,7 @@ - + @@ -475,7 +496,7 @@ - + @@ -527,7 +548,7 @@ - + @@ -560,8 +581,8 @@ - - + + @@ -726,7 +747,7 @@ - + @@ -829,7 +850,7 @@ - + @@ -885,7 +906,7 @@ - + @@ -926,9 +947,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Settings/SettingsAccountTableViewCell.swift b/iOS/Settings/SettingsComboTableViewCell.swift similarity index 61% rename from iOS/Settings/SettingsAccountTableViewCell.swift rename to iOS/Settings/SettingsComboTableViewCell.swift index 2d5ba352d..ec1775600 100644 --- a/iOS/Settings/SettingsAccountTableViewCell.swift +++ b/iOS/Settings/SettingsComboTableViewCell.swift @@ -8,18 +8,18 @@ import UIKit -class SettingsAccountTableViewCell: VibrantTableViewCell { +class SettingsComboTableViewCell: VibrantTableViewCell { - @IBOutlet weak var accountImage: UIImageView! - @IBOutlet weak var accountNameLabel: UILabel! + @IBOutlet weak var comboImage: UIImageView! + @IBOutlet weak var comboNameLabel: UILabel! override func updateVibrancy(animated: Bool) { super.updateVibrancy(animated: animated) - updateLabelVibrancy(accountNameLabel, color: labelColor, animated: animated) + updateLabelVibrancy(comboNameLabel, color: labelColor, animated: animated) let tintColor = isHighlighted || isSelected ? AppAssets.vibrantTextColor : UIColor.label UIView.animate(withDuration: duration(animated: animated)) { - self.accountImage?.tintColor = tintColor + self.comboImage?.tintColor = tintColor } } diff --git a/iOS/Settings/SettingsAccountTableViewCell.xib b/iOS/Settings/SettingsComboTableViewCell.xib similarity index 90% rename from iOS/Settings/SettingsAccountTableViewCell.xib rename to iOS/Settings/SettingsComboTableViewCell.xib index 9eafae53f..4cf193f27 100644 --- a/iOS/Settings/SettingsAccountTableViewCell.xib +++ b/iOS/Settings/SettingsComboTableViewCell.xib @@ -1,14 +1,14 @@ - + - + - + @@ -39,8 +39,8 @@ - - + + diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 0f3c47505..3814e3ddc 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -34,8 +34,9 @@ class SettingsViewController: UITableViewController { NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidAddAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidDeleteAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(activeExtensionPointsDidChange), name: .ActiveExtensionPointsDidChange, object: nil) - tableView.register(UINib(nibName: "SettingsAccountTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsAccountTableViewCell") + tableView.register(UINib(nibName: "SettingsComboTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsComboTableViewCell") tableView.register(UINib(nibName: "SettingsTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsTableViewCell") tableView.rowHeight = UITableView.automaticDimension @@ -110,12 +111,14 @@ class SettingsViewController: UITableViewController { case 1: return AccountManager.shared.accounts.count + 1 case 2: + return ExtensionPointManager.shared.activeExtensionPoints.count + 1 + case 3: let defaultNumberOfRows = super.tableView(tableView, numberOfRowsInSection: section) if AccountManager.shared.activeAccounts.isEmpty || AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) { return defaultNumberOfRows - 1 } return defaultNumberOfRows - case 4: + case 5: return traitCollection.userInterfaceIdiom == .phone ? 2 : 1 default: return super.tableView(tableView, numberOfRowsInSection: section) @@ -133,11 +136,26 @@ class SettingsViewController: UITableViewController { cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) cell.textLabel?.text = NSLocalizedString("Add Account", comment: "Accounts") } else { - let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsAccountTableViewCell + let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell acctCell.applyThemeProperties() let account = sortedAccounts[indexPath.row] - acctCell.accountImage?.image = AppAssets.image(for: account.type) - acctCell.accountNameLabel?.text = account.nameForDisplay + acctCell.comboImage?.image = AppAssets.image(for: account.type) + acctCell.comboNameLabel?.text = account.nameForDisplay + cell = acctCell + } + + case 2: + + let extensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values) + if indexPath.row == extensionPoints.count { + cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) + cell.textLabel?.text = NSLocalizedString("Add Extension", comment: "Extensions") + } else { + let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell + acctCell.applyThemeProperties() + let extensionPoint = extensionPoints[indexPath.row] + acctCell.comboImage?.image = extensionPoint.templateImage + acctCell.comboNameLabel?.text = extensionPoint.title cell = acctCell } @@ -166,6 +184,16 @@ class SettingsViewController: UITableViewController { self.navigationController?.pushViewController(controller, animated: true) } case 2: + let extensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values) + if indexPath.row == extensionPoints.count { + let controller = UIStoryboard.settings.instantiateController(ofType: AddExtensionPointViewController.self) + self.navigationController?.pushViewController(controller, animated: true) + } else { + let controller = UIStoryboard.inspector.instantiateController(ofType: ExtensionPointInspectorViewController.self) + controller.extensionPoint = extensionPoints[indexPath.row] + self.navigationController?.pushViewController(controller, animated: true) + } + case 3: switch indexPath.row { case 0: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) @@ -185,7 +213,7 @@ class SettingsViewController: UITableViewController { default: break } - case 3: + case 4: switch indexPath.row { case 3: let timeline = UIStoryboard.settings.instantiateController(ofType: TimelineCustomizerViewController.self) @@ -193,10 +221,10 @@ class SettingsViewController: UITableViewController { default: break } - case 5: + case 6: let colorPalette = UIStoryboard.settings.instantiateController(ofType: ColorPaletteTableViewController.self) self.navigationController?.pushViewController(colorPalette, animated: true) - case 6: + case 7: switch indexPath.row { case 0: openURL("https://ranchero.com/netnewswire/help/ios/5.0/en/") @@ -310,6 +338,10 @@ class SettingsViewController: UITableViewController { tableView.reloadData() } + @objc func activeExtensionPointsDidChange() { + tableView.reloadData() + } + } // MARK: OPML Document Picker