From b8f7e3f5198e48f01ba43460374e1558d39743fa Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 8 Nov 2019 09:51:59 +1100 Subject: [PATCH 01/30] Use ASWebAuthenticationSession to authenticate Feedly users and grant NNW access tokens. --- Frameworks/Account/Account.swift | 25 ++- .../Account/Feedly/FeedlyAPICaller.swift | 19 -- .../Feedly/FeedlyAccountDelegate+OAuth.swift | 4 - .../Feedly/FeedlyAccountDelegate.swift | 15 +- .../OAuthAuthorizationCodeGranting.swift | 4 +- .../Accounts/AccountsAddViewController.swift | 22 ++- .../AccountsFeedlyWebWindowController.swift | 4 +- NetNewsWire.xcodeproj/project.pbxproj | 97 +++++++-- .../OAuthAccountAuthorizationOperation.swift | 187 ++++++++++++++++++ ...OAuthAuthorizationClient+NetNewsWire.swift | 35 ++++ 10 files changed, 357 insertions(+), 55 deletions(-) create mode 100644 Shared/OAuthAccountAuthorizationOperation.swift create mode 100644 Shared/OAuthAuthorizationClient+NetNewsWire.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index f40d48c43..b3579391f 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -308,18 +308,23 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient { - let grantingType: OAuthAuthorizationGranting.Type - switch type { - case .feedly: - grantingType = FeedlyAccountDelegate.self - default: - fatalError("\(type) does not support OAuth authorization code granting.") + public var oauthAuthorizationClient: OAuthAuthorizationClient? { + get { + guard let oauthGrantingAccount = delegate as? OAuthAuthorizationGranting else { + assertionFailure() + return nil + } + return oauthGrantingAccount.oauthAuthorizationClient + } + set { + guard var oauthGrantingAccount = delegate as? OAuthAuthorizationGranting else { + assertionFailure() + return + } + oauthGrantingAccount.oauthAuthorizationClient = newValue } - - return grantingType.oauthAuthorizationClient } - + public static func oauthAuthorizationCodeGrantRequest(for type: AccountType, client: OAuthAuthorizationClient) -> URLRequest { let grantingType: OAuthAuthorizationGranting.Type switch type { diff --git a/Frameworks/Account/Feedly/FeedlyAPICaller.swift b/Frameworks/Account/Feedly/FeedlyAPICaller.swift index dfe4e9c99..085df384e 100644 --- a/Frameworks/Account/Feedly/FeedlyAPICaller.swift +++ b/Frameworks/Account/Feedly/FeedlyAPICaller.swift @@ -28,25 +28,6 @@ final class FeedlyAPICaller { } return components } - - var oauthAuthorizationClient: OAuthAuthorizationClient { - switch self { - case .cloud: - /// Models private NetNewsWire client secrets. - /// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code - return OAuthAuthorizationClient(id: "{FEEDLY-ID}", - redirectUri: "{FEEDLY-REDIRECT-URI}", - state: nil, - secret: "{FEEDLY-SECRET}") - case .sandbox: - /// Models public sandbox API values found at: - /// https://groups.google.com/forum/#!topic/feedly-cloud/WwQWMgDmOuw - return OAuthAuthorizationClient(id: "sandbox", - redirectUri: "http://localhost", - state: nil, - secret: "ReVGXA6WekanCxbf") - } - } } private let transport: Transport diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift index abe0c465a..4c71f54b6 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift @@ -27,10 +27,6 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting { private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions" - static var oauthAuthorizationClient: OAuthAuthorizationClient { - return environment.oauthAuthorizationClient - } - static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest { let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id, redirectUri: client.redirectUri, diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 37e33ff16..403a4696b 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -17,14 +17,16 @@ final class FeedlyAccountDelegate: AccountDelegate { /// Feedly has a sandbox API and a production API. /// This property is referred to when clients need to know which environment it should be pointing to. + /// The value of this proptery must match any `OAuthAuthorizationClient` used. static var environment: FeedlyAPICaller.API { #if DEBUG // https://developer.feedly.com/v3/developer/ if let token = ProcessInfo.processInfo.environment["FEEDLY_DEV_ACCESS_TOKEN"], !token.isEmpty { return .cloud } + // To debug on a different Feedly environment, do a workspace search for `FEEDLY_ENVIRONMENT` + // and ensure the environment matches the client. return .sandbox - #else return .cloud #endif @@ -53,6 +55,8 @@ final class FeedlyAccountDelegate: AccountDelegate { } } + var oauthAuthorizationClient: OAuthAuthorizationClient? + var accountMetadata: AccountMetadata? var refreshProgress = DownloadProgress(numberOfTasks: 0) @@ -484,9 +488,12 @@ final class FeedlyAccountDelegate: AccountDelegate { func accountDidInitialize(_ account: Account) { credentials = try? account.retrieveCredentials(type: .oauthAccessToken) - let client = FeedlyAccountDelegate.oauthAuthorizationClient - let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: client, log: log) - operationQueue.addOperation(refreshAccessToken) + if let client = oauthAuthorizationClient { + let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: client, log: log) + operationQueue.addOperation(refreshAccessToken) + } else { + os_log(.debug, log: log, "*** WARNING! Not refreshing token because the oauthAuthorizationClient has not been injected. ***") + } } static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { diff --git a/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift b/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift index 433687c32..01f506f6d 100644 --- a/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift +++ b/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift @@ -61,7 +61,7 @@ public struct OAuthAuthorizationResponse { public extension OAuthAuthorizationResponse { init(url: URL, client: OAuthAuthorizationClient) throws { - guard let host = url.host, client.redirectUri.contains(host) else { + guard let scheme = url.scheme, client.redirectUri.hasPrefix(scheme) else { throw URLError(.unsupportedURL) } guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { @@ -166,7 +166,7 @@ public protocol OAuthAuthorizationCodeGrantRequesting { protocol OAuthAuthorizationGranting: AccountDelegate { - static var oauthAuthorizationClient: OAuthAuthorizationClient { get } + var oauthAuthorizationClient: OAuthAuthorizationClient? { get set } static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index 02b87bd38..1c43cf6ee 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -101,9 +101,16 @@ extension AccountsAddViewController: NSTableViewDelegate { accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsReaderAPIWindowController case .feedly: - let accountsFeedlyWindowController = AccountsFeedlyWebWindowController() - accountsFeedlyWindowController.runSheetOnWindow(self.view.window!) - accountsAddWindowController = accountsFeedlyWindowController + // To debug on a different Feedly environment, do a workspace search for `FEEDLY_ENVIRONMENT` + // and ensure the environment matches the client. + #if DEBUG + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, oauthClient: .feedlySandboxClient) + #else + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, oauthClient: .feedlyCloudClient) + #endif + addAccount.delegate = self + addAccount.presentationAnchor = self.view.window! + OperationQueue.main.addOperation(addAccount) default: break } @@ -113,3 +120,12 @@ extension AccountsAddViewController: NSTableViewDelegate { } } + +// MARK: OAuthAccountAuthorizationOperationDelegate + +extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate { + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + view.window?.presentError(error) + } +} diff --git a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift index 7529a8a9a..586d44421 100644 --- a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift @@ -29,7 +29,7 @@ class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegat } // MARK: Requesting an Access Token - let client = Account.oauthAuthorizationClient(for: .feedly) + let client = OAuthAuthorizationClient.feedlySandboxClient private func beginAuthorization() { let request = Account.oauthAuthorizationCodeGrantRequest(for: .feedly, client: client) @@ -92,6 +92,8 @@ class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegat // Now store the access token because we want the account delegate to use it. try account.storeCredentials(grant.accessToken) + account.oauthAuthorizationClient = client + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { NSApplication.shared.presentError(error) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 94820373d..11390b61c 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -615,8 +615,14 @@ 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; + 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; + 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; + 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; + 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; + 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; + 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; @@ -1538,8 +1544,10 @@ 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = ""; }; 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; + 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+NetNewsWire.swift"; sourceTree = ""; }; 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = ""; }; 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = ""; }; + 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -2441,6 +2449,8 @@ 84C9FC6822629C9A00D921D6 /* Shared */ = { isa = PBXGroup; children = ( + 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */, + 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */, 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, @@ -2798,8 +2808,9 @@ buildConfigurationList = 65ED407F235DEF6C0081F399 /* Build configuration list for PBXNativeTarget "NetNewsWire MAS" */; buildPhases = ( 65ED3FB5235DEF6C0081F399 /* Run Script: Update ArticleExtractorConfig.swift */, + 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 65ED3FB6235DEF6C0081F399 /* Sources */, - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */, + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, 65ED4042235DEF6C0081F399 /* Frameworks */, 65ED404D235DEF6C0081F399 /* Resources */, 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */, @@ -2848,8 +2859,9 @@ buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */; buildPhases = ( 517D2D80233A46ED00FF3E35 /* Run Script: Update ArticleExtractorConfig.swift */, + 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 840D61782029031C009BC708 /* Sources */, - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, 840D61792029031C009BC708 /* Frameworks */, 840D617A2029031C009BC708 /* Resources */, 51C451DF2264C7F200C03939 /* Embed Frameworks */, @@ -2871,8 +2883,9 @@ buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */; buildPhases = ( 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */, + 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 849C645C1ED37A5D003D8FC0 /* Sources */, - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, 849C645D1ED37A5D003D8FC0 /* Frameworks */, 849C645E1ED37A5D003D8FC0 /* Resources */, 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */, @@ -3523,7 +3536,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n"; }; - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3532,16 +3545,16 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; }; - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3550,14 +3563,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; }; 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; @@ -3595,7 +3608,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n"; }; - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */ = { + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3604,14 +3617,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; }; 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; @@ -3677,6 +3690,60 @@ shellPath = /bin/sh; shellScript = "# See https://blog.curtisherbert.com/automated-build-numbers/\n\n# WARNING: If automated build numbers are restored then take \n# care to ensure any app extensions are versioned the same as the app.\n# ask Daniel (jalkut@red-sweater.com) if in doubt about this. \n#git=`sh /etc/profile; which git`\n#branch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\n#git_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\n#simple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\n#build_number=\"$git_count\"\n#if [ $CONFIGURATION != \"Release\" ]; then\n#build_number+=\"-$simple_branch_name\"\n#fi\n\n#plist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n#dsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\n#if [ -f \"$DSYM_INFO_PLIST\" ] ; then\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\n#fi\n"; }; + 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; + }; + 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; + }; + 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; + }; D519E77022EE5B4100923F27 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3829,6 +3896,7 @@ 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */, 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */, 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */, + 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 65ED400F235DEF6C0081F399 /* ArticleExtractorButton.swift in Sources */, 65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */, 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, @@ -3859,6 +3927,7 @@ 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, + 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */, 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */, @@ -3928,6 +3997,7 @@ 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */, FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, + 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, @@ -3962,6 +4032,7 @@ 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, + 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, @@ -4064,11 +4135,13 @@ D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, + 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */, + 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, diff --git a/Shared/OAuthAccountAuthorizationOperation.swift b/Shared/OAuthAccountAuthorizationOperation.swift new file mode 100644 index 000000000..ec047bb7b --- /dev/null +++ b/Shared/OAuthAccountAuthorizationOperation.swift @@ -0,0 +1,187 @@ +// +// OAuthAccountAuthorizationOperation.swift +// NetNewsWire +// +// Created by Kiel Gillard on 8/11/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import AuthenticationServices + +protocol OAuthAccountAuthorizationOperationDelegate: class { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) +} + +final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPresentationContextProviding { + + weak var presentationAnchor: ASPresentationAnchor? + weak var delegate: OAuthAccountAuthorizationOperationDelegate? + + private let accountType: AccountType + private let oauthClient: OAuthAuthorizationClient + private var session: ASWebAuthenticationSession? + + init(accountType: AccountType, oauthClient: OAuthAuthorizationClient) { + self.accountType = accountType + self.oauthClient = oauthClient + } + + override func main() { + assert(Thread.isMainThread) + assert(presentationAnchor != nil, "\(self) outlived presentation anchor.") + + guard !isCancelled else { + didFinish() + return + } + + let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType, client: oauthClient) + + guard let url = request.url else { + return DispatchQueue.main.async { + self.didEndAuthentication(url: nil, error: URLError(.badURL)) + } + } + + guard let redirectUri = URL(string: oauthClient.redirectUri), let scheme = redirectUri.scheme else { + assertionFailure("Could not get callback URL scheme from \(oauthClient.redirectUri)") + return DispatchQueue.main.async { + self.didEndAuthentication(url: nil, error: URLError(.badURL)) + } + } + + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme) { url, error in + DispatchQueue.main.async { [weak self] in + self?.didEndAuthentication(url: url, error: error) + } + } + self.session = session + session.presentationContextProvider = self + + session.start() + } + + override func cancel() { + session?.cancel() + super.cancel() + } + + private func didEndAuthentication(url: URL?, error: Error?) { + guard !isCancelled else { + didFinish() + return + } + + do { + guard let url = url else { + if let error = error { + throw error + } + throw URLError(.badURL) + } + + let response = try OAuthAuthorizationResponse(url: url, client: oauthClient) + + Account.requestOAuthAccessToken(with: response, client: oauthClient, accountType: accountType, completionHandler: didEndRequestingAccessToken(_:)) + + } catch is ASWebAuthenticationSessionError { + didFinish() // Primarily, cancellation. + + } catch { + didFinish(error) + } + } + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + guard let anchor = presentationAnchor else { + fatalError("\(self) has outlived presentation anchor.") + } + return anchor + } + + private func didEndRequestingAccessToken(_ result: Result) { + guard !isCancelled else { + didFinish() + return + } + + switch result { + case .success(let tokenResponse): + saveAccount(for: tokenResponse) + case .failure(let error): + didFinish(error) + } + } + + private func saveAccount(for grant: OAuthAuthorizationGrant) { + // TODO: Find an already existing account for this username? + let account = AccountManager.shared.createAccount(type: .feedly) + do { + + // Store the refresh token first because it sends this token to the account delegate. + if let token = grant.refreshToken { + try account.storeCredentials(token) + } + + // Now store the access token because we want the account delegate to use it. + try account.storeCredentials(grant.accessToken) + + account.oauthAuthorizationClient = oauthClient + + didFinish() + } catch { + didFinish(error) + } + } + + // MARK: Managing Operation State + + private func didFinish() { + assert(Thread.isMainThread) + assert(!isFinished, "Finished operation is attempting to finish again.") + self.isExecutingOperation = false + self.isFinishedOperation = true + } + + private func didFinish(_ error: Error) { + assert(Thread.isMainThread) + assert(!isFinished, "Finished operation is attempting to finish again.") + delegate?.oauthAccountAuthorizationOperation(self, didFailWith: error) + didFinish() + } + + override func start() { + isExecutingOperation = true + DispatchQueue.main.async { + self.main() + } + } + + override var isExecuting: Bool { + return isExecutingOperation + } + + private var isExecutingOperation = false { + willSet { + willChangeValue(for: \.isExecuting) + } + didSet { + didChangeValue(for: \.isExecuting) + } + } + + override var isFinished: Bool { + return isFinishedOperation + } + + private var isFinishedOperation = false { + willSet { + willChangeValue(for: \.isFinished) + } + didSet { + didChangeValue(for: \.isFinished) + } + } +} diff --git a/Shared/OAuthAuthorizationClient+NetNewsWire.swift b/Shared/OAuthAuthorizationClient+NetNewsWire.swift new file mode 100644 index 000000000..5e00d1fe3 --- /dev/null +++ b/Shared/OAuthAuthorizationClient+NetNewsWire.swift @@ -0,0 +1,35 @@ +// +// OAuthAuthorizationClient+NetNewsWire.swift +// NetNewsWire +// +// Created by Kiel Gillard on 8/11/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +extension OAuthAuthorizationClient { + + static var feedlyCloudClient: OAuthAuthorizationClient { + /// Models private NetNewsWire client secrets. + /// These placeholders are substitued at build time using a Run Script phase with build settings. + /// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code + return OAuthAuthorizationClient(id: "{FEEDLY_CLIENT_ID}", + redirectUri: "netnewswire://auth/feedly", + state: nil, + secret: "{FEEDLY_CLIENT_SECRET}") + } + + static var feedlySandboxClient: OAuthAuthorizationClient { + /// We use this funky redirect URI because ASWebAuthenticationSession will try to load http://localhost URLs. + /// See https://developer.feedly.com/v3/sandbox/ for more information. + /// The return value models public sandbox API values found at: + /// https://groups.google.com/forum/#!topic/feedly-cloud/WwQWMgDmOuw + /// They are due to expire on November 30 2019. + return OAuthAuthorizationClient(id: "sandbox", + redirectUri: "urn:ietf:wg:oauth:2.0:oob", + state: nil, + secret: "ReVGXA6WekanCxbf") + } +} From 39c7bdb5e2451b51e74729fd7e5802fe2eb60983 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 8 Nov 2019 13:47:42 +1100 Subject: [PATCH 02/30] Give this test a bit more time to execute on a stressed system. --- .../AccountTests/Feedly/FeedlySyncAllOperationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift index 983c28e45..cd1a988d3 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift @@ -130,7 +130,7 @@ class FeedlySyncAllOperationTests: XCTestCase { OperationQueue.main.addOperation(syncAll) - waitForExpectations(timeout: 2) + waitForExpectations(timeout: 5) } func performInitialSync() { From 8c27187ad81262c90d83c9626dcac02872a5befb Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 8 Nov 2019 18:35:22 +1100 Subject: [PATCH 03/30] Make the OAuthAuthorizationClient an implementation detail the Account.framework. --- Frameworks/Account/Account.swift | 26 ++-- .../Account/Account.xcodeproj/project.pbxproj | 46 ++++++++ .../Account/Feedly/FeedlyAPICaller.swift | 9 ++ .../Feedly/FeedlyAccountDelegate+OAuth.swift | 6 +- .../Feedly/FeedlyAccountDelegate.swift | 13 +- .../OAuthAccountAuthorizationOperation.swift | 31 +++-- .../OAuthAuthorizationClient+Feedly.swift | 3 +- .../OAuthAuthorizationCodeGranting.swift | 8 +- .../Account/xcconfig/Account_project.xcconfig | 3 +- .../Accounts/AccountsAddViewController.swift | 8 +- .../Accounts/AccountsFeedlyWeb.xib | 66 ----------- .../AccountsFeedlyWebWindowController.swift | 102 ---------------- NetNewsWire.xcodeproj/project.pbxproj | 111 ++---------------- 13 files changed, 104 insertions(+), 328 deletions(-) rename {Shared => Frameworks/Account/Feedly}/OAuthAccountAuthorizationOperation.swift (84%) rename Shared/OAuthAuthorizationClient+NetNewsWire.swift => Frameworks/Account/Feedly/OAuthAuthorizationClient+Feedly.swift (97%) delete mode 100644 Mac/Preferences/Accounts/AccountsFeedlyWeb.xib delete mode 100644 Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index b3579391f..597fd920e 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -308,24 +308,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public var oauthAuthorizationClient: OAuthAuthorizationClient? { - get { - guard let oauthGrantingAccount = delegate as? OAuthAuthorizationGranting else { - assertionFailure() - return nil - } - return oauthGrantingAccount.oauthAuthorizationClient - } - set { - guard var oauthGrantingAccount = delegate as? OAuthAuthorizationGranting else { - assertionFailure() - return - } - oauthGrantingAccount.oauthAuthorizationClient = newValue + internal static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient { + switch type { + case .feedly: + return FeedlyAccountDelegate.environment.oauthAuthorizationClient + default: + fatalError("\(type) is not a client for OAuth authorization code granting.") } } - public static func oauthAuthorizationCodeGrantRequest(for type: AccountType, client: OAuthAuthorizationClient) -> URLRequest { + public static func oauthAuthorizationCodeGrantRequest(for type: AccountType) -> URLRequest { let grantingType: OAuthAuthorizationGranting.Type switch type { case .feedly: @@ -334,7 +326,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, fatalError("\(type) does not support OAuth authorization code granting.") } - return grantingType.oauthAuthorizationCodeGrantRequest(for: client) + return grantingType.oauthAuthorizationCodeGrantRequest() } public static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, @@ -351,7 +343,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, fatalError("\(accountType) does not support OAuth authorization code granting.") } - grantingType.requestOAuthAccessToken(with: response, client: client, transport: transport, completionHandler: completionHandler) + grantingType.requestOAuthAccessToken(with: response, transport: transport, completionHandler: completionHandler) } public func refreshAll(completion: @escaping (Result) -> Void) { diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index e0f88b7d8..8044c4679 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -120,6 +120,8 @@ 9E85C8E82366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */; }; 9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */; }; 9E85C8ED2367020700D0F1F7 /* FeedlyGetEntriesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8EC2367020700D0F1F7 /* FeedlyGetEntriesService.swift */; }; + 9E964EB823754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */; }; + 9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */; }; 9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; }; 9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; }; 9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; }; @@ -320,6 +322,8 @@ 9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetPagedStreamContentsService.swift; sourceTree = ""; }; 9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesOperation.swift; sourceTree = ""; }; 9E85C8EC2367020700D0F1F7 /* FeedlyGetEntriesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesService.swift; sourceTree = ""; }; + 9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+Feedly.swift"; sourceTree = ""; }; + 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = ""; }; 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = ""; }; 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.swift; sourceTree = ""; }; 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeed.swift; sourceTree = ""; }; @@ -616,7 +620,9 @@ 9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */, 9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */, 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */, + 9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */, 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */, + 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */, 9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */, 9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */, 9E510D6D234F16A8002E6F1A /* FeedlyAddFeedRequest.swift */, @@ -716,7 +722,9 @@ isa = PBXNativeTarget; buildConfigurationList = 8489350A1F62485000CEBD24 /* Build configuration list for PBXNativeTarget "Account" */; buildPhases = ( + 9E964EBB2375512300A7AF2E /* Run Script: Update OAuthAuthorizationClient+Feedly.swift */, 848934F11F62484F00CEBD24 /* Sources */, + 9E964EBC2375517100A7AF2E /* Run Script: Reset OAuthAuthorizationClient+Feedly.swift */, 848934F21F62484F00CEBD24 /* Frameworks */, 848934F31F62484F00CEBD24 /* Headers */, 848934F41F62484F00CEBD24 /* Resources */, @@ -889,6 +897,42 @@ shellPath = /bin/sh; shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; }; + 9E964EBB2375512300A7AF2E /* Run Script: Update OAuthAuthorizationClient+Feedly.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Update OAuthAuthorizationClient+Feedly.swift"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Feedly/OAuthAuthorizationClient+Feedly.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; + }; + 9E964EBC2375517100A7AF2E /* Run Script: Reset OAuthAuthorizationClient+Feedly.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Reset OAuthAuthorizationClient+Feedly.swift"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "git checkout \"${SRCROOT}/Feedly/OAuthAuthorizationClient+Feedly.swift\"\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -932,6 +976,7 @@ 9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */, 9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */, 844B297D2106C7EC004020B3 /* Feed.swift in Sources */, + 9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */, 9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */, 9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */, 9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */, @@ -972,6 +1017,7 @@ 9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */, 9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */, 9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */, + 9E964EB823754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift in Sources */, 9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */, 84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */, 84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */, diff --git a/Frameworks/Account/Feedly/FeedlyAPICaller.swift b/Frameworks/Account/Feedly/FeedlyAPICaller.swift index 085df384e..38fd68734 100644 --- a/Frameworks/Account/Feedly/FeedlyAPICaller.swift +++ b/Frameworks/Account/Feedly/FeedlyAPICaller.swift @@ -28,6 +28,15 @@ final class FeedlyAPICaller { } return components } + + var oauthAuthorizationClient: OAuthAuthorizationClient { + switch self { + case .sandbox: + return .feedlySandboxClient + case .cloud: + return .feedlyCloudClient + } + } } private let transport: Transport diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift index 4c71f54b6..f0c3bae48 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate+OAuth.swift @@ -27,7 +27,8 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting { private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions" - static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest { + static func oauthAuthorizationCodeGrantRequest() -> URLRequest { + let client = environment.oauthAuthorizationClient let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id, redirectUri: client.redirectUri, scope: oauthAuthorizationGrantScope, @@ -36,7 +37,8 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting { return FeedlyAPICaller.authorizationCodeUrlRequest(for: authorizationRequest, baseUrlComponents: baseURLComponents) } - static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, client: OAuthAuthorizationClient, transport: Transport, completionHandler: @escaping (Result) -> ()) { + static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result) -> ()) { + let client = environment.oauthAuthorizationClient let request = OAuthAccessTokenRequest(authorizationResponse: response, scope: oauthAuthorizationGrantScope, client: client) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 403a4696b..2c35ffec3 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -24,8 +24,6 @@ final class FeedlyAccountDelegate: AccountDelegate { if let token = ProcessInfo.processInfo.environment["FEEDLY_DEV_ACCESS_TOKEN"], !token.isEmpty { return .cloud } - // To debug on a different Feedly environment, do a workspace search for `FEEDLY_ENVIRONMENT` - // and ensure the environment matches the client. return .sandbox #else return .cloud @@ -55,7 +53,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - var oauthAuthorizationClient: OAuthAuthorizationClient? + let oauthAuthorizationClient: OAuthAuthorizationClient var accountMetadata: AccountMetadata? @@ -101,6 +99,7 @@ final class FeedlyAccountDelegate: AccountDelegate { let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3") self.database = SyncDatabase(databaseFilePath: databaseFilePath) + self.oauthAuthorizationClient = api.oauthAuthorizationClient } // MARK: Account API @@ -488,12 +487,8 @@ final class FeedlyAccountDelegate: AccountDelegate { func accountDidInitialize(_ account: Account) { credentials = try? account.retrieveCredentials(type: .oauthAccessToken) - if let client = oauthAuthorizationClient { - let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: client, log: log) - operationQueue.addOperation(refreshAccessToken) - } else { - os_log(.debug, log: log, "*** WARNING! Not refreshing token because the oauthAuthorizationClient has not been injected. ***") - } + let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: oauthAuthorizationClient, log: log) + operationQueue.addOperation(refreshAccessToken) } static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { diff --git a/Shared/OAuthAccountAuthorizationOperation.swift b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift similarity index 84% rename from Shared/OAuthAccountAuthorizationOperation.swift rename to Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift index ec047bb7b..72e867ef5 100644 --- a/Shared/OAuthAccountAuthorizationOperation.swift +++ b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift @@ -7,28 +7,27 @@ // import Foundation -import Account import AuthenticationServices -protocol OAuthAccountAuthorizationOperationDelegate: class { +public protocol OAuthAccountAuthorizationOperationDelegate: class { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) } -final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPresentationContextProviding { +public final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPresentationContextProviding { - weak var presentationAnchor: ASPresentationAnchor? - weak var delegate: OAuthAccountAuthorizationOperationDelegate? + public weak var presentationAnchor: ASPresentationAnchor? + public weak var delegate: OAuthAccountAuthorizationOperationDelegate? private let accountType: AccountType private let oauthClient: OAuthAuthorizationClient private var session: ASWebAuthenticationSession? - init(accountType: AccountType, oauthClient: OAuthAuthorizationClient) { + public init(accountType: AccountType) { self.accountType = accountType - self.oauthClient = oauthClient + self.oauthClient = Account.oauthAuthorizationClient(for: accountType) } - override func main() { + override public func main() { assert(Thread.isMainThread) assert(presentationAnchor != nil, "\(self) outlived presentation anchor.") @@ -37,7 +36,7 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr return } - let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType, client: oauthClient) + let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType) guard let url = request.url else { return DispatchQueue.main.async { @@ -63,7 +62,7 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr session.start() } - override func cancel() { + override public func cancel() { session?.cancel() super.cancel() } @@ -94,7 +93,7 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr } } - func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { guard let anchor = presentationAnchor else { fatalError("\(self) has outlived presentation anchor.") } @@ -127,9 +126,7 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr // Now store the access token because we want the account delegate to use it. try account.storeCredentials(grant.accessToken) - - account.oauthAuthorizationClient = oauthClient - + didFinish() } catch { didFinish(error) @@ -152,14 +149,14 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr didFinish() } - override func start() { + override public func start() { isExecutingOperation = true DispatchQueue.main.async { self.main() } } - override var isExecuting: Bool { + override public var isExecuting: Bool { return isExecutingOperation } @@ -172,7 +169,7 @@ final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPr } } - override var isFinished: Bool { + override public var isFinished: Bool { return isFinishedOperation } diff --git a/Shared/OAuthAuthorizationClient+NetNewsWire.swift b/Frameworks/Account/Feedly/OAuthAuthorizationClient+Feedly.swift similarity index 97% rename from Shared/OAuthAuthorizationClient+NetNewsWire.swift rename to Frameworks/Account/Feedly/OAuthAuthorizationClient+Feedly.swift index 5e00d1fe3..c114bae87 100644 --- a/Shared/OAuthAuthorizationClient+NetNewsWire.swift +++ b/Frameworks/Account/Feedly/OAuthAuthorizationClient+Feedly.swift @@ -1,13 +1,12 @@ // // OAuthAuthorizationClient+NetNewsWire.swift -// NetNewsWire +// Account // // Created by Kiel Gillard on 8/11/19. // Copyright © 2019 Ranchero Software. All rights reserved. // import Foundation -import Account extension OAuthAuthorizationClient { diff --git a/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift b/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift index 01f506f6d..3f1aabbaf 100644 --- a/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift +++ b/Frameworks/Account/Feedly/OAuthAuthorizationCodeGranting.swift @@ -165,10 +165,8 @@ public protocol OAuthAuthorizationCodeGrantRequesting { } protocol OAuthAuthorizationGranting: AccountDelegate { + + static func oauthAuthorizationCodeGrantRequest() -> URLRequest - var oauthAuthorizationClient: OAuthAuthorizationClient? { get set } - - static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest - - static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, client: OAuthAuthorizationClient, transport: Transport, completionHandler: @escaping (Result) -> ()) + static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result) -> ()) } diff --git a/Frameworks/Account/xcconfig/Account_project.xcconfig b/Frameworks/Account/xcconfig/Account_project.xcconfig index f1144a6e9..b7f10e36e 100644 --- a/Frameworks/Account/xcconfig/Account_project.xcconfig +++ b/Frameworks/Account/xcconfig/Account_project.xcconfig @@ -9,7 +9,7 @@ PROVISIONING_PROFILE_SPECIFIER = #include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig" SDKROOT = macosx -MACOSX_DEPLOYMENT_TARGET = 10.14 +MACOSX_DEPLOYMENT_TARGET = 10.15 IPHONEOS_DEPLOYMENT_TARGET = 13.0 SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator @@ -18,7 +18,6 @@ SWIFT_VERSION = 5.1 COMBINE_HIDPI_IMAGES = YES COPY_PHASE_STRIP = NO -MACOSX_DEPLOYMENT_TARGET = 10.14 ALWAYS_SEARCH_USER_PATHS = NO CURRENT_PROJECT_VERSION = 1 VERSION_INFO_PREFIX = diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index 1c43cf6ee..5574248ea 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -101,13 +101,7 @@ extension AccountsAddViewController: NSTableViewDelegate { accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsReaderAPIWindowController case .feedly: - // To debug on a different Feedly environment, do a workspace search for `FEEDLY_ENVIRONMENT` - // and ensure the environment matches the client. - #if DEBUG - let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, oauthClient: .feedlySandboxClient) - #else - let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly, oauthClient: .feedlyCloudClient) - #endif + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) addAccount.delegate = self addAccount.presentationAnchor = self.view.window! OperationQueue.main.addOperation(addAccount) diff --git a/Mac/Preferences/Accounts/AccountsFeedlyWeb.xib b/Mac/Preferences/Accounts/AccountsFeedlyWeb.xib deleted file mode 100644 index 73455ac32..000000000 --- a/Mac/Preferences/Accounts/AccountsFeedlyWeb.xib +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift deleted file mode 100644 index 586d44421..000000000 --- a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// AccountsFeedlyWebWindowController.swift -// NetNewsWire -// -// Created by Kiel Gillard on 30/8/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Cocoa -import Account -import WebKit - -class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegate { - - @IBOutlet private weak var webView: WKWebView! - - private weak var hostWindow: NSWindow? - - convenience init() { - self.init(windowNibName: NSNib.Name("AccountsFeedlyWeb")) - } - - // MARK: API - - func runSheetOnWindow(_ hostWindow: NSWindow, completionHandler handler: ((NSApplication.ModalResponse) -> Void)? = nil) { - self.hostWindow = hostWindow - hostWindow.beginSheet(window!, completionHandler: handler) - beginAuthorization() - } - - // MARK: Requesting an Access Token - let client = OAuthAuthorizationClient.feedlySandboxClient - - private func beginAuthorization() { - let request = Account.oauthAuthorizationCodeGrantRequest(for: .feedly, client: client) - webView.load(request) - } - - private func requestAccessToken(for response: OAuthAuthorizationResponse) { - Account.requestOAuthAccessToken(with: response, client: client, accountType: .feedly) { [weak self] result in - switch result { - case .success(let tokenResponse): - self?.saveAccount(for: tokenResponse) - case .failure(let error): - NSApplication.shared.presentError(error) - } - } - } - - // MARK: Actions - - @IBAction func cancel(_ sender: Any) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - - do { - guard let url = navigationAction.request.url else { return } - - let response = try OAuthAuthorizationResponse(url: url, client: client) - - requestAccessToken(for: response) - - // No point the web view trying to load this. - return decisionHandler(.cancel) - - } catch let error as OAuthAuthorizationErrorResponse { - NSApplication.shared.presentError(error) - - } catch { - print(error) - } - - decisionHandler(.allow) - } - - func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - print(error) - } - - private func saveAccount(for grant: OAuthAuthorizationGrant) { - // TODO: Find an already existing account for this username? - let account = AccountManager.shared.createAccount(type: .feedly) - do { - - // Store the refresh token first because it sends this token to the account delegate. - if let token = grant.refreshToken { - try account.storeCredentials(token) - } - - // Now store the access token because we want the account delegate to use it. - try account.storeCredentials(grant.accessToken) - - account.oauthAuthorizationClient = client - - self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) - } catch { - NSApplication.shared.presentError(error) - } - } -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 11390b61c..5732cbf80 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -276,7 +276,6 @@ 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; }; - 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; }; 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; }; @@ -307,7 +306,6 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; - 65ED3FEB235DEF6C0081F399 /* (null) in Sources */ = {isa = PBXBuildFile; }; 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; @@ -407,7 +405,6 @@ 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 65ED4052235DEF6C0081F399 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; }; 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; }; 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; }; @@ -615,14 +612,6 @@ 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; - 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; - 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; - 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; - 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; @@ -1544,10 +1533,6 @@ 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = ""; }; 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; - 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+NetNewsWire.swift"; sourceTree = ""; }; - 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = ""; }; - 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = ""; }; - 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -2449,8 +2434,6 @@ 84C9FC6822629C9A00D921D6 /* Shared */ = { isa = PBXGroup; children = ( - 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */, - 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */, 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, @@ -2520,8 +2503,6 @@ 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */, 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */, 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */, - 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */, - 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */, 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */, 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */, 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */, @@ -2808,9 +2789,8 @@ buildConfigurationList = 65ED407F235DEF6C0081F399 /* Build configuration list for PBXNativeTarget "NetNewsWire MAS" */; buildPhases = ( 65ED3FB5235DEF6C0081F399 /* Run Script: Update ArticleExtractorConfig.swift */, - 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 65ED3FB6235DEF6C0081F399 /* Sources */, - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */, 65ED4042235DEF6C0081F399 /* Frameworks */, 65ED404D235DEF6C0081F399 /* Resources */, 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */, @@ -2859,9 +2839,8 @@ buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */; buildPhases = ( 517D2D80233A46ED00FF3E35 /* Run Script: Update ArticleExtractorConfig.swift */, - 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 840D61782029031C009BC708 /* Sources */, - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 840D61792029031C009BC708 /* Frameworks */, 840D617A2029031C009BC708 /* Resources */, 51C451DF2264C7F200C03939 /* Embed Frameworks */, @@ -2883,9 +2862,8 @@ buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */; buildPhases = ( 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */, - 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 849C645C1ED37A5D003D8FC0 /* Sources */, - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 849C645D1ED37A5D003D8FC0 /* Frameworks */, 849C645E1ED37A5D003D8FC0 /* Resources */, 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */, @@ -3372,7 +3350,6 @@ 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */, 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */, 65ED4052235DEF6C0081F399 /* template.html in Resources */, - 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */, 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */, 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */, 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */, @@ -3458,7 +3435,6 @@ 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */, 845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */, 848362FF2262A30E00DA1D35 /* template.html in Resources */, - 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */, 848363082262A3DD00DA1D35 /* Main.storyboard in Resources */, 51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */, 84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */, @@ -3536,7 +3512,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n"; }; - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3545,16 +3521,16 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3563,14 +3539,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; @@ -3608,7 +3584,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n"; }; - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3617,14 +3593,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; @@ -3690,60 +3666,6 @@ shellPath = /bin/sh; shellScript = "# See https://blog.curtisherbert.com/automated-build-numbers/\n\n# WARNING: If automated build numbers are restored then take \n# care to ensure any app extensions are versioned the same as the app.\n# ask Daniel (jalkut@red-sweater.com) if in doubt about this. \n#git=`sh /etc/profile; which git`\n#branch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\n#git_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\n#simple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\n#build_number=\"$git_count\"\n#if [ $CONFIGURATION != \"Release\" ]; then\n#build_number+=\"-$simple_branch_name\"\n#fi\n\n#plist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n#dsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\n#if [ -f \"$DSYM_INFO_PLIST\" ] ; then\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\n#fi\n"; }; - 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; - 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; - 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; D519E77022EE5B4100923F27 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3829,7 +3751,6 @@ 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */, 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */, 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */, - 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */, 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */, 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */, 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */, @@ -3860,7 +3781,6 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */, 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, - 65ED3FEB235DEF6C0081F399 /* (null) in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, @@ -3896,7 +3816,6 @@ 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */, 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */, 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */, - 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 65ED400F235DEF6C0081F399 /* ArticleExtractorButton.swift in Sources */, 65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */, 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, @@ -3927,7 +3846,6 @@ 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, - 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */, 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */, @@ -3997,7 +3915,6 @@ 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */, FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, - 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, @@ -4032,7 +3949,6 @@ 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, - 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, @@ -4124,7 +4040,6 @@ 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, - 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */, 8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */, @@ -4135,13 +4050,11 @@ D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, - 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */, - 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, From a4bcbf5c367ed9dd2a688688c25c63b482eea927 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 08:10:39 +1100 Subject: [PATCH 04/30] Automatically refreshes a new Feedly account after its creation. --- .../Feedly/OAuthAccountAuthorizationOperation.swift | 3 +++ .../Accounts/AccountsAddViewController.swift | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift index 72e867ef5..bc73c6f3e 100644 --- a/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift +++ b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift @@ -10,6 +10,7 @@ import Foundation import AuthenticationServices public protocol OAuthAccountAuthorizationOperationDelegate: class { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) } @@ -126,6 +127,8 @@ public final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthentic // Now store the access token because we want the account delegate to use it. try account.storeCredentials(grant.accessToken) + + delegate?.oauthAccountAuthorizationOperation(self, didCreate: account) didFinish() } catch { diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index 5574248ea..db0def90c 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -119,6 +119,17 @@ extension AccountsAddViewController: NSTableViewDelegate { extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + account.refreshAll { [weak self] result in + switch result { + case .success: + break + case .failure(let error): + self?.presentError(error) + } + } + } + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { view.window?.presentError(error) } From 4976537a4018e46f57552159c9f83861cec81986 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 08:12:54 +1100 Subject: [PATCH 05/30] Enables the iOS app to add Feedly accounts. --- iOS/Settings/AddAccountViewController.swift | 30 ++++++++++++++++++ iOS/Settings/Settings.storyboard | 34 +++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift index f53d46a55..0f803b8a6 100644 --- a/iOS/Settings/AddAccountViewController.swift +++ b/iOS/Settings/AddAccountViewController.swift @@ -38,6 +38,11 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate let addViewController = navController.topViewController as! FeedbinAccountViewController addViewController.delegate = self present(navController, animated: true) + case 2: + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) + addAccount.delegate = self + addAccount.presentationAnchor = self.view.window! + OperationQueue.main.addOperation(addAccount) default: break } @@ -48,3 +53,28 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate } } + +extension AddAccountViewController: OAuthAccountAuthorizationOperationDelegate { + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + let rootViewController = view.window?.rootViewController + + account.refreshAll { result in + switch result { + case .success: + break + case .failure(let error): + guard let viewController = rootViewController else { + return + } + viewController.presentError(error) + } + } + + dismiss() + } + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + presentError(error) + } +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index ff43a99bd..ed9e460df 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -424,6 +424,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -811,6 +844,7 @@ + From 63a42042db569eb3be6ad3fdfaf186f3c9590ca6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 10 Nov 2019 18:04:21 -0600 Subject: [PATCH 06/30] Add feedly assets --- iOS/AppAssets.swift | 6 ++++++ .../accountFeedly.imageset/Contents.json | 15 +++++++++++++++ .../accountFeedly.imageset/accountFeedly.pdf | Bin 0 -> 4406 bytes 3 files changed, 21 insertions(+) create mode 100644 iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json create mode 100644 iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 36e6c057a..20993af6f 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -23,6 +23,10 @@ struct AppAssets { return UIImage(named: "accountFeedbin")! }() + static var accountFeedlyImage: UIImage = { + return UIImage(named: "accountFeedly")! + }() + static var accountFreshRSSImage: UIImage = { return UIImage(named: "accountFreshRSS")! }() @@ -200,6 +204,8 @@ struct AppAssets { } case .feedbin: return AppAssets.accountFeedbinImage + case .feedly: + return AppAssets.accountFeedlyImage case .freshRSS: return AppAssets.accountFreshRSSImage default: diff --git a/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json new file mode 100644 index 000000000..754629a4a --- /dev/null +++ b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "accountFeedly.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf new file mode 100644 index 0000000000000000000000000000000000000000..907e486bc4a0e1efc925ff12110144ddc8895b53 GIT binary patch literal 4406 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 literal 0 HcmV?d00001 From c87f8c974a2fe9a41bed151aa9c30c488c6e0541 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 17:42:14 +1100 Subject: [PATCH 07/30] Store the last article fetch when articles were successfully fetched. --- Frameworks/Account/Feedly/FeedlyAccountDelegate.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 2c35ffec3..62742a4fe 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -131,7 +131,9 @@ final class FeedlyAccountDelegate: AccountDelegate { let date = Date() operation.syncCompletionHandler = { [weak self] result in - self?.accountMetadata?.lastArticleFetch = date + if case .success = result { + self?.accountMetadata?.lastArticleFetch = date + } os_log(.debug, log: log, "Sync took %{public}.3f seconds", -date.timeIntervalSinceNow) progress.completeTask() From 91f7da615c3692a4f19d8c89afb1fb183be6f7e3 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 10:13:51 -0600 Subject: [PATCH 08/30] Delete dead code previously used by SwiftUI --- NetNewsWire.xcodeproj/project.pbxproj | 16 -------------- iOS/Model Extensions/Account-Extensions.swift | 22 ------------------- .../RefreshInterval-Extensions.swift | 15 ------------- 3 files changed, 53 deletions(-) delete mode 100644 iOS/Model Extensions/Account-Extensions.swift delete mode 100644 iOS/Model Extensions/RefreshInterval-Extensions.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5732cbf80..471435d6b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; - 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; @@ -128,7 +127,6 @@ 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; }; 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; - 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -1279,7 +1277,6 @@ 51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = ""; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; - 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; @@ -1289,7 +1286,6 @@ 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; - 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -1831,15 +1827,6 @@ path = Activity; sourceTree = ""; }; - 519D740423243C68008BB345 /* Model Extensions */ = { - isa = PBXGroup; - children = ( - 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, - 51AF460D232488C6001742EF /* Account-Extensions.swift */, - ); - path = "Model Extensions"; - sourceTree = ""; - }; 51C45245226506C800C03939 /* UIKit Extensions */ = { isa = PBXGroup; children = ( @@ -2557,7 +2544,6 @@ 5123DB95233EC69300282CC9 /* Inspector */, 513145F9235A55A700387FDC /* Intents */, 5183CCEB227117C70010922C /* Settings */, - 519D740423243C68008BB345 /* Model Extensions */, 51C45245226506C800C03939 /* UIKit Extensions */, 513C5CE7232571C2003D4054 /* ShareExtension */, 51314643235A7C2300387FDC /* IntentsExtension */, @@ -3900,7 +3886,6 @@ 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, - 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, @@ -3988,7 +3973,6 @@ 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */, - 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, diff --git a/iOS/Model Extensions/Account-Extensions.swift b/iOS/Model Extensions/Account-Extensions.swift deleted file mode 100644 index bdad16270..000000000 --- a/iOS/Model Extensions/Account-Extensions.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Account-Extensions.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/7/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Foundation -import Account - -extension AccountType: Identifiable { - public var id: Int { - return rawValue - } -} - -extension Account: Identifiable { - public var id: String { - return accountID - } -} diff --git a/iOS/Model Extensions/RefreshInterval-Extensions.swift b/iOS/Model Extensions/RefreshInterval-Extensions.swift deleted file mode 100644 index 5906d10a2..000000000 --- a/iOS/Model Extensions/RefreshInterval-Extensions.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RefreshInterval-Extensions.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/7/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Foundation - -extension RefreshInterval: Identifiable { - var id: Int { - return rawValue - } -} From 866988edcf4813e4463ef8503a7ca67c0ca95c15 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 13:47:28 -0600 Subject: [PATCH 09/30] Correct deep link comments. --- Frameworks/Account/Feed.swift | 3 ++- Frameworks/Account/Folder.swift | 3 ++- Shared/Data/ArticleUtilities.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift index 0ca76adcd..cd3a17ec1 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/Feed.swift @@ -179,7 +179,8 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, De account.renameFeed(self, to: newName, completion: completion) } - // MARK: - PathIDUserInfoProvider + // MARK: - DeepLinkProvider + public var deepLinkUserInfo: [AnyHashable : Any] { return [ DeepLinkKey.accountID.rawValue: account?.accountID ?? "", diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index c341e110a..e23e7804a 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -33,7 +33,8 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun return name ?? Folder.untitledName } - // MARK: - PathIDUserInfoProvider + // MARK: - DeepLinkProvider + public var deepLinkUserInfo: [AnyHashable : Any] { return [ DeepLinkKey.accountID.rawValue: account?.accountID ?? "", diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index e954f215b..51c598115 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -94,7 +94,7 @@ extension Article { } } -// MARK: PathIDUserInfoProvider +// MARK: DeepLinkProvider extension Article: DeepLinkProvider { From 766cd2f86864dd830a985ae8910c3cf1589cfa73 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 16:59:42 -0600 Subject: [PATCH 10/30] Save and restore scroll position when transitioning between three column mode and normal. Issue #1242 --- .../MasterTimelineViewController.swift | 16 ++++++++++++++++ iOS/SceneCoordinator.swift | 8 ++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index bf23f7dd1..16e6d9cc1 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -25,6 +25,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner weak var coordinator: SceneCoordinator! var undoableCommands = [UndoableCommand]() + let scrollPositionQueue = CoalescingQueue(name: "Scroll Position", interval: 0.3, maxInterval: 1.0) private let keyboardManager = KeyboardManager(type: .timeline) override var keyCommands: [UIKeyCommand]? { @@ -73,6 +74,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner override func viewWillAppear(_ animated: Bool) { applyChanges(animate: false) + + // Restore the scroll position if we have one stored + if let restoreIndexPath = coordinator.timelineMiddleIndexPath { + tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false) + } + + // Hide the search controller if we don't have any rows if dataSource.snapshot().numberOfItems < 1 { navigationItem.searchController?.isActive = false } @@ -288,6 +296,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner coordinator.selectArticle(article, animated: true) } + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + scrollPositionQueue.add(self, #selector(scrollPositionDidChange)) + } + // MARK: Notifications @objc dynamic func unreadCountDidChange(_ notification: Notification) { @@ -366,6 +378,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner titleView?.label.text = coordinator.timelineName } + @objc func scrollPositionDidChange() { + coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow() + } + // MARK: Reloading func queueReloadAvailableCells() { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index b57d60ed3..b9c60d2ef 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -135,6 +135,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var timelineFetcher: ArticleFetcher? { didSet { + timelineMiddleIndexPath = nil + if timelineFetcher is Feed { showFeedNames = false } else { @@ -153,6 +155,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } } + var timelineMiddleIndexPath: IndexPath? + private(set) var showFeedNames = false private(set) var showIcons = false @@ -529,7 +533,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func selectFeed(_ indexPath: IndexPath?, animated: Bool = false) { - guard indexPath != currentFeedIndexPath else { return } + guard indexPath != currentFeedIndexPath else { return } selectArticle(nil) currentFeedIndexPath = indexPath @@ -1589,7 +1593,7 @@ private extension SceneCoordinator { subSplitViewController!.showDetailViewController(navController, sender: self) masterFeedViewController.restoreSelectionIfNecessary(adjustScroll: true) - masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: true) + masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: false) // We made sure this was there above when we called configureDoubleSplit return subSplitViewController! From d803d686db66a2683bbc5a33d5760baa8c00756e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 17:10:48 -0600 Subject: [PATCH 11/30] Update to latest RSCore --- submodules/RSCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSCore b/submodules/RSCore index 972ff3237..6b9d5ace8 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 972ff3237f819a2250e0bc1ca2814bafe328fa69 +Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6 From 4c97d099ea7275f34bf236e7700a459cb85eb2a6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 17:57:49 -0600 Subject: [PATCH 12/30] Add ProjectSettings.xcconfig to be included in the Account build. --- Frameworks/Account/xcconfig/Account_project.xcconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Account/xcconfig/Account_project.xcconfig b/Frameworks/Account/xcconfig/Account_project.xcconfig index b7f10e36e..8e096dabd 100644 --- a/Frameworks/Account/xcconfig/Account_project.xcconfig +++ b/Frameworks/Account/xcconfig/Account_project.xcconfig @@ -7,6 +7,7 @@ PROVISIONING_PROFILE_SPECIFIER = // DeveloperSettings.xcconfig is #included here #include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig" +#include? "../../../SharedXcodeSettings/ProjectSettings.xcconfig" SDKROOT = macosx MACOSX_DEPLOYMENT_TARGET = 10.15 From 3b3dd9f1bd04277ad52f10af9afba3e3e445c483 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 18:18:59 -0600 Subject: [PATCH 13/30] Always use cloud environment for Feedly. --- Frameworks/Account/Feedly/FeedlyAccountDelegate.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 62742a4fe..fbec23b02 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -18,16 +18,9 @@ final class FeedlyAccountDelegate: AccountDelegate { /// Feedly has a sandbox API and a production API. /// This property is referred to when clients need to know which environment it should be pointing to. /// The value of this proptery must match any `OAuthAuthorizationClient` used. + /// Currently this is always returning the cloud API, but we are leaving it stubbed out for now. static var environment: FeedlyAPICaller.API { - #if DEBUG - // https://developer.feedly.com/v3/developer/ - if let token = ProcessInfo.processInfo.environment["FEEDLY_DEV_ACCESS_TOKEN"], !token.isEmpty { - return .cloud - } - return .sandbox - #else return .cloud - #endif } // TODO: Kiel, if you decide not to support OPML import you will have to disallow it in the behaviors From d8b1b6c236eebc69b1a1bf42ae2210624d4120a3 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 20:45:14 -0600 Subject: [PATCH 14/30] Stop adjusting the scroll position on every appearance. --- iOS/MasterTimeline/MasterTimelineViewController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 16e6d9cc1..d340e7109 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -69,10 +69,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner resetEstimatedRowHeight() resetUI() - - } - - override func viewWillAppear(_ animated: Bool) { + applyChanges(animate: false) // Restore the scroll position if we have one stored @@ -80,6 +77,9 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false) } + } + + override func viewWillAppear(_ animated: Bool) { // Hide the search controller if we don't have any rows if dataSource.snapshot().numberOfItems < 1 { navigationItem.searchController?.isActive = false From 701070f2dd5af6fdd09725550cf308a43479bd9d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 09:22:23 -0600 Subject: [PATCH 15/30] Make Article icons/avatars match Timeline icons/avatars. Issue #1273 --- .../Detail/DetailWebViewController.swift | 4 + .../TimelineIconView.swift => IconView.swift} | 6 +- .../Timeline/Cell/TimelineTableCellView.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 22 +-- .../ArticleIconSchemeHandler.swift | 0 .../Article Rendering/ArticleRenderer.swift | 128 +----------------- submodules/RSCore | 2 +- 7 files changed, 25 insertions(+), 139 deletions(-) rename Mac/MainWindow/{Timeline/Cell/TimelineIconView.swift => IconView.swift} (94%) rename {iOS/Article => Shared/Article Rendering}/ArticleIconSchemeHandler.swift (100%) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 4ebf7a697..82dc20524 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -39,6 +39,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } #endif + private let articleIconSchemeHandler = ArticleIconSchemeHandler() private var waitingForFirstReload = false private let keyboardDelegate = DetailKeyboardDelegate() @@ -65,6 +66,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { let configuration = WKWebViewConfiguration() configuration.preferences = preferences + configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) let userContentController = WKUserContentController() userContentController.add(self, name: MessageName.mouseDidEnter) @@ -185,8 +187,10 @@ private extension DetailWebViewController { case .loading: rendering = ArticleRenderer.loadingHTML(style: style) case .article(let article): + articleIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, style: style) case .extracted(let article, let extractedArticle): + articleIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift b/Mac/MainWindow/IconView.swift similarity index 94% rename from Mac/MainWindow/Timeline/Cell/TimelineIconView.swift rename to Mac/MainWindow/IconView.swift index f537c3124..92d9f30dd 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -8,7 +8,7 @@ import AppKit -final class TimelineIconView: NSView { +final class IconView: NSView { var iconImage: IconImage? = nil { didSet { @@ -71,13 +71,13 @@ final class TimelineIconView: NSView { return } - let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineIconView.darkBackgroundColor : TimelineIconView.lightBackgroundColor + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? IconView.darkBackgroundColor : IconView.lightBackgroundColor color.set() dirtyRect.fill() } } -private extension TimelineIconView { +private extension IconView { func commonInit() { addSubview(imageView) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 219e7bdc5..2271a7a53 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -18,7 +18,7 @@ class TimelineTableCellView: NSTableCellView { private let dateView = TimelineTableCellView.singleLineTextField() private let feedNameView = TimelineTableCellView.singleLineTextField() - private lazy var iconView = TimelineIconView() + private lazy var iconView = IconView() private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone) private let separatorView = TimelineTableCellView.separatorView() diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 471435d6b..1c8df4ab1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -113,6 +113,8 @@ 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; }; + 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; }; + 518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; }; 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; @@ -255,7 +257,7 @@ 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; - 65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; }; + 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; }; 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; @@ -489,7 +491,7 @@ 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; - 847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; }; + 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; @@ -1407,7 +1409,7 @@ 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = ""; }; - 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = ""; }; + 847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; @@ -1910,7 +1912,6 @@ children = ( 51C4527E2265092C00C03939 /* ArticleViewController.swift */, 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */, - 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */, 5142192923522B5500E07E2C /* ImageViewController.swift */, 514219362352510100E07E2C /* ImageScrollView.swift */, @@ -1934,10 +1935,11 @@ 51C452A822650DA100C03939 /* Article Rendering */ = { isa = PBXGroup; children = ( - 49F40DEF2335B71000552BF4 /* newsfoot.js */, + 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, - 848362FE2262A30E00DA1D35 /* template.html */, 517630032336215100E15FFF /* main.js */, + 49F40DEF2335B71000552BF4 /* newsfoot.js */, + 848362FE2262A30E00DA1D35 /* template.html */, ); path = "Article Rendering"; sourceTree = ""; @@ -2068,6 +2070,7 @@ 519B8D322143397200FA689C /* SharingServiceDelegate.swift */, 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */, 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */, + 847CD6C9232F4CBF00FAC46D /* IconView.swift */, 844B5B6B1FEA224B00C7C76A /* Keyboard */, 849A975F1ED9EB95007D329B /* Sidebar */, 849A97681ED9EBC8007D329B /* Timeline */, @@ -2245,7 +2248,6 @@ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, - 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */, ); path = Cell; sourceTree = ""; @@ -3718,7 +3720,7 @@ files = ( 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */, 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */, - 65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */, + 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */, 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */, 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */, 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */, @@ -3768,6 +3770,7 @@ 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, + 518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, @@ -4003,7 +4006,7 @@ files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, - 847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */, + 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */, 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, @@ -4098,6 +4101,7 @@ 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, + 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */, 84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */, 84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */, 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/Shared/Article Rendering/ArticleIconSchemeHandler.swift similarity index 100% rename from iOS/Article/ArticleIconSchemeHandler.swift rename to Shared/Article Rendering/ArticleIconSchemeHandler.swift diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 93d1676cf..ae74fd84c 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -31,9 +31,8 @@ struct ArticleRenderer { private let title: String private let body: String private let baseURL: String? - private let useImageIcon: Bool - private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle, useImageIcon: Bool = false) { + private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) { self.article = article self.extractedArticle = extractedArticle self.articleStyle = style @@ -45,13 +44,12 @@ struct ArticleRenderer { self.body = article?.body ?? "" self.baseURL = article?.baseURL?.absoluteString } - self.useImageIcon = useImageIcon } // MARK: - API static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle, useImageIcon: Bool = false) -> Rendering { - let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style, useImageIcon: useImageIcon) + let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) return (renderer.styleString(), renderer.articleHTML) } @@ -104,9 +102,6 @@ private extension ArticleRenderer { return renderHTML(withBody: "") } - static var faviconImgTagCache = [Feed: String]() - static var feedIconImgTagCache = [Feed: String]() - static var defaultStyleSheet: String = { let path = Bundle.main.path(forResource: "styleSheet", ofType: "css")! let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) @@ -146,13 +141,7 @@ private extension ArticleRenderer { d["title"] = title d["body"] = body - - d["avatars"] = "" - var didAddAvatar = false - if let avatarHTML = avatarImgTag() { - d["avatars"] = "\(avatarHTML)"; - didAddAvatar = true - } + d["avatars"] = ""; var feedLink = "" if let feedTitle = article.feed?.nameForDisplay { @@ -163,12 +152,6 @@ private extension ArticleRenderer { } d["feedlink"] = feedLink - if !didAddAvatar, let feed = article.feed { - if let favicon = faviconImgTag(forFeed: feed) { - d["avatars"] = "\(favicon)"; - } - } - let datePublished = article.logicalDatePublished let longDate = dateString(datePublished, .long, .medium) let mediumDate = dateString(datePublished, .medium, .short) @@ -200,111 +183,6 @@ private extension ArticleRenderer { return permalink != preferredLink // Make date a link if it’s a different link from the title’s link } - func faviconImgTag(forFeed feed: Feed) -> String? { - - if let cachedImgTag = ArticleRenderer.faviconImgTagCache[feed] { - return cachedImgTag - } - - if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) { - if let s = base64String(forImage: iconImage.image) { - var dimension = min(iconImage.image.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images. - dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger. - if dimension >= CGFloat(ArticleRenderer.avatarDimension) * 0.8 { //Close enough to scale up. - dimension = CGFloat(ArticleRenderer.avatarDimension) - } - - let imgTag: String - if dimension >= CGFloat(ArticleRenderer.avatarDimension) { - // Use rounded corners. - imgTag = "" - } - else { - imgTag = "" - } - ArticleRenderer.faviconImgTagCache[feed] = imgTag - return imgTag - } - } - - return nil - } - - func feedIconImgTag(forFeed feed: Feed) -> String? { - if let cachedImgTag = ArticleRenderer.feedIconImgTagCache[feed] { - return cachedImgTag - } - - if useImageIcon { - return "" - } - - if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) { - if let s = base64String(forImage: iconImage.image) { - #if os(macOS) - let imgTag = "" - #else - let imgTag = "" - #endif - ArticleRenderer.feedIconImgTagCache[feed] = imgTag - return imgTag - } - } - - return nil - } - - func base64String(forImage image: RSImage) -> String? { - return image.dataRepresentation()?.base64EncodedString() - } - - func singleArticleSpecifiedAuthor() -> Author? { - // The author of this article, if just one. - if let authors = article?.authors, authors.count == 1 { - return authors.first! - } - return nil - } - - func singleFeedSpecifiedAuthor() -> Author? { - if let authors = article?.feed?.authors, authors.count == 1 { - return authors.first! - } - return nil - } - - static let avatarDimension = 48 - - struct Avatar { - let imageURL: String - let url: String? - - func html(dimension: Int) -> String { - let imageTag = "" - if let url = url { - return imageTag.htmlByAddingLink(url) - } - return imageTag - } - } - - func avatarImgTag() -> String? { - if let author = singleArticleSpecifiedAuthor(), let authorImageURL = author.avatarURL { - let imageURL = useImageIcon ? ArticleRenderer.imageIconScheme : authorImageURL - return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension) - } - if let feed = article?.feed, let imgTag = feedIconImgTag(forFeed: feed) { - return imgTag - } - if let feedIconURL = article?.feed?.iconURL { - return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: ArticleRenderer.avatarDimension) - } - if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL { - return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension) - } - return nil - } - func byline() -> String { guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else { return "" diff --git a/submodules/RSCore b/submodules/RSCore index 6b9d5ace8..ff2072b8d 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6 +Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab From 0d2583948ac54894f8f0626e4ce0b8bededb917d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 13:05:52 -0600 Subject: [PATCH 16/30] Add additional assets for Article Extractor Button for when the app is inactive. --- Mac/AppAssets.swift | 8 ++++++++ Mac/MainWindow/ArticleExtractorButton.swift | 10 +++++++++- .../ArticleExtractorInactiveDark.pdf | Bin 0 -> 3911 bytes .../Contents.json | 12 ++++++++++++ .../ArticleExtractorInactiveLight.pdf | Bin 0 -> 3911 bytes .../Contents.json | 12 ++++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.pdf create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/Contents.json diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index ab5eb8dc6..ddab9827d 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -50,6 +50,14 @@ struct AppAssets { return RSImage(named: "articleExtractorError") }() + static var articleExtractorInactiveDark: RSImage! = { + return RSImage(named: "articleExtractorInactiveDark") + }() + + static var articleExtractorInactiveLight: RSImage! = { + return RSImage(named: "articleExtractorInactiveLight") + }() + static var articleExtractorProgress1: RSImage! = { return RSImage(named: "articleExtractorProgress1") }() diff --git a/Mac/MainWindow/ArticleExtractorButton.swift b/Mac/MainWindow/ArticleExtractorButton.swift index 69e250ed5..67b9f75da 100644 --- a/Mac/MainWindow/ArticleExtractorButton.swift +++ b/Mac/MainWindow/ArticleExtractorButton.swift @@ -59,7 +59,15 @@ class ArticleExtractorButton: NSButton { case isInProgress: addAnimatedSublayer(to: hostedLayer) default: - addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity) + if NSApplication.shared.isActive { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity) + } else { + if NSApplication.shared.effectiveAppearance.isDarkMode { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorInactiveDark, opacity: opacity) + } else { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorInactiveLight, opacity: opacity) + } + } } } diff --git a/Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf b/Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..28edebd24c5931a9792c2a4911fa836f56325880 GIT binary patch literal 3911 zcmai%c{r49`^PO)7_yXzRCgXr3bPqYWgjL{wv@3pV+Lazjj=abvOE~cmMA15WT{b! zhU~;+k}VYyL$;)3P2QpUJw5OH{*L!P?&H3$<9nUgd0f}`y#M%|vSuhlC73b-EZfNX z%$hFva_dE7Ggt+H0wkOp`1o-EVnXn8p|}D_wqyZ742d2T0+~H}U?~I?!HI+?09smL z9}1a(^#liS-bpeR4A&7xE&0S&aj5_G$v&9t-2PBk72RE9-(&0tg~jzuyz3~i%t>@F z^nHYgh`mEj)Q8oocQkpN!|0WU3p*E*Yf=eGJ$Y8hudPJ@a=&m`U)aMQ>}6Hw3k|*2 z@m|S&Q}iZ&?Z9oOyR18ON(AvOsUqBih4hUMU5badB>P=I2h9ac@WTJR8hf_s$fgaOBOI!}xwtj`&8#zl-UI+*hIQHck_lcE0P({(3xW^H zm+VCF0aSko`Xnz3d))`vG}~g+#Gm;#*MFIBMkYB~5-5NzJ1NQ#um>ReBo7kV(i`hU z05+?m?*jwWekS-Whx%_heil$;D++R&eISVLW)Z#E6$BtCf*;X|V1d^A-?h!W=6}X= zKrEWcP-DEM7Qop(;7nq93R0 z>~`*IZ(QQqBg`pi&G~G^*%^dhMsf9gV`!$Q-#yVxU;L;Xim!g7SZgNd)3*_lD6OV1 zvz-HM(5rgh4~?X~F>&O?T{=kjwFifg6ZQ ze2n?b#lf4>sPU967`LL~n#B>2?=lt(iZR0Fi}YV7vOcsrug=pgS8%W+DGjGa{tPf- zE>s9yfr^Kgk**nV%3?gzT@z5Z zdk}^g$D)kQjeYc*R4a}O&+R&_k$a|d0LSNT92G-1zwD3la7b!=FCfn!H#6hm!$ey}qCkir)c<60egJk0v4G9DZe(mN8eM^&Cj;!>u&sH?9d2Q^Z|tkQ_5 zm88j`{|C2>mv#^rb=p=K25zl+>ROUm=92&q`aZFGfZs0GYs5rX+H!nRAOTO7y{#yb zI~?Ir$F=h+59JnEB}^I0rFedaR4BrmdkEl&2t6UpeT`fB1Q$^U^gvhZ6$e-s_KMT* zvYR(YPgu|k(CKhh@9lP=bI*2o^9zKcb-+<|#!$|oFd}dGC*j?>o}z&|E8jmCpv` z9xin78X)5#b4FN_cY=34LZn{BJnXJ1MLJ42u+a{uaR16H-KR!Ua;K9;jiO}4Eo2nZ zE5gngxk=m0Y05DUCbu2MqseZ+fBo`;Tr%tB@%bKS2r}?KjDrd_(9t=78 zJ^x!JmwBB|on=^V`Pm%9k&1fFNF#%c+o`xeaT&N6+~xPkCUe=RNnuHkcbXRJOqRct zy>n1fulr8m%gTyV6-QqaUPJex)$-Q5lP8-N4t!uN@DXGQwyZ-eDV8LQ{@h}FJ@ouC z!b0K>EtytLOZcKP{)9Qoe4|=Mbl7;Rkh*f%>+r=?=~U-b&eVh=6Dv}$Xm4hbXOU?S z)J@I}>o(xF)~j3`&n#g2XE8G!RW`=HMNgGi#ROHZx_xb!(ws_`%#f^+l#sNNOjBt_ zN@RFtv}Ld|CRAD~Ty6Ltf^1ke6AvArsp%g(&N$1`<1}(gN@oj{y0r2Ji#1D}?$WJY za!=DcciAT?+p6h4wtH;puV-ouv4%L7na|~DnP-<|=hdQXZ9|?vdjY&YAE$ex?ru(O zPAxVIn>*t4M^R#N*Wp~7++)q^PLD3c&TEa@%9Y1t8YLOUx1F+l)iZSFd=;~TDd0Zt zDyLG6tZuno{uYC|XV`a3qv3e##oWLfD@XV>)h0YzFx%>j+pTv8V_K>?Sj8saI z6P5AGG{*q%oF1z;siQX2s~xBM<0t$YJsSsKX%4Jct{o9F7D`0NwWJgzPe?NP9sW!! zJC}H_>~Yc(Zq9A4KtQx|I@^3|C%XC9dwWH12_+4 z2s+E7#SiD(xy={UP{&zU9ex1v(ox2)M~qAFo4#Mmr5`-X)#Vs_ORvP?=B z9H02JK>f+p(Q8Q)%||BU=k1pX-x5J-5f37|P-%_!F-}IMM!SthxlDB*zVm(H_&IE> zFy(4Wt^%U4tMD&kM8$GNx1KCEw)}`aLQ|drX;8_9>Q_A{_I|Y5bz7$^(d3X1#<}|X zmG35BPtas;$X>?q)7>5nxu6Nz&&@kT#uvsi#=P41wS8$@yM%N``un=n_$!UN4x~h?{HPZ>6fai&UPwj^7@r1p@Ua>%njXJFqJnI zCYg{i&X@{*Je&EtuxCg&XFPWkkJf%ssiz^WOJ?pRx}HtgwG$jt9a53k^5vBq<&Ci z{(Z~xfw?S!;{$=Q)S^lG_3Xt*FKl9Ma{Au(#XQPWB?g-W4^M`6XYT9Vr=)#j9k+e;qjF8~hQnLUN$Svg7f+#(gLX%d`nv17)6D2?d9dl>tRn4__1@)v=R(H9 z%&q?OO!}WL2~m~$kd4%pGSh}_%tGc7!wSPGbP1YCTcHgG-Ct<>)J{w59*wM3`2J`q zwW~P$lK&dR1=rU2$-QejUHjzF-5QP6zD3Uo{D-k_f8tzzLF`a*jrL;Ldd`~DLQCT0 zjOnszYIeM~|3$uUp9ADPoyUilBEGEsCqHje=oU1q!r;GvcavW?H8$iL>FeuZeF%79 z6JRX>`(GiO5dDjZ|7L6-0CI|eCt~$T0e~%(9RgEjzgv*(%O-Ca05K-wec12}|B>xC zK!$CG|1+aLmV)&lx%|L)pP$_RHaQVQYA2fw4tc z$1RHklx(+h4;fWLj^+w6tYpQ$#krcdo8QyNR?@I*0D5gx*0sGkSDOdnmnt_gy~}t{ z3;{xNCH}k`Yqsskwhh}O9ICv%_fVNPvO?&i?zCN^+$Eh#$rok=!XBR07Ez zQ2ZfiQ(S4Rb$4LfY@=-xf9Bg>|7E@bl|nEk(Ev+UQnW5$4Jc?+oGDaeHynWkY*$Cy z9R?`L{<==ArETGEwD24N^11V^37txhfK|ldb@+1>TMi{OCU)ziuUKfmqgu>}{ zN_8*xNB~kxqQ2a~%af<~fej<{0u9*1LZm&>J9Gti%bF4|JY~a#OAvGfd9DTH2e}so z4B8qE(K^iGXeaLZ64d9B^v!nj(V@wz!6Ey3|G^rN!Pj7L6U~e*5Se8r_;IGuYR}%z zmKFB>0&IL{Y)=_>c3{jZn!WF9ooZTI!RfY&<&W}##M(D<4F=Ng16u*{5=z>VJ3$bg ze#KK)rG=U7NyZheBJeHn^QB~2co+;Wl*OH0EHTzn=DeIgajdo@HAQ+nOVqquo#h+>*AW!?7_rC> z;!0{!c|zrjT2pbj4f4*jACCk_=;8Br52litAD-E*FH{(>;bF&;n$I!*^wy^@Rq|ek z3I~=^Zs@Q{nY*Mp#Ltz8&&`<+OgOAH42>SVYP_lX%y+|NL3);cTQmB$`Ka@3u|k~? zXj#(Y=7Ea(WD`)O%T5g@qZm66fEIUvDo1X8J{aa~lhE>>SB58QcGlUQEz?Q<*axMJ z%wWp%hA@?tw9bzkrYG}+)r0=f+lL_OF!S>B&Z4`zc==N3#LX3ft+A&nlY*K@+Xvvc9%{hw}x1+_}l-AF?}@y%)FZ73fjm zQ29x5E=*JBJGA${Tb#mA-!RTp3#su{H3>7sK7r&yZJQFgG@fyB^yr##i7lYD;>;x3 zb{}?%m&j!=)Jc!y%N2VZb2|R=ILFTLEL`e-p*E4RaPw#e-V`^0KLhPgC8V0_3r{H8 z2_S;jg74z{@8_Ax6svo3EAGA-bG*g$QtTzm@|61=uW+*`#rfKwio6$hyIQ^93hs(3 z1y}J8M%M<;H1A?aFL*yWIwCk9%-`t#34i5qJtROp;NWQNp|%E@IBpBDQ!xMG8-S#< znbMrD&~sGd%Gb~64?d^$b?f9*Z0)llk5LQq209&Dk;RQXG(yN5-!dhhzY ztgbv)dEzzcHS_>RDR-kcajJFc(1*GuZjuzql6izF&J<-6G+ne_R7BK7GzHm)5=nPW??`8+ zPa@kZ9V~brf-RU9lMij6$!QbNa6jYem=bW$T-fNv8 zZ>gkNWmRSDrG-7KV5VSOW;mavW|&!$ncIMAunc(q^ab$xQk3S+#)7QKtOnd|Tn>Zq z$G!NXo})PyIVanc36CyEE~t%JN|#4u=q2dIbeuDO)i-kCQVqS5&g(ScAdM_S)wXAs zzcn{6)*ZN`(tPUKl^ma&YsYw0qs*c*gocG|<>us!kI%>*GM6>qVdhdfUB2G@x+UGo zn&96a<~T;E=*}CScvw<+eAKgH&i`b(b@{MR?fF{zGT*YqhV%xEQ#IT>ypC_>70vTV zZHRyW?wJpa>h6W;F-{pjnftwZ`J)-;-Ll=gx^sAVg+B@R9p6vwxy+~*r^d?@<)dxA z-Lm>j-XxD%%&d2v8;qIsY;kEBex*9JS-o+L|15tzCaOItKXFo&&SUduO4-Hui)B>_ zEBJZG`Fyn#Y7uHNYJ2NF>x=vqR&kr`o1W{#p9eopt`BZt!OGxb?hlY$urAn?Q;i4C zy=R99xVe$7u{QXS!b@98t3Dxit*_dip{KY}yw>~H-S^AOA&aGFeX32ouX=ldkK=XF z+AX!GW1qczhKY(1IxFO*+kt7->BpSY@zY7sIh$f8XI?R`8*LE{}%>nW??72G|sls{PNL*Vuucl3T#E}Z7O8ymoC{p_F{qu z6Klga5+>V@O~x!(uadsTgHu8tg!G_OTC5`odZl{%^v2k+nh)Q3Jg{918ZS(WOUjW& z6!sMUgB()1TG^{5g^MgdW{pslsZ(f1=0LS;o|F4On(WQi=!w@q;%;tNn|l45{+H9y zk~gKUne$XQJ{YmbkTRbecI}>68c!d0?L5%&xntuh$`0k_VPEh0E$nL}>Z221N$5bS z)tkrXy{sE+7+>mVVqE~2(?g#+bvt87Vxz8+BYlQ<49n{;IaJI~v}rxoGGx>p@3!P~ zZgt-0JU47tQR4U}MZ%`%3%>JxbN`EL!(GcEb9Hx@?{0X#^W^e+J3LkM z*(?ojZZq_v^qqAHvuiPXv8wjsY`rPY^718qax5{n5?=72?o62@rayl=cRENkHhrRQ z+OKLZ<8}L+!G=+nfL^v3j#f^!gM#8u0-miHIF)F+wBp_}iks;(+0!)*_5rp5=(*Di z@pMcm=5BjatVjRJzCon|r6k3sn1r_bW0%`0oA178`|T~7GC<@!UIi;3R#|x8zItf> zHt(q+pGg0EQ!<;G%a2}IL|SAGyd8*ml&eVg)At*l3hd1|(0$;z`pvB?OVP_|=@rlI z&(h<&_uaawlIeAIQL8OA+R9IU;4!*=#6dFtW3OWl-P#paE%lI!(5{3q9~Hxx#eYy6qlw=%>y zqIRx-l&|;OvU#gIt>m>tWOfnbl`zNcQY*}pnyS6H_+duQ+x zz2Z;%*syACg{|bZGHmk>dLjLoZl&%trUXNeUW*>~y}#7@sWUpIcPz9)_S>VC?<_3Fz(n^_x#rS|x#S?nq{ zIWtDx>k9YRMQ>>ryNS`2kk1?c$1TVentTyUnlL8Vhpuw6(Qx?j$0x4X{Rl z^{XMA0Wxem z{O=jHaWtGW#r_AryZ_|&zpxzk+Xfbv6Fms5KIn)Gj({}=Oi5ICGQ|~uLFHj6c^F`I zM9YKhOax#mTFS~+vVfrnj!N?dSP1`}`n_qgEOKXcOf=ReGz-HaN6;tWP?!n~27|+t zkT4|50t%I3y{x!%6ykOW@V`U;I-@t0WCvl{0E0mP^#F<}6aod<0Y5baLWyR$Xf)Wu Date: Tue, 12 Nov 2019 15:36:03 -0600 Subject: [PATCH 17/30] Change to use Favicon generator. --- Mac/AppAssets.swift | 6 ------ Mac/Inspector/FeedInspectorViewController.swift | 2 +- Mac/MainWindow/SharingServicePickerDelegate.swift | 2 +- Mac/MainWindow/Sidebar/Cell/SidebarCell.swift | 3 +-- Shared/Data/SmallIconProvider.swift | 4 ---- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index ddab9827d..644ce65ea 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -16,12 +16,6 @@ extension NSImage.Name { struct AppAssets { - static var genericFeedImage: IconImage? = { - let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns" - let image = RSImage(contentsOfFile: path) - return image != nil ? IconImage(image!) : nil - }() - static var timelineStar: RSImage! = { return RSImage(named: .timelineStar) }() diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index 0c2bbd568..18ac3e770 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -119,7 +119,7 @@ private extension FeedInspectorViewController { return } - imageView?.image = AppAssets.genericFeedImage?.image + imageView?.image = feed.smallIcon?.image } func updateName() { diff --git a/Mac/MainWindow/SharingServicePickerDelegate.swift b/Mac/MainWindow/SharingServicePickerDelegate.swift index 5e2d38dc0..45cbd9eb7 100644 --- a/Mac/MainWindow/SharingServicePickerDelegate.swift +++ b/Mac/MainWindow/SharingServicePickerDelegate.swift @@ -41,7 +41,7 @@ import RSCore return nil } - let image = sendToCommand.image ?? AppAssets.genericFeedImage?.image ?? NSImage() + let image = sendToCommand.image ?? NSImage() return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) { sendToCommand.sendObject(object, selectedText: nil) } diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift index 610a51ace..7b021a073 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift @@ -81,8 +81,7 @@ class SidebarCell : NSTableCellView { }() private let faviconImageView: NSImageView = { - let iconImage = AppAssets.genericFeedImage - let imageView = iconImage != nil ? NSImageView(image: iconImage!.image) : NSImageView(frame: NSRect.zero) + let imageView = NSImageView(frame: NSRect.zero) imageView.animates = false imageView.imageAlignment = .alignCenter imageView.imageScaling = .scaleProportionallyDown diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Data/SmallIconProvider.swift index 968a9767c..a070dec30 100644 --- a/Shared/Data/SmallIconProvider.swift +++ b/Shared/Data/SmallIconProvider.swift @@ -22,11 +22,7 @@ extension Feed: SmallIconProvider { if let iconImage = appDelegate.faviconDownloader.favicon(for: self) { return iconImage } - #if os(macOS) - return AppAssets.genericFeedImage - #else return FaviconGenerator.favicon(self) - #endif } } From ec2b23c9f05230364f66b7aa174a0148fc6b1c3d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 15:52:07 -0600 Subject: [PATCH 18/30] Change Feed Inspector to use same IconView class as other places in code displaying feed icons. --- .../FeedInspectorViewController.swift | 20 ++++-------- Mac/Inspector/Inspector.storyboard | 31 +++++++++---------- Mac/MainWindow/IconView.swift | 1 + 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index 18ac3e770..964668798 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -12,7 +12,7 @@ import Account final class FeedInspectorViewController: NSViewController, Inspector { - @IBOutlet weak var imageView: NSImageView? + @IBOutlet weak var iconView: IconView! @IBOutlet weak var nameTextField: NSTextField? @IBOutlet weak var homePageURLTextField: NSTextField? @IBOutlet weak var urlTextField: NSTextField? @@ -43,11 +43,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { // MARK: NSViewController override func viewDidLoad() { - imageView!.wantsLayer = true - imageView!.layer?.cornerRadius = 4.0 - updateUI() - NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: nil) } @@ -101,25 +97,21 @@ private extension FeedInspectorViewController { } func updateImage() { - guard let feed = feed else { - imageView?.image = nil + guard let feed = feed, let iconView = iconView else { return } if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { - imageView?.image = feedIcon.image + iconView.iconImage = feedIcon return } - if let favicon = appDelegate.faviconDownloader.favicon(for: feed)?.image { - if favicon.size.height < 16.0 && favicon.size.width < 16.0 { - favicon.size = NSSize(width: 16, height: 16) - } - imageView?.image = favicon + if let favicon = appDelegate.faviconDownloader.favicon(for: feed) { + iconView.iconImage = favicon return } - imageView?.image = feed.smallIcon?.image + iconView.iconImage = feed.smallIcon } func updateName() { diff --git a/Mac/Inspector/Inspector.storyboard b/Mac/Inspector/Inspector.storyboard index eb460de87..b35d220cf 100644 --- a/Mac/Inspector/Inspector.storyboard +++ b/Mac/Inspector/Inspector.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -37,14 +36,6 @@ - - - - - - - - @@ -114,13 +105,19 @@ Field + + + + + + + - - + @@ -128,11 +125,12 @@ Field + - + @@ -142,7 +140,7 @@ Field - + @@ -151,7 +149,7 @@ Field - + @@ -294,7 +292,6 @@ Field - diff --git a/Mac/MainWindow/IconView.swift b/Mac/MainWindow/IconView.swift index 92d9f30dd..e03ea6be4 100644 --- a/Mac/MainWindow/IconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -82,6 +82,7 @@ private extension IconView { func commonInit() { addSubview(imageView) wantsLayer = true + layer?.cornerRadius = 4.0 } func rectForImageView() -> NSRect { From 3d2806287a3ddd562566b338019f655e8c9300d4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 16:13:59 -0600 Subject: [PATCH 19/30] Fix to show display mode button on launch for iPad in portrait. Issue #1291 --- iOS/SceneCoordinator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index b9c60d2ef..e188f7370 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -318,7 +318,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) articleViewController.coordinator = self - let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: false) + let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: true) rootSplitViewController.showDetailViewController(detailNavigationController, sender: self) configureThreePanelMode(for: size) From 96dbd96527be8e7df90506893d3e737ab8309944 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 19:24:07 -0600 Subject: [PATCH 20/30] Change keychain accessiblity to allow access when the device is locked. Issue #1292 --- .../Credentials/CredentialsManager.swift | 28 +++++++++---------- .../Feedbin/FeedbinAccountDelegate.swift | 20 ------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/Frameworks/Account/Credentials/CredentialsManager.swift b/Frameworks/Account/Credentials/CredentialsManager.swift index b52b6c30c..1043d9f6d 100644 --- a/Frameworks/Account/Credentials/CredentialsManager.swift +++ b/Frameworks/Account/Credentials/CredentialsManager.swift @@ -20,11 +20,12 @@ public struct CredentialsManager { }() public static func storeCredentials(_ credentials: Credentials, server: String) throws { - + var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword, - kSecAttrAccount as String: credentials.username, - kSecAttrServer as String: server] - + kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, + kSecAttrAccount as String: credentials.username, + kSecAttrServer as String: server] + if credentials.type != .basic { query[kSecAttrSecurityDomain as String] = credentials.type.rawValue } @@ -32,26 +33,25 @@ public struct CredentialsManager { if let securityGroup = keychainGroup { query[kSecAttrAccessGroup as String] = securityGroup } - + let secretData = credentials.secret.data(using: String.Encoding.utf8)! - let attributes: [String: Any] = [kSecValueData as String: secretData] - let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) - + query[kSecValueData as String] = secretData + + let status = SecItemAdd(query as CFDictionary, nil) + switch status { case errSecSuccess: return - case errSecItemNotFound: + case errSecDuplicateItem: break default: throw CredentialsError.unhandledError(status: status) } - guard status == errSecItemNotFound else { - return - } + var deleteQuery = query + deleteQuery.removeValue(forKey: kSecAttrAccessible as String) + SecItemDelete(deleteQuery as CFDictionary) - query[kSecValueData as String] = secretData - let addStatus = SecItemAdd(query as CFDictionary, nil) if addStatus != errSecSuccess { throw CredentialsError.unhandledError(status: status) diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 50eb9356c..ea3027a4f 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -78,7 +78,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) refreshProgress.addToNumberOfTasksAndRemaining(5) @@ -112,7 +111,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Sending article statuses...") @@ -169,7 +167,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Refreshing article statuses...") @@ -216,7 +213,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) var fileData: Data? @@ -263,7 +259,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func addFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let folder = account.ensureFolder(with: name) { completion(.success(folder)) } else { @@ -272,7 +267,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) guard folder.hasAtLeastOneFeed() else { folder.name = name @@ -300,7 +294,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) // Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system guard folder.hasAtLeastOneFeed() else { @@ -364,7 +357,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createSubscription(url: url) { result in @@ -397,7 +389,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -433,7 +424,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if from is Account { addFeed(for: account, with: feed, to: to, completion: completion) } else { @@ -449,7 +439,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let folder = container as? Folder, let feedID = Int(feed.feedID) { refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -482,7 +471,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let existingFeed = account.existingFeed(withURL: feed.url) { account.addFeed(existingFeed, to: container) { result in @@ -507,7 +495,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) let group = DispatchGroup() @@ -536,7 +523,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { - retrieveCredentialsIfNecessary(account) let syncStatuses = articles.map { article in return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag) @@ -1308,11 +1294,5 @@ private extension FeedbinAccountDelegate { } } - - func retrieveCredentialsIfNecessary(_ account: Account) { - if credentials == nil { - credentials = try? account.retrieveCredentials(type: .basic) - } - } } From a89e5004175f4b228ef07ff816aa454cc50ae2d2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 08:36:01 -0600 Subject: [PATCH 21/30] Updated to latest RSCore --- submodules/RSCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSCore b/submodules/RSCore index ff2072b8d..ba7bbb2ce 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab +Subproject commit ba7bbb2ce10ee04a730c0a1e425a1b2e9d338520 From 690608b1557da7a074442bfdb1b2f2e611d72474 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 08:44:22 -0600 Subject: [PATCH 22/30] Add Github Actions CI integration with Slack --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ec5b8ce8..ae4d1a17a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,4 +49,14 @@ jobs: SCHEME: ${{ matrix.run-config['scheme'] }} DESTINATION: ${{ matrix.run-config['destination'] }} - run: buildscripts/ci-build.sh \ No newline at end of file + run: buildscripts/ci-build.sh + + - name: Notify Slack + uses: 8398a7/action-slack@v2.4.2 + with: + status: ${{ job.status }} + author_name: GitHub Actions CI + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() \ No newline at end of file From 8a0c3887ea7740de762d5d0debe0680a98979e27 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 09:07:14 -0600 Subject: [PATCH 23/30] Change to only notify slack on failure --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae4d1a17a..4c8984b2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,4 +59,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - if: always() \ No newline at end of file + if: failure() \ No newline at end of file From 315ac3ee128c15059c37937ed3ea075a19bae138 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 09:36:05 -0600 Subject: [PATCH 24/30] Remove code that wasn't actually hiding the search bar --- iOS/MasterTimeline/MasterTimelineViewController.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index d340e7109..570f3bba4 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -79,13 +79,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } - override func viewWillAppear(_ animated: Bool) { - // Hide the search controller if we don't have any rows - if dataSource.snapshot().numberOfItems < 1 { - navigationItem.searchController?.isActive = false - } - } - // MARK: Actions @IBAction func markAllAsRead(_ sender: Any) { From 2fd2b8b1b040928441f1115cfa9c17e596d838c4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 13:43:02 -0600 Subject: [PATCH 25/30] Update the refresh indicator since time when coming to the foreground. --- iOS/MasterFeed/MasterFeedViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index f97341272..bdc953f6c 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -58,6 +58,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) @@ -140,6 +141,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { applyChanges(animate: false) } + @objc func willEnterForeground(_ note: Notification) { + updateUI() + } + // MARK: Table View override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { From 38535910efb17ee8c929d5ded2efce09126af1a5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:22:22 -0600 Subject: [PATCH 26/30] Change Select Feed to Go to Feed. Issue #1240 --- iOS/MasterFeed/MasterFeedViewController.swift | 16 +++--- .../MasterTimelineViewController.swift | 10 ++-- iOS/SceneCoordinator.swift | 51 ++++++++++--------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index bdc953f6c..561a7ee20 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -65,11 +65,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { configureToolbar() becomeFirstResponder() + } override func viewWillAppear(_ animated: Bool) { - navigationController?.title = NSLocalizedString("Feeds", comment: "Feeds") - applyChanges(animate: false) updateUI() super.viewWillAppear(animated) } @@ -101,7 +100,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } if let node = node, dataSource.indexPath(for: node) != nil { - reloadNode(node) + // This can stop the scrolling of the disclose function if we don't delay it enough to complete + DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { + self.reloadNode(node) + } } } @@ -462,14 +464,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } - func updateFeedSelection() { + func updateFeedSelection(animated: Bool) { if dataSource.snapshot().numberOfItems > 0 { if let indexPath = coordinator.currentFeedIndexPath { if tableView.indexPathForSelectedRow != indexPath { - tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: true) + tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: animated) } } else { - tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + tableView.selectRow(at: nil, animated: animated, scrollPosition: .none) } } } @@ -1022,7 +1024,7 @@ private extension MasterFeedViewController { deleteCommand.perform() if indexPath == coordinator.currentFeedIndexPath { - coordinator.selectFeed(nil) + coordinator.selectFeed(nil, animated: false) } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 570f3bba4..6990a53a4 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -592,10 +592,9 @@ private extension MasterTimelineViewController { func discloseFeedAction(_ article: Article) -> UIAction? { guard let feed = article.feed else { return nil } - let title = NSLocalizedString("Select Feed", comment: "Select Feed") + let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in - self?.coordinator.selectFeed(nil, animated: true) - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(feed, animated: false) } return action } @@ -603,10 +602,9 @@ private extension MasterTimelineViewController { func discloseFeedAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { guard let feed = article.feed else { return nil } - let title = NSLocalizedString("Select Feed", comment: "Select Feed") + let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.selectFeed(nil, animated: true) - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(feed, animated: false) completionHandler(true) } return action diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index e188f7370..5c9d46b61 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -89,9 +89,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } private let treeControllerDelegate = FeedTreeControllerDelegate() - private lazy var treeController: TreeController = { - return TreeController(delegate: treeControllerDelegate) - }() + private let treeController: TreeController var stateRestorationActivity: NSUserActivity? { return activityManager.stateRestorationActivity @@ -280,6 +278,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } override init() { + treeController = TreeController(delegate: treeControllerDelegate) + super.init() for section in treeController.rootNode.childNodes { @@ -315,6 +315,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterFeedViewController = UIStoryboard.main.instantiateController(ofType: MasterFeedViewController.self) masterFeedViewController.coordinator = self masterNavigationController.pushViewController(masterFeedViewController, animated: false) + masterFeedViewController.reloadFeeds() let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) articleViewController.coordinator = self @@ -327,7 +328,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func handle(_ activity: NSUserActivity) { - selectFeed(nil) + selectFeed(nil, animated: false) guard let activityType = ActivityType(rawValue: activity.activityType) else { return } switch activityType { @@ -371,12 +372,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func selectFirstUnreadInAllUnread() { - selectFeed(IndexPath(row: 1, section: 0)) + selectFeed(IndexPath(row: 1, section: 0), animated: false) selectFirstUnreadArticleInTimeline() } func showSearch() { - selectFeed(nil) + selectFeed(nil, animated: false) installTimelineControllerIfNecessary(animated: false) DispatchQueue.main.asyncAfter(deadline: .now()) { self.masterTimelineViewController!.showSearchAll() @@ -532,13 +533,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return indexPathFor(node) } - func selectFeed(_ indexPath: IndexPath?, animated: Bool = false) { + func selectFeed(_ indexPath: IndexPath?, animated: Bool) { guard indexPath != currentFeedIndexPath else { return } selectArticle(nil) currentFeedIndexPath = indexPath - masterFeedViewController.updateFeedSelection() + masterFeedViewController.updateFeedSelection(animated: animated) if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { timelineFetcher = fetcher @@ -556,31 +557,31 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func selectPrevFeed() { if let indexPath = prevFeedIndexPath { - selectFeed(indexPath) + selectFeed(indexPath, animated: true) } } func selectNextFeed() { if let indexPath = nextFeedIndexPath { - selectFeed(indexPath) + selectFeed(indexPath, animated: true) } } func selectTodayFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 0, section: 0)) + self.selectFeed(IndexPath(row: 0, section: 0), animated: true) } } func selectAllUnreadFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 1, section: 0)) + self.selectFeed(IndexPath(row: 1, section: 0), animated: true) } } func selectStarredFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 2, section: 0)) + self.selectFeed(IndexPath(row: 2, section: 0), animated: true) } } @@ -830,7 +831,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showAdd(_ type: AddControllerType, initialFeed: String? = nil, initialFeedName: String? = nil) { - selectFeed(nil) + selectFeed(nil, animated: false) let addViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController @@ -969,7 +970,7 @@ extension SceneCoordinator: UINavigationControllerDelegate { // If we are showing the Feeds and only the feeds start clearing stuff if viewController === masterFeedViewController && !isThreePanelMode && !isTimelineViewControllerPending { activityManager.invalidateCurrentActivities() - selectFeed(nil) + selectFeed(nil, animated: true) return } @@ -1204,7 +1205,7 @@ private extension SceneCoordinator { } if unreadCountProvider.unreadCount > 0 { - selectFeed(prevIndexPath) + selectFeed(prevIndexPath, animated: true) return true } @@ -1310,7 +1311,7 @@ private extension SceneCoordinator { } if unreadCountProvider.unreadCount > 0 { - selectFeed(nextIndexPath) + selectFeed(nextIndexPath, animated: true) return true } @@ -1492,13 +1493,15 @@ private extension SceneCoordinator { // MARK: Double Split func installTimelineControllerIfNecessary(animated: Bool) { - - isTimelineViewControllerPending = true - if navControllerForTimeline().viewControllers.filter({ $0 is MasterTimelineViewController }).count < 1 { + + isTimelineViewControllerPending = true + masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) masterTimelineViewController!.coordinator = self navControllerForTimeline().pushViewController(masterTimelineViewController!, animated: animated) + + masterTimelineViewController?.reloadArticles(animate: false) } } @@ -1646,19 +1649,19 @@ private extension SceneCoordinator { func handleSelectToday() { if let indexPath = indexPathFor(SmartFeedsController.shared.todayFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } func handleSelectAllUnread() { if let indexPath = indexPathFor(SmartFeedsController.shared.unreadFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } func handleSelectStarred() { if let indexPath = indexPathFor(SmartFeedsController.shared.starredFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } @@ -1667,7 +1670,7 @@ private extension SceneCoordinator { return } if let indexPath = indexPathFor(folderNode) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } From ef84acc02d97e4f319947a50cfafc3824eb8c838 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:31:14 -0600 Subject: [PATCH 27/30] Implement better scroll interference avoidance strategy. --- iOS/MasterFeed/MasterFeedViewController.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 561a7ee20..334ebbeff 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -99,11 +99,17 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject) } - if let node = node, dataSource.indexPath(for: node) != nil { - // This can stop the scrolling of the disclose function if we don't delay it enough to complete - DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { + // Only do the reload of the node when absolutely necessary. It can stop programatic scrolling from + // completing if called to soon after a selectRow where scrolling is necessary. See discloseFeed. + if let node = node, + let indexPath = dataSource.indexPath(for: node), + let cell = tableView.cellForRow(at: indexPath) as? MasterFeedTableViewCell, + let unreadCountProvider = node.representedObject as? UnreadCountProvider { + + if cell.unreadCount != unreadCountProvider.unreadCount { self.reloadNode(node) } + } } From 0c33f6c496e324d4bb6c650d06cf32cd28e86a2e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:41:41 -0600 Subject: [PATCH 28/30] Animate disclosing the feed when using Go to Feed. --- iOS/MasterTimeline/MasterTimelineViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 6990a53a4..c1613e66d 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -594,7 +594,7 @@ private extension MasterTimelineViewController { let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: false) + self?.coordinator.discloseFeed(feed, animated: true) } return action } @@ -604,7 +604,7 @@ private extension MasterTimelineViewController { let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: false) + self?.coordinator.discloseFeed(feed, animated: true) completionHandler(true) } return action From 179cce177cca4c4100c7a8df92551a643baf968d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 17:02:14 -0600 Subject: [PATCH 29/30] Fix Feed Inspector form size on iPad --- iOS/Inspector/FeedInspectorViewController.swift | 2 +- iOS/SceneCoordinator.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Inspector/FeedInspectorViewController.swift b/iOS/Inspector/FeedInspectorViewController.swift index 2aa9dab67..d52a8275f 100644 --- a/iOS/Inspector/FeedInspectorViewController.swift +++ b/iOS/Inspector/FeedInspectorViewController.swift @@ -11,7 +11,7 @@ import Account class FeedInspectorViewController: UITableViewController { - static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 400.0) + static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0) var feed: Feed! @IBOutlet weak var nameTextField: UITextField! diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 5c9d46b61..d84c0636a 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -824,8 +824,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let feedInspectorNavController = UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController - feedInspectorController.modalPresentationStyle = .formSheet - feedInspectorController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay + feedInspectorNavController.modalPresentationStyle = .formSheet + feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay feedInspectorController.feed = feed rootSplitViewController.present(feedInspectorNavController, animated: true) } From 48fef65bc4ea194e86f496190ed8c7a75adc4c27 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 17:13:06 -0600 Subject: [PATCH 30/30] Remove refresh interval setting. Issue #1293 --- NetNewsWire.xcodeproj/project.pbxproj | 6 - iOS/AppDefaults.swift | 12 -- iOS/AppDelegate.swift | 4 +- .../RefreshIntervalViewController.swift | 111 ------------------ iOS/Settings/Settings.storyboard | 83 +++---------- iOS/Settings/SettingsViewController.swift | 17 +-- 6 files changed, 19 insertions(+), 214 deletions(-) delete mode 100644 iOS/Settings/RefreshIntervalViewController.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 1c8df4ab1..819da2fc1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -108,7 +108,6 @@ 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; - 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 518651B223555EB20078E021 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; }; 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; @@ -121,7 +120,6 @@ 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 */; }; - 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.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 */; }; @@ -1280,7 +1278,6 @@ 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 = ""; }; - 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.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 = ""; }; @@ -1798,7 +1795,6 @@ 51A16990235E10D600EB091F /* Settings.storyboard */, 51A16995235E10D600EB091F /* AboutViewController.swift */, 51A16992235E10D600EB091F /* AddAccountViewController.swift */, - 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */, 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */, 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, @@ -3963,14 +3959,12 @@ 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */, - 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */, 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */, 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */, 518651DA235621840078E021 /* ImageTransition.swift in Sources */, 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, - 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */, 516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index d68d1bd48..e2e576835 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -24,7 +24,6 @@ struct AppDefaults { static let timelineIconSize = "timelineIconSize" static let timelineSortDirection = "timelineSortDirection" static let displayUndoAvailableTip = "displayUndoAvailableTip" - static let refreshInterval = "refreshInterval" static let lastRefresh = "lastRefresh" } @@ -45,16 +44,6 @@ struct AppDefaults { } } - static var refreshInterval: RefreshInterval { - get { - let rawValue = AppDefaults.shared.integer(forKey: Key.refreshInterval) - return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour - } - set { - AppDefaults.shared.set(newValue.rawValue, forKey: Key.refreshInterval) - } - } - static var timelineGroupByFeed: Bool { get { return bool(for: Key.timelineGroupByFeed) @@ -112,7 +101,6 @@ struct AppDefaults { static func registerDefaults() { let defaults: [String : Any] = [Key.lastImageCacheFlushDate: Date(), - Key.refreshInterval: RefreshInterval.everyHour.rawValue, Key.timelineGroupByFeed: false, Key.timelineNumberOfLines: 2, Key.timelineIconSize: MasterTimelineIconSize.medium.rawValue, diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 58c741ea4..8fc666e1b 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -290,9 +290,9 @@ private extension AppDelegate { /// Schedules a background app refresh based on `AppDefaults.refreshInterval`. func scheduleBackgroundFeedRefresh() { let request = BGAppRefreshTaskRequest(identifier: "com.ranchero.NetNewsWire.FeedRefresh") - request.earliestBeginDate = Date(timeIntervalSinceNow: AppDefaults.refreshInterval.inSeconds()) + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) - // We send this to a dedicated seria queue because as of 11/05/19 on iOS 13.2 the call to the + // We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the // task scheduler can hang indefinitely. bgTaskDispatchQueue.async { do { diff --git a/iOS/Settings/RefreshIntervalViewController.swift b/iOS/Settings/RefreshIntervalViewController.swift deleted file mode 100644 index 8f3d89ba9..000000000 --- a/iOS/Settings/RefreshIntervalViewController.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// RefreshIntervalViewController.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/25/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import UIKit - -class RefreshIntervalViewController: UITableViewController { - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 7 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - - cell.textLabel?.adjustsFontForContentSizeCategory = true - - let userRefreshInterval = AppDefaults.refreshInterval - - switch indexPath.row { - case 0: - cell.textLabel?.text = RefreshInterval.manually.description() - if userRefreshInterval == RefreshInterval.manually { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 1: - cell.textLabel?.text = RefreshInterval.every10Minutes.description() - if userRefreshInterval == RefreshInterval.every10Minutes { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 2: - cell.textLabel?.text = RefreshInterval.every30Minutes.description() - if userRefreshInterval == RefreshInterval.every30Minutes { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 3: - cell.textLabel?.text = RefreshInterval.everyHour.description() - if userRefreshInterval == RefreshInterval.everyHour { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 4: - cell.textLabel?.text = RefreshInterval.every2Hours.description() - if userRefreshInterval == RefreshInterval.every2Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 5: - cell.textLabel?.text = RefreshInterval.every4Hours.description() - if userRefreshInterval == RefreshInterval.every4Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - default: - cell.textLabel?.text = RefreshInterval.every8Hours.description() - if userRefreshInterval == RefreshInterval.every8Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - } - - return cell - - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - let refreshInterval: RefreshInterval - - switch indexPath.row { - case 0: - refreshInterval = RefreshInterval.manually - case 1: - refreshInterval = RefreshInterval.every10Minutes - case 2: - refreshInterval = RefreshInterval.every30Minutes - case 3: - refreshInterval = RefreshInterval.everyHour - case 4: - refreshInterval = RefreshInterval.every2Hours - case 5: - refreshInterval = RefreshInterval.every4Hours - default: - refreshInterval = RefreshInterval.every8Hours - } - - AppDefaults.refreshInterval = refreshInterval - self.navigationController?.popViewController(animated: true) - - } - -} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index ed9e460df..bcb9ff294 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -60,32 +60,8 @@ - - - - - - - - - - - - - + @@ -102,7 +78,7 @@ - + @@ -119,7 +95,7 @@ - + @@ -140,7 +116,7 @@ - + @@ -171,7 +147,7 @@ - + @@ -202,7 +178,7 @@ - + @@ -223,7 +199,7 @@ - + @@ -240,7 +216,7 @@ - + @@ -257,7 +233,7 @@ - + @@ -274,7 +250,7 @@ - + @@ -291,7 +267,7 @@ - + @@ -308,7 +284,7 @@ - + @@ -474,35 +450,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -680,7 +627,7 @@ - + @@ -783,7 +730,7 @@ - + @@ -839,7 +786,7 @@ - + diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 2a36606c8..ec690552a 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -104,16 +104,6 @@ class SettingsViewController: UITableViewController { cell = acctCell } - case 2: - - if indexPath.row == 0 { - cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) - cell.textLabel?.text = NSLocalizedString("Refresh Interval", comment: "Refresh Interval") - cell.detailTextLabel?.text = AppDefaults.refreshInterval.description() - } else { - cell = super.tableView(tableView, cellForRowAt: indexPath) - } - default: cell = super.tableView(tableView, cellForRowAt: indexPath) @@ -141,21 +131,18 @@ class SettingsViewController: UITableViewController { case 2: switch indexPath.row { case 0: - let timeline = UIStoryboard.settings.instantiateController(ofType: RefreshIntervalViewController.self) - self.navigationController?.pushViewController(timeline, animated: true) - case 1: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) if let sourceView = tableView.cellForRow(at: indexPath) { let sourceRect = tableView.rectForRow(at: indexPath) importOPML(sourceView: sourceView, sourceRect: sourceRect) } - case 2: + case 1: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) if let sourceView = tableView.cellForRow(at: indexPath) { let sourceRect = tableView.rectForRow(at: indexPath) exportOPML(sourceView: sourceView, sourceRect: sourceRect) } - case 3: + case 2: addFeed() tableView.selectRow(at: nil, animated: true, scrollPosition: .none) default: