Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
b763a4bb52
|
@ -49,4 +49,14 @@ jobs:
|
|||
SCHEME: ${{ matrix.run-config['scheme'] }}
|
||||
DESTINATION: ${{ matrix.run-config['destination'] }}
|
||||
|
||||
run: buildscripts/ci-build.sh
|
||||
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: failure()
|
|
@ -308,19 +308,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
}
|
||||
}
|
||||
|
||||
public static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
|
||||
let grantingType: OAuthAuthorizationGranting.Type
|
||||
internal static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
|
||||
switch type {
|
||||
case .feedly:
|
||||
grantingType = FeedlyAccountDelegate.self
|
||||
return FeedlyAccountDelegate.environment.oauthAuthorizationClient
|
||||
default:
|
||||
fatalError("\(type) does not support OAuth authorization code granting.")
|
||||
fatalError("\(type) is not a client for OAuth authorization code granting.")
|
||||
}
|
||||
|
||||
return grantingType.oauthAuthorizationClient
|
||||
}
|
||||
|
||||
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:
|
||||
|
@ -329,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,
|
||||
|
@ -346,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, Error>) -> Void) {
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesOperation.swift; sourceTree = "<group>"; };
|
||||
9E85C8EC2367020700D0F1F7 /* FeedlyGetEntriesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesService.swift; sourceTree = "<group>"; };
|
||||
9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+Feedly.swift"; sourceTree = "<group>"; };
|
||||
9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = "<group>"; };
|
||||
9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = "<group>"; };
|
||||
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.swift; sourceTree = "<group>"; };
|
||||
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeed.swift; sourceTree = "<group>"; };
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -130,7 +130,7 @@ class FeedlySyncAllOperationTests: XCTestCase {
|
|||
|
||||
OperationQueue.main.addOperation(syncAll)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
waitForExpectations(timeout: 5)
|
||||
}
|
||||
|
||||
func performInitialSync() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ?? "",
|
||||
|
|
|
@ -78,7 +78,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
retrieveCredentialsIfNecessary(account)
|
||||
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(5)
|
||||
|
||||
|
@ -112,7 +111,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> 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, Error>) -> 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, Error>) -> Void) {
|
||||
retrieveCredentialsIfNecessary(account)
|
||||
|
||||
var fileData: Data?
|
||||
|
||||
|
@ -263,7 +259,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func addFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> 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, Error>) -> 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, Error>) -> 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<Feed, Error>) -> 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, Error>) -> 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, Error>) -> 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, Error>) -> 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, Error>) -> 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, Error>) -> Void) {
|
||||
retrieveCredentialsIfNecessary(account)
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
|
@ -536,7 +523,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,20 +31,10 @@ final class FeedlyAPICaller {
|
|||
|
||||
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")
|
||||
return .feedlySandboxClient
|
||||
case .cloud:
|
||||
return .feedlyCloudClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,8 @@ 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 {
|
||||
static func oauthAuthorizationCodeGrantRequest() -> URLRequest {
|
||||
let client = environment.oauthAuthorizationClient
|
||||
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id,
|
||||
redirectUri: client.redirectUri,
|
||||
scope: oauthAuthorizationGrantScope,
|
||||
|
@ -40,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<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
let client = environment.oauthAuthorizationClient
|
||||
let request = OAuthAccessTokenRequest(authorizationResponse: response,
|
||||
scope: oauthAuthorizationGrantScope,
|
||||
client: client)
|
||||
|
|
|
@ -17,17 +17,10 @@ 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
|
||||
|
@ -53,6 +46,8 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
let oauthAuthorizationClient: OAuthAuthorizationClient
|
||||
|
||||
var accountMetadata: AccountMetadata?
|
||||
|
||||
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
||||
|
@ -97,6 +92,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
|
||||
|
@ -128,7 +124,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()
|
||||
|
@ -484,8 +482,7 @@ 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)
|
||||
let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: oauthAuthorizationClient, log: log)
|
||||
operationQueue.addOperation(refreshAccessToken)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 AuthenticationServices
|
||||
|
||||
public protocol OAuthAccountAuthorizationOperationDelegate: class {
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account)
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error)
|
||||
}
|
||||
|
||||
public final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPresentationContextProviding {
|
||||
|
||||
public weak var presentationAnchor: ASPresentationAnchor?
|
||||
public weak var delegate: OAuthAccountAuthorizationOperationDelegate?
|
||||
|
||||
private let accountType: AccountType
|
||||
private let oauthClient: OAuthAuthorizationClient
|
||||
private var session: ASWebAuthenticationSession?
|
||||
|
||||
public init(accountType: AccountType) {
|
||||
self.accountType = accountType
|
||||
self.oauthClient = Account.oauthAuthorizationClient(for: accountType)
|
||||
}
|
||||
|
||||
override public func main() {
|
||||
assert(Thread.isMainThread)
|
||||
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
|
||||
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType)
|
||||
|
||||
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 public 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)
|
||||
}
|
||||
}
|
||||
|
||||
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
guard let anchor = presentationAnchor else {
|
||||
fatalError("\(self) has outlived presentation anchor.")
|
||||
}
|
||||
return anchor
|
||||
}
|
||||
|
||||
private func didEndRequestingAccessToken(_ result: Result<OAuthAuthorizationGrant, Error>) {
|
||||
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)
|
||||
|
||||
delegate?.oauthAccountAuthorizationOperation(self, didCreate: account)
|
||||
|
||||
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 public func start() {
|
||||
isExecutingOperation = true
|
||||
DispatchQueue.main.async {
|
||||
self.main()
|
||||
}
|
||||
}
|
||||
|
||||
override public var isExecuting: Bool {
|
||||
return isExecutingOperation
|
||||
}
|
||||
|
||||
private var isExecutingOperation = false {
|
||||
willSet {
|
||||
willChangeValue(for: \.isExecuting)
|
||||
}
|
||||
didSet {
|
||||
didChangeValue(for: \.isExecuting)
|
||||
}
|
||||
}
|
||||
|
||||
override public var isFinished: Bool {
|
||||
return isFinishedOperation
|
||||
}
|
||||
|
||||
private var isFinishedOperation = false {
|
||||
willSet {
|
||||
willChangeValue(for: \.isFinished)
|
||||
}
|
||||
didSet {
|
||||
didChangeValue(for: \.isFinished)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// OAuthAuthorizationClient+NetNewsWire.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 8/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
@ -165,10 +165,8 @@ public protocol OAuthAuthorizationCodeGrantRequesting {
|
|||
}
|
||||
|
||||
protocol OAuthAuthorizationGranting: AccountDelegate {
|
||||
|
||||
static func oauthAuthorizationCodeGrantRequest() -> URLRequest
|
||||
|
||||
static var oauthAuthorizationClient: OAuthAuthorizationClient { get }
|
||||
|
||||
static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest
|
||||
|
||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, client: OAuthAuthorizationClient, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||
}
|
||||
|
|
|
@ -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 ?? "",
|
||||
|
|
|
@ -7,9 +7,10 @@ PROVISIONING_PROFILE_SPECIFIER =
|
|||
// DeveloperSettings.xcconfig is #included here
|
||||
|
||||
#include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig"
|
||||
#include? "../../../SharedXcodeSettings/ProjectSettings.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 +19,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 =
|
||||
|
|
|
@ -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)
|
||||
}()
|
||||
|
@ -50,6 +44,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")
|
||||
}()
|
||||
|
|
|
@ -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 = AppAssets.genericFeedImage?.image
|
||||
iconView.iconImage = feed.smallIcon
|
||||
}
|
||||
|
||||
func updateName() {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15504"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15505"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -37,14 +36,6 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="256" height="332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="H9X-OG-K0p">
|
||||
<rect key="frame" x="104" y="264" width="48" height="48"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="48" id="1Cy-0w-dBg"/>
|
||||
<constraint firstAttribute="height" constant="48" id="edb-lw-Ict"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSNetwork" id="MZ2-89-Bje"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="IWu-80-XC5">
|
||||
<rect key="frame" x="20" y="200" width="216" height="56"/>
|
||||
<constraints>
|
||||
|
@ -114,13 +105,19 @@ Field</string>
|
|||
<action selector="isNotifyAboutNewArticlesChanged:" target="sfH-oR-GXm" id="Vx9-pQ-RnP"/>
|
||||
</connections>
|
||||
</button>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="I6k-QR-VmV" customClass="IconView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="104" y="264" width="48" height="48"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="48" id="Faa-nE-lkA"/>
|
||||
<constraint firstAttribute="width" constant="48" id="esD-dT-oWU"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="zm0-15-BFy" firstAttribute="top" secondItem="2WO-Iu-p5e" secondAttribute="bottom" constant="4" id="2fb-QO-XIm"/>
|
||||
<constraint firstItem="IWu-80-XC5" firstAttribute="top" secondItem="H9X-OG-K0p" secondAttribute="bottom" constant="8" symbolic="YES" id="4WB-WJ-3Z4"/>
|
||||
<constraint firstItem="ZBX-E8-k9c" firstAttribute="top" secondItem="IWu-80-XC5" secondAttribute="bottom" constant="20" id="5L7-aZ-vdg"/>
|
||||
<constraint firstItem="nH2-ab-KJ5" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="8pK-lW-xQk"/>
|
||||
<constraint firstItem="H9X-OG-K0p" firstAttribute="centerX" secondItem="ecA-UY-KEd" secondAttribute="centerX" id="9CA-KA-HEg"/>
|
||||
<constraint firstItem="IWu-80-XC5" firstAttribute="top" secondItem="I6k-QR-VmV" secondAttribute="bottom" constant="8" symbolic="YES" id="Bea-j0-QMb"/>
|
||||
<constraint firstItem="nH2-ab-KJ5" firstAttribute="top" secondItem="ZBX-E8-k9c" secondAttribute="bottom" constant="20" id="CpA-X9-EbP"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Vvk-KG-JlG" secondAttribute="bottom" constant="20" id="IxJ-5N-NhL"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ju6-Zo-8X4" secondAttribute="trailing" constant="20" symbolic="YES" id="Jzi-tP-TIw"/>
|
||||
|
@ -128,11 +125,12 @@ Field</string>
|
|||
<constraint firstItem="ju6-Zo-8X4" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="NwI-2x-dAr"/>
|
||||
<constraint firstItem="ju6-Zo-8X4" firstAttribute="top" secondItem="zm0-15-BFy" secondAttribute="bottom" constant="20" id="PFv-jF-JIZ"/>
|
||||
<constraint firstItem="2WO-Iu-p5e" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="PeT-mm-2HJ"/>
|
||||
<constraint firstItem="I6k-QR-VmV" firstAttribute="top" secondItem="ecA-UY-KEd" secondAttribute="top" constant="20" symbolic="YES" id="URB-DN-7vz"/>
|
||||
<constraint firstAttribute="trailing" secondItem="IWu-80-XC5" secondAttribute="trailing" constant="20" symbolic="YES" id="WW6-xR-Zue"/>
|
||||
<constraint firstItem="H9X-OG-K0p" firstAttribute="top" secondItem="ecA-UY-KEd" secondAttribute="top" constant="20" symbolic="YES" id="Z6q-PN-wOC"/>
|
||||
<constraint firstItem="zm0-15-BFy" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="aho-BJ-kmB"/>
|
||||
<constraint firstItem="ZBX-E8-k9c" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="cjR-0i-YNG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2WO-Iu-p5e" secondAttribute="trailing" constant="20" symbolic="YES" id="dLU-a6-nfx"/>
|
||||
<constraint firstItem="I6k-QR-VmV" firstAttribute="centerX" secondItem="ecA-UY-KEd" secondAttribute="centerX" id="gFG-ZY-eNp"/>
|
||||
<constraint firstAttribute="trailing" secondItem="zm0-15-BFy" secondAttribute="trailing" constant="20" symbolic="YES" id="js6-b2-FIR"/>
|
||||
<constraint firstItem="IWu-80-XC5" firstAttribute="leading" secondItem="ecA-UY-KEd" secondAttribute="leading" constant="20" symbolic="YES" id="r6h-Z0-g7b"/>
|
||||
<constraint firstItem="2WO-Iu-p5e" firstAttribute="top" secondItem="nH2-ab-KJ5" secondAttribute="bottom" constant="20" id="rRv-qO-dPa"/>
|
||||
|
@ -142,7 +140,7 @@ Field</string>
|
|||
</view>
|
||||
<connections>
|
||||
<outlet property="homePageURLTextField" destination="zm0-15-BFy" id="0Jh-yy-mnF"/>
|
||||
<outlet property="imageView" destination="H9X-OG-K0p" id="Rm6-X6-csH"/>
|
||||
<outlet property="iconView" destination="I6k-QR-VmV" id="zrk-zx-zk7"/>
|
||||
<outlet property="isNotifyAboutNewArticlesCheckBox" destination="ZBX-E8-k9c" id="FWc-Ds-LUy"/>
|
||||
<outlet property="isReaderViewAlwaysOnCheckBox" destination="nH2-ab-KJ5" id="xPg-P5-3cr"/>
|
||||
<outlet property="nameTextField" destination="IWu-80-XC5" id="zg4-5h-hoP"/>
|
||||
|
@ -151,7 +149,7 @@ Field</string>
|
|||
</viewController>
|
||||
<customObject id="1ho-ZO-Gkb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="67" y="69.5"/>
|
||||
<point key="canvasLocation" x="67" y="69"/>
|
||||
</scene>
|
||||
<!--Folder-->
|
||||
<scene sceneID="8By-fa-WDQ">
|
||||
|
@ -294,7 +292,6 @@ Field</string>
|
|||
</scenes>
|
||||
<resources>
|
||||
<image name="NSFolder" width="32" height="32"/>
|
||||
<image name="NSNetwork" width="32" height="32"/>
|
||||
<image name="NSSmartBadgeTemplate" width="14" height="14"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import AppKit
|
||||
|
||||
final class TimelineIconView: NSView {
|
||||
final class IconView: NSView {
|
||||
|
||||
var iconImage: IconImage? = nil {
|
||||
didSet {
|
||||
|
@ -71,17 +71,18 @@ 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)
|
||||
wantsLayer = true
|
||||
layer?.cornerRadius = 4.0
|
||||
}
|
||||
|
||||
func rectForImageView() -> NSRect {
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -101,9 +101,10 @@ extension AccountsAddViewController: NSTableViewDelegate {
|
|||
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
|
||||
accountsAddWindowController = accountsReaderAPIWindowController
|
||||
case .feedly:
|
||||
let accountsFeedlyWindowController = AccountsFeedlyWebWindowController()
|
||||
accountsFeedlyWindowController.runSheetOnWindow(self.view.window!)
|
||||
accountsAddWindowController = accountsFeedlyWindowController
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
addAccount.delegate = self
|
||||
addAccount.presentationAnchor = self.view.window!
|
||||
OperationQueue.main.addOperation(addAccount)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -113,3 +114,23 @@ extension AccountsAddViewController: NSTableViewDelegate {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: OAuthAccountAuthorizationOperationDelegate
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14865.1"/>
|
||||
<plugIn identifier="com.apple.WebKit2IBPlugin" version="14865.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AccountsFeedlyWebWindowController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="webView" destination="W4c-Xp-rpq" id="l11-5B-8yc"/>
|
||||
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="708"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="708"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<wkWebView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="W4c-Xp-rpq">
|
||||
<rect key="frame" x="0.0" y="61" width="480" height="647"/>
|
||||
<wkWebViewConfiguration key="configuration">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
<connections>
|
||||
<outlet property="navigationDelegate" destination="-2" id="wAp-Oh-5EK"/>
|
||||
</connections>
|
||||
</wkWebView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PD2-Zk-3yM">
|
||||
<rect key="frame" x="384" y="13" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IEi-N0-sbw">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="cancel:" target="-2" id="5BT-to-e4W"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="W4c-Xp-rpq" secondAttribute="trailing" id="D9j-IU-BZj"/>
|
||||
<constraint firstAttribute="bottom" secondItem="PD2-Zk-3yM" secondAttribute="bottom" constant="20" symbolic="YES" id="Qdc-tu-9kO"/>
|
||||
<constraint firstItem="W4c-Xp-rpq" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" id="V7Q-kM-JDA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="PD2-Zk-3yM" secondAttribute="trailing" constant="20" symbolic="YES" id="bQS-L4-jbx"/>
|
||||
<constraint firstItem="W4c-Xp-rpq" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" id="ec6-U0-t8X"/>
|
||||
<constraint firstItem="PD2-Zk-3yM" firstAttribute="top" secondItem="W4c-Xp-rpq" secondAttribute="bottom" constant="20" symbolic="YES" id="zlA-8I-aKr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="134" y="556"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,100 +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 = Account.oauthAuthorizationClient(for: .feedly)
|
||||
|
||||
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)
|
||||
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} catch {
|
||||
NSApplication.shared.presentError(error)
|
||||
}
|
||||
}
|
||||
}
|
BIN
Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf
vendored
Normal file
BIN
Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf
vendored
Normal file
Binary file not shown.
12
Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/Contents.json
vendored
Normal file
12
Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ArticleExtractorInactiveDark.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
12
Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/Contents.json
vendored
Normal file
12
Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ArticleExtractorInactiveLight.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -108,19 +108,18 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; };
|
||||
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.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 */; };
|
||||
|
@ -257,7 +255,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 */; };
|
||||
|
@ -276,7 +274,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 +304,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 +403,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 */; };
|
||||
|
@ -494,7 +489,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 */; };
|
||||
|
@ -615,8 +610,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 */; };
|
||||
9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; };
|
||||
9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; };
|
||||
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 */; };
|
||||
|
@ -1284,9 +1277,7 @@
|
|||
51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = "<group>"; };
|
||||
51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; };
|
||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
||||
519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = "<group>"; };
|
||||
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = "<group>"; };
|
||||
51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = "<group>"; };
|
||||
51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
|
||||
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1294,7 +1285,6 @@
|
|||
51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = "<group>"; };
|
||||
51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = "<group>"; };
|
||||
51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
||||
|
@ -1416,7 +1406,7 @@
|
|||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = "<group>"; };
|
||||
848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; };
|
||||
|
@ -1538,8 +1528,6 @@
|
|||
84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = "<group>"; };
|
||||
84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
|
||||
9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = "<group>"; };
|
||||
9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = "<group>"; };
|
||||
B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = "<group>"; };
|
||||
B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
||||
|
@ -1807,7 +1795,6 @@
|
|||
51A16990235E10D600EB091F /* Settings.storyboard */,
|
||||
51A16995235E10D600EB091F /* AboutViewController.swift */,
|
||||
51A16992235E10D600EB091F /* AddAccountViewController.swift */,
|
||||
51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */,
|
||||
516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */,
|
||||
516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */,
|
||||
516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */,
|
||||
|
@ -1838,15 +1825,6 @@
|
|||
path = Activity;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
519D740423243C68008BB345 /* Model Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */,
|
||||
51AF460D232488C6001742EF /* Account-Extensions.swift */,
|
||||
);
|
||||
path = "Model Extensions";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
51C45245226506C800C03939 /* UIKit Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1930,7 +1908,6 @@
|
|||
children = (
|
||||
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
|
||||
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
|
||||
5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */,
|
||||
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */,
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */,
|
||||
|
@ -1954,10 +1931,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 = "<group>";
|
||||
|
@ -2088,6 +2066,7 @@
|
|||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */,
|
||||
849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */,
|
||||
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* IconView.swift */,
|
||||
844B5B6B1FEA224B00C7C76A /* Keyboard */,
|
||||
849A975F1ED9EB95007D329B /* Sidebar */,
|
||||
849A97681ED9EBC8007D329B /* Timeline */,
|
||||
|
@ -2265,7 +2244,6 @@
|
|||
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */,
|
||||
849A97711ED9EC04007D329B /* TimelineCellData.swift */,
|
||||
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2510,8 +2488,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 */,
|
||||
|
@ -2566,7 +2542,6 @@
|
|||
5123DB95233EC69300282CC9 /* Inspector */,
|
||||
513145F9235A55A700387FDC /* Intents */,
|
||||
5183CCEB227117C70010922C /* Settings */,
|
||||
519D740423243C68008BB345 /* Model Extensions */,
|
||||
51C45245226506C800C03939 /* UIKit Extensions */,
|
||||
513C5CE7232571C2003D4054 /* ShareExtension */,
|
||||
51314643235A7C2300387FDC /* IntentsExtension */,
|
||||
|
@ -3359,7 +3334,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 */,
|
||||
|
@ -3445,7 +3419,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 */,
|
||||
|
@ -3743,7 +3716,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 */,
|
||||
|
@ -3762,7 +3735,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 */,
|
||||
|
@ -3793,8 +3765,8 @@
|
|||
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 */,
|
||||
518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */,
|
||||
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
|
||||
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
|
||||
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */,
|
||||
|
@ -3913,7 +3885,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,20 +3959,17 @@
|
|||
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 */,
|
||||
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 */,
|
||||
|
@ -4032,7 +4000,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 */,
|
||||
|
@ -4053,7 +4021,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 */,
|
||||
|
@ -4128,6 +4095,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 */,
|
||||
|
|
|
@ -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"] = "<td class=\"header rightAlign avatar\">\(avatarHTML)</td>";
|
||||
didAddAvatar = true
|
||||
}
|
||||
d["avatars"] = "<td class=\"header rightAlign avatar\"><img src=\"\(ArticleRenderer.imageIconScheme)://\" height=48 width=48 /></td>";
|
||||
|
||||
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"] = "<td class=\"header rightAlign\">\(favicon)</td>";
|
||||
}
|
||||
}
|
||||
|
||||
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 = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) style=\"border-radius:4px\" />"
|
||||
}
|
||||
else {
|
||||
imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) />"
|
||||
}
|
||||
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 "<img src=\"\(ArticleRenderer.imageIconScheme)://article.png\" height=48 width=48 />"
|
||||
}
|
||||
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let s = base64String(forImage: iconImage.image) {
|
||||
#if os(macOS)
|
||||
let imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=48 width=48 />"
|
||||
#else
|
||||
let imgTag = "<img src=\"data:image/png;base64, " + s + "\" height=48 width=48 />"
|
||||
#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 = "<img src=\"\(imageURL)\" width=\(dimension) height=\(dimension) />"
|
||||
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 ""
|
||||
|
|
|
@ -94,7 +94,7 @@ extension Article {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: PathIDUserInfoProvider
|
||||
// MARK: DeepLinkProvider
|
||||
|
||||
extension Article: DeepLinkProvider {
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -58,17 +58,17 @@ 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)
|
||||
|
||||
configureToolbar()
|
||||
becomeFirstResponder()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
navigationController?.title = NSLocalizedString("Feeds", comment: "Feeds")
|
||||
applyChanges(animate: false)
|
||||
updateUI()
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
@ -99,8 +99,17 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject)
|
||||
}
|
||||
|
||||
if let node = node, dataSource.indexPath(for: node) != nil {
|
||||
reloadNode(node)
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +149,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 {
|
||||
|
@ -457,14 +470,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1017,7 +1030,7 @@ private extension MasterFeedViewController {
|
|||
deleteCommand.perform()
|
||||
|
||||
if indexPath == coordinator.currentFeedIndexPath {
|
||||
coordinator.selectFeed(nil)
|
||||
coordinator.selectFeed(nil, animated: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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]? {
|
||||
|
@ -68,14 +69,14 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
resetEstimatedRowHeight()
|
||||
|
||||
resetUI()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
||||
applyChanges(animate: false)
|
||||
if dataSource.snapshot().numberOfItems < 1 {
|
||||
navigationItem.searchController?.isActive = false
|
||||
|
||||
// Restore the scroll position if we have one stored
|
||||
if let restoreIndexPath = coordinator.timelineMiddleIndexPath {
|
||||
tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
@ -288,6 +289,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 +371,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
titleView?.label.text = coordinator.timelineName
|
||||
}
|
||||
|
||||
@objc func scrollPositionDidChange() {
|
||||
coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow()
|
||||
}
|
||||
|
||||
// MARK: Reloading
|
||||
|
||||
func queueReloadAvailableCells() {
|
||||
|
@ -583,9 +592,8 @@ 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)
|
||||
}
|
||||
return action
|
||||
|
@ -594,9 +602,8 @@ 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)
|
||||
completionHandler(true)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "accountFeedly.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -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
|
||||
|
@ -135,6 +133,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
var timelineFetcher: ArticleFetcher? {
|
||||
didSet {
|
||||
|
||||
timelineMiddleIndexPath = nil
|
||||
|
||||
if timelineFetcher is Feed {
|
||||
showFeedNames = false
|
||||
} else {
|
||||
|
@ -153,6 +153,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
var timelineMiddleIndexPath: IndexPath?
|
||||
|
||||
private(set) var showFeedNames = false
|
||||
private(set) var showIcons = false
|
||||
|
||||
|
@ -276,6 +278,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
override init() {
|
||||
treeController = TreeController(delegate: treeControllerDelegate)
|
||||
|
||||
super.init()
|
||||
|
||||
for section in treeController.rootNode.childNodes {
|
||||
|
@ -311,10 +315,11 @@ 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
|
||||
let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: false)
|
||||
let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: true)
|
||||
rootSplitViewController.showDetailViewController(detailNavigationController, sender: self)
|
||||
|
||||
configureThreePanelMode(for: size)
|
||||
|
@ -323,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 {
|
||||
|
@ -367,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()
|
||||
|
@ -528,13 +533,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return indexPathFor(node)
|
||||
}
|
||||
|
||||
func selectFeed(_ indexPath: IndexPath?, animated: Bool = false) {
|
||||
guard indexPath != currentFeedIndexPath else { return }
|
||||
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
|
||||
|
@ -552,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -819,14 +824,14 @@ 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)
|
||||
}
|
||||
|
||||
func showAdd(_ type: AddControllerType, initialFeed: String? = nil, initialFeedName: String? = nil) {
|
||||
selectFeed(nil)
|
||||
selectFeed(nil, animated: false)
|
||||
|
||||
let addViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController
|
||||
|
||||
|
@ -965,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
|
||||
}
|
||||
|
||||
|
@ -1200,7 +1205,7 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
if unreadCountProvider.unreadCount > 0 {
|
||||
selectFeed(prevIndexPath)
|
||||
selectFeed(prevIndexPath, animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1306,7 +1311,7 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
if unreadCountProvider.unreadCount > 0 {
|
||||
selectFeed(nextIndexPath)
|
||||
selectFeed(nextIndexPath, animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1488,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1589,7 +1596,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!
|
||||
|
@ -1642,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1663,7 +1670,7 @@ private extension SceneCoordinator {
|
|||
return
|
||||
}
|
||||
if let indexPath = indexPathFor(folderNode) {
|
||||
selectFeed(indexPath)
|
||||
selectFeed(indexPath, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -60,32 +60,8 @@
|
|||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Feeds" id="hAC-uA-RbS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="qur-cL-wrM" detailTextLabel="qIl-N6-6wQ" style="IBUITableViewCellStyleValue1" id="z1J-VF-St0" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="255.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="z1J-VF-St0" id="Y8U-Ka-GeZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Refresh Interval" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qur-cL-wrM">
|
||||
<rect key="frame" x="20" y="12" width="119.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qIl-N6-6wQ" customClass="VibrantLabel" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="291" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="4Hg-B3-zAE" style="IBUITableViewCellStyleDefault" id="glf-Pg-s3P" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="299.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="255.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="glf-Pg-s3P" id="bPA-43-Oqh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -102,7 +78,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="25J-iX-3at" style="IBUITableViewCellStyleDefault" id="qke-Ha-PXl" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="343.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="299.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qke-Ha-PXl" id="pZi-ck-RV5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -119,7 +95,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="dXN-Mw-yf2" style="IBUITableViewCellStyleDefault" id="F0L-Ut-reX" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="387.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="343.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="F0L-Ut-reX" id="5SX-M2-2jR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -140,7 +116,7 @@
|
|||
<tableViewSection headerTitle="Timeline" id="9Pk-Y8-JVJ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="MpA-w1-Wwh" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="487.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="443.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="MpA-w1-Wwh" id="GhU-ib-Mz8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -171,7 +147,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="f7r-AZ-aDn" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="531.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="487.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="f7r-AZ-aDn" id="KHC-cc-tOC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -202,7 +178,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="6C6-JQ-lfQ" style="IBUITableViewCellStyleDefault" id="5wo-fM-0l6" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="575.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="531.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5wo-fM-0l6" id="XAn-lK-LoN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
|
@ -223,7 +199,7 @@
|
|||
<tableViewSection headerTitle="About" id="TkH-4v-yhk">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="2o6-8W-nyK" style="IBUITableViewCellStyleDefault" id="he9-Ql-yfa" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="675.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="631.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="he9-Ql-yfa" id="q6L-C8-H9a">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
|
@ -240,7 +216,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="lOk-Dh-GfZ" style="IBUITableViewCellStyleDefault" id="GWZ-jk-qU6" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="719.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="675.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GWZ-jk-qU6" id="ZgS-bo-xDl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -257,7 +233,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Pm8-6D-fdE" style="IBUITableViewCellStyleDefault" id="3cU-BG-6kK" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="763.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="719.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3cU-BG-6kK" id="Qm0-SY-0vx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -274,7 +250,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="TEA-EG-V6d" style="IBUITableViewCellStyleDefault" id="4yc-ig-I61" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="807.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="763.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4yc-ig-I61" id="uQl-VP-9p9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -291,7 +267,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Q9a-Pi-uCc" style="IBUITableViewCellStyleDefault" id="mSW-A7-8lf" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="851.5" width="374" height="44"/>
|
||||
<rect key="frame" x="20" y="807.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="mSW-A7-8lf" id="shF-ro-Zpx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -308,7 +284,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="dWz-1o-EpJ" style="IBUITableViewCellStyleDefault" id="2MG-qn-idJ" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="895.5" width="374" height="44"/>
|
||||
<rect key="frame" x="0.0" y="851.5" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2MG-qn-idJ" id="gP9-ry-keC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
|
@ -424,6 +400,39 @@
|
|||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="56" id="zcM-qz-glk" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="129" width="374" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="zcM-qz-glk" id="3VG-Ax-7gi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="cXZ-17-bhe">
|
||||
<rect key="frame" x="20" y="12" width="128" height="32"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="accountFeedly" translatesAutoresizingMaskIntoConstraints="NO" id="fAO-P0-gtD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="32" height="32"/>
|
||||
<color key="tintColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="32" id="581-u2-SxX"/>
|
||||
<constraint firstAttribute="height" constant="32" id="onv-oj-10a"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Feedly" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="u2M-c5-ujy">
|
||||
<rect key="frame" x="48" y="0.0" width="80" height="32"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="cXZ-17-bhe" firstAttribute="leading" secondItem="3VG-Ax-7gi" secondAttribute="leading" constant="20" symbolic="YES" id="BYO-oH-a6T"/>
|
||||
<constraint firstItem="cXZ-17-bhe" firstAttribute="centerY" secondItem="3VG-Ax-7gi" secondAttribute="centerY" id="r36-pZ-Siw"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
|
@ -441,35 +450,6 @@
|
|||
</objects>
|
||||
<point key="canvasLocation" x="983" y="151"/>
|
||||
</scene>
|
||||
<!--Refresh Interval-->
|
||||
<scene sceneID="5WY-bu-OPU">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="RefreshIntervalViewController" title="Refresh Interval" useStoryboardIdentifierAsRestorationIdentifier="YES" id="Vd0-lF-iff" customClass="RefreshIntervalViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="KyE-ob-CYm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="91W-kj-0Dw" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="55.5" width="374" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="91W-kj-0Dw" id="AXy-Ti-xiS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Vd0-lF-iff" id="ZDd-4x-0M5"/>
|
||||
<outlet property="delegate" destination="Vd0-lF-iff" id="3tH-oh-oZ3"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Refresh Interval" id="lIq-gS-6ui"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" translucent="NO" prompted="NO"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="PkF-Up-3qC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1655" y="151"/>
|
||||
</scene>
|
||||
<!--About-->
|
||||
<scene sceneID="pWd-ql-XAA">
|
||||
<objects>
|
||||
|
@ -647,7 +627,7 @@
|
|||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kRt-nH-nOf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2330" y="151"/>
|
||||
<point key="canvasLocation" x="1680" y="151"/>
|
||||
</scene>
|
||||
<!--Timeline Layout-->
|
||||
<scene sceneID="XRu-Jc-bbU">
|
||||
|
@ -750,7 +730,7 @@
|
|||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iNo-Vj-YZx" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2995.6521739130435" y="150.66964285714286"/>
|
||||
<point key="canvasLocation" x="2346" y="151"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="Ezn-Ny-zye">
|
||||
|
@ -806,11 +786,12 @@
|
|||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Oq6-5f-Oa7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3697" y="151"/>
|
||||
<point key="canvasLocation" x="3096" y="151"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="accountFeedbin" width="120" height="102"/>
|
||||
<image name="accountFeedly" width="138" height="123"/>
|
||||
<image name="accountLocal" width="99" height="77"/>
|
||||
<namedColor name="primaryAccentColor">
|
||||
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 972ff3237f819a2250e0bc1ca2814bafe328fa69
|
||||
Subproject commit ba7bbb2ce10ee04a730c0a1e425a1b2e9d338520
|
Loading…
Reference in New Issue