This commit is contained in:
Andrew Brehaut
2019-09-25 12:18:28 +12:00
23 changed files with 385 additions and 100 deletions

View File

@@ -48,8 +48,8 @@ private extension AccountMetadataFile {
if let fileData = try? Data(contentsOf: readURL) {
let decoder = PropertyListDecoder()
account.metadata = (try? decoder.decode(AccountMetadata.self, from: fileData)) ?? AccountMetadata()
account.metadata.delegate = account
}
account.metadata.delegate = account
})
if let error = errorPointer?.pointee {

View File

@@ -48,10 +48,10 @@ private extension FeedMetadataFile {
if let fileData = try? Data(contentsOf: readURL) {
let decoder = PropertyListDecoder()
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
account.feedMetadata.values.forEach { $0.delegate = account }
if !account.startingUp {
account.resetFeedMetadataAndUnreadCounts()
}
}
account.feedMetadata.values.forEach { $0.delegate = account }
if !account.startingUp {
account.resetFeedMetadataAndUnreadCounts()
}
})

View File

@@ -57,7 +57,7 @@ class ArticleExtractorButton: NSButton {
case isError:
addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorError, opacity: opacity)
case isInProgress:
addProgressSublayer(to: hostedLayer)
addAnimatedSublayer(to: hostedLayer)
default:
addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity)
}
@@ -77,7 +77,7 @@ class ArticleExtractorButton: NSButton {
hostedLayer.addSublayer(imageLayer)
}
private func addProgressSublayer(to hostedLayer: CALayer) {
private func addAnimatedSublayer(to hostedLayer: CALayer) {
let imageProgress1 = AppAssets.articleExtractorProgress1
let imageProgress2 = AppAssets.articleExtractorProgress2
let imageProgress3 = AppAssets.articleExtractorProgress3

View File

@@ -10,6 +10,7 @@
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; };
@@ -81,7 +82,7 @@
5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */; };
517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */; };
5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; };
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; };
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; };
@@ -144,7 +145,7 @@
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */; };
51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */; };
51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */; };
51C4527F2265092C00C03939 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* DetailViewController.swift */; };
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; };
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; };
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddFeedViewController.swift */; };
@@ -780,6 +781,7 @@
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = "<group>"; };
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; };
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; };
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
@@ -819,7 +821,7 @@
515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = "<group>"; };
51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = "<group>"; };
517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewControllerWebViewProvider.swift; sourceTree = "<group>"; };
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewControllerWebViewProvider.swift; sourceTree = "<group>"; };
5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = "<group>"; };
5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = "<group>"; };
5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = "<group>"; };
@@ -853,7 +855,7 @@
51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineTableViewCell.swift; sourceTree = "<group>"; };
51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterUnreadIndicatorView.swift; sourceTree = "<group>"; };
51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineDefaultCellLayout.swift; sourceTree = "<group>"; };
51C4527E2265092C00C03939 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = "<group>"; };
51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = "<group>"; };
51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = "<group>"; };
51C452842265093600C03939 /* AddFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFeedViewController.swift; sourceTree = "<group>"; };
@@ -1375,13 +1377,14 @@
path = Cell;
sourceTree = "<group>";
};
51C4527D2265092C00C03939 /* Detail */ = {
51C4527D2265092C00C03939 /* Article */ = {
isa = PBXGroup;
children = (
51C4527E2265092C00C03939 /* DetailViewController.swift */,
517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */,
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
);
path = Detail;
path = Article;
sourceTree = "<group>";
};
51C452802265093600C03939 /* Add */ = {
@@ -1963,7 +1966,7 @@
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */,
51C4525D226508F600C03939 /* MasterFeed */,
51C4526D2265091600C03939 /* MasterTimeline */,
51C4527D2265092C00C03939 /* Detail */,
51C4527D2265092C00C03939 /* Article */,
51C452802265093600C03939 /* Add */,
5183CCEB227117C70010922C /* Settings */,
5183CCDB226F1EEB0010922C /* Progress */,
@@ -2160,7 +2163,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */;
buildPhases = (
517D2D80233A46ED00FF3E35 /* Update ArticleExtractorConfig.swift */,
840D61782029031C009BC708 /* Sources */,
517D2D81233A47AD00FF3E35 /* Reset ArticleExtractorConfig.swift */,
840D61792029031C009BC708 /* Frameworks */,
840D617A2029031C009BC708 /* Resources */,
51C451DF2264C7F200C03939 /* Embed Frameworks */,
@@ -2190,8 +2195,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */;
buildPhases = (
51D6803823330CFF0097A009 /* Update Feedbin Mercury API Keys */,
51D6803823330CFF0097A009 /* Update ArticleExtractorConfig.swift */,
849C645C1ED37A5D003D8FC0 /* Sources */,
517D2D82233A53D600FF3E35 /* Reset ArticleExtractorConfig.swift */,
849C645D1ED37A5D003D8FC0 /* Frameworks */,
849C645E1ED37A5D003D8FC0 /* Resources */,
84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */,
@@ -2603,7 +2609,7 @@
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";
};
51D6803823330CFF0097A009 /* Update Feedbin Mercury API Keys */ = {
517D2D80233A46ED00FF3E35 /* Update ArticleExtractorConfig.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -2612,14 +2618,68 @@
);
inputPaths = (
);
name = "Update Feedbin Mercury API Keys";
name = "Update ArticleExtractorConfig.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "FAILED=false\n\nif [ \"${CONFIGURATION}\" = \"Debug\" ]; then\necho \"Not a release build. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\necho \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift:21:1: warning: Missing Feedbin Mercury Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\necho \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift:22:1: warning: Missing Feedbin Mercury Client Secret\"\nFAILED=true\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\nif [ \"$FAILED\" = true ]; then\nexit 1\nfi\n\necho \"All env values found!\"\n";
shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n";
};
517D2D81233A47AD00FF3E35 /* Reset ArticleExtractorConfig.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Reset ArticleExtractorConfig.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n";
};
517D2D82233A53D600FF3E35 /* Reset ArticleExtractorConfig.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Reset ArticleExtractorConfig.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n";
};
51D6803823330CFF0097A009 /* Update ArticleExtractorConfig.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Update ArticleExtractorConfig.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n";
};
8423E3E3220158E700C3795B /* Run Script: codesign release builds */ = {
isa = PBXShellScriptBuildPhase;
@@ -2714,7 +2774,7 @@
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */,
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */,
517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */,
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */,
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
@@ -2763,7 +2823,7 @@
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */,
51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */,
51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */,
51C4527F2265092C00C03939 /* DetailViewController.swift in Sources */,
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */,
51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */,
51C452AE2265104D00C03939 /* TimelineStringFormatter.swift in Sources */,
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */,
@@ -2772,6 +2832,7 @@
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,

View File

@@ -119,8 +119,8 @@ class ActivityManager {
}
func invalidateReading() {
nextUnreadActivity?.invalidate()
nextUnreadActivity = nil
readingActivity?.invalidate()
readingActivity = nil
}
static func cleanUp(_ account: Account) {

View File

@@ -21,12 +21,6 @@ protocol ArticleExtractorDelegate {
func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
}
enum ArticleExtractorError: Error {
case UnableToParseHTML
case MissingURL
case UnableToLoadURL
}
class ArticleExtractor {
private var dataTask: URLSessionDataTask? = nil
@@ -41,9 +35,9 @@ class ArticleExtractor {
public init?(_ articleLink: String) {
self.articleLink = articleLink
let clientURL = ArticleExtractorConfig.Mercury.clientURL
let username = ArticleExtractorConfig.Mercury.clientId
let signiture = articleLink.hmacUsingSHA1(key: ArticleExtractorConfig.Mercury.clientSecret)
let clientURL = ArticleExtractorConfig.clientURL
let username = ArticleExtractorConfig.clientId
let signiture = articleLink.hmacUsingSHA1(key: ArticleExtractorConfig.clientSecret)
if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() {
let fullURL = "\(clientURL)/\(username)/\(signiture)?base64_url=\(base64URL)"
@@ -75,7 +69,7 @@ class ArticleExtractor {
guard let data = data else {
self.state = .failedToParse
DispatchQueue.main.async {
self.delegate?.articleExtractionDidFail(with: ArticleExtractorError.UnableToLoadURL)
self.delegate?.articleExtractionDidFail(with: URLError(.cannotDecodeContentData))
}
return
}

View File

@@ -9,27 +9,7 @@
import Foundation
enum ArticleExtractorConfig {
enum Mercury {
// For testing add the environment variables in the scheme you are using
static let clientId = ArticleExtractorConfig.environmentVariable(named: "MERCURY_CLIENT_ID") ?? Release.mercuryId
static let clientSecret = ArticleExtractorConfig.environmentVariable(named: "MERCURY_CLIENT_SECRET") ?? Release.mercurySecret
static let clientURL = Release.mercuryURL
}
private enum Release {
static let mercuryId = "{MERCURYID}"
static let mercurySecret = "{MERCURYSECRET}"
static let mercuryURL = "https://extract.feedbin.com/parser"
}
private static func environmentVariable(named: String) -> String? {
let processInfo = ProcessInfo.processInfo
guard let value = processInfo.environment[named] else {
print("‼️ Missing Environment Variable: '\(named)'")
return nil
}
return value
}
static let clientId = "{MERCURYID}"
static let clientSecret = "{MERCURYSECRET}"
static let clientURL = "https://extract.feedbin.com/parser"
}

View File

@@ -10,6 +10,28 @@ import RSCore
struct AppAssets {
static var articleExtractorError: UIImage = {
return UIImage(named: "articleExtractorOff")!
}()
static var articleExtractorOff: UIImage = {
return UIImage(named: "articleExtractorOff")!
}()
static var articleExtractorOffTinted: UIImage = {
let image = UIImage(named: "articleExtractorOff")!
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
}()
static var articleExtractorOn: UIImage = {
return UIImage(named: "articleExtractorOn")!
}()
static var articleExtractorOnTinted: UIImage = {
let image = UIImage(named: "articleExtractorOn")!
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
}()
static var avatarBackgroundColor: UIColor = {
return UIColor(named: "avatarBackgroundColor")!
}()

View File

@@ -54,7 +54,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
appDelegate = self
// Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views
let _ = DetailViewControllerWebViewProvider.shared
let _ = ArticleViewControllerWebViewProvider.shared
AccountManager.shared = AccountManager()
AppDefaults.shared = UserDefaults.init(suiteName: "group.\(Bundle.main.bundleIdentifier!)")!

View File

@@ -0,0 +1,78 @@
//
// ArticleExtractorButton.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/24/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
enum ArticleExtractorButtonState {
case error
case animated
case on
case off
}
class ArticleExtractorButton: UIButton {
var buttonState: ArticleExtractorButtonState = .off {
didSet {
if buttonState != oldValue {
switch buttonState {
case .error:
stripSublayer()
setImage(AppAssets.articleExtractorError, for: .normal)
case .animated:
setImage(nil, for: .normal)
setNeedsLayout()
case .on:
stripSublayer()
setImage(AppAssets.articleExtractorOn, for: .normal)
case .off:
stripSublayer()
setImage(AppAssets.articleExtractorOff, for: .normal)
}
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard case .animated = buttonState else {
return
}
stripSublayer()
addAnimatedSublayer(to: layer)
}
private func stripSublayer() {
if layer.sublayers?.count ?? 0 > 1 {
layer.sublayers?.last?.removeFromSuperlayer()
}
}
private func addAnimatedSublayer(to hostedLayer: CALayer) {
let image1 = AppAssets.articleExtractorOffTinted.cgImage!
let image2 = AppAssets.articleExtractorOnTinted.cgImage!
let images = [image1, image2, image1]
let imageLayer = CALayer()
let imageSize = AppAssets.articleExtractorOff.size
imageLayer.bounds = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
hostedLayer.addSublayer(imageLayer)
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = CAAnimationCalculationMode.linear
animation.keyTimes = [0, 0.5, 1]
animation.duration = 2
animation.values = images as [Any]
animation.repeatCount = HUGE
imageLayer.add(animation, forKey: "contents")
}
}

View File

@@ -1,5 +1,5 @@
//
// DetailViewController.swift
// ArticleViewController.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/8/19.
@@ -12,7 +12,7 @@ import Account
import Articles
import SafariServices
enum DetailViewState: Equatable {
enum ArticleViewState: Equatable {
case noSelection
case multipleSelection
case loading
@@ -20,8 +20,9 @@ enum DetailViewState: Equatable {
case extracted(Article, ExtractedArticle)
}
class DetailViewController: UIViewController {
class ArticleViewController: UIViewController {
@IBOutlet private weak var articleExtractorButton: ArticleExtractorButton!
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var prevArticleBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var nextArticleBarButtonItem: UIBarButtonItem!
@@ -34,7 +35,7 @@ class DetailViewController: UIViewController {
weak var coordinator: SceneCoordinator!
var state: DetailViewState = .noSelection {
var state: ArticleViewState = .noSelection {
didSet {
if state != oldValue {
updateUI()
@@ -54,6 +55,15 @@ class DetailViewController: UIViewController {
}
}
var articleExtractorButtonState: ArticleExtractorButtonState {
get {
return articleExtractorButton.buttonState
}
set {
articleExtractorButton.buttonState = newValue
}
}
private let keyboardManager = KeyboardManager(type: .detail)
override var keyCommands: [UIKeyCommand]? {
return keyboardManager.keyCommands
@@ -61,7 +71,7 @@ class DetailViewController: UIViewController {
deinit {
webView.removeFromSuperview()
DetailViewControllerWebViewProvider.shared.enqueueWebView(webView)
ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView)
webView = nil
}
@@ -73,7 +83,10 @@ class DetailViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
DetailViewControllerWebViewProvider.shared.dequeueWebView() { webView in
// For some reason interface builder won't let me set this there.
articleExtractorButton.addTarget(self, action: #selector(toggleArticleExtractor(_:)), for: .touchUpInside)
ArticleViewControllerWebViewProvider.shared.dequeueWebView() { webView in
self.webView = webView
self.webViewContainer.addChildAndPin(webView)
@@ -95,6 +108,7 @@ class DetailViewController: UIViewController {
func updateUI() {
guard let article = currentArticle else {
articleExtractorButton.isEnabled = false
nextUnreadBarButtonItem.isEnabled = false
prevArticleBarButtonItem.isEnabled = false
nextArticleBarButtonItem.isEnabled = false
@@ -109,6 +123,7 @@ class DetailViewController: UIViewController {
prevArticleBarButtonItem.isEnabled = coordinator.isPrevArticleAvailable
nextArticleBarButtonItem.isEnabled = coordinator.isNextArticleAvailable
articleExtractorButton.isEnabled = true
readBarButtonItem.isEnabled = true
starBarButtonItem.isEnabled = true
browserBarButtonItem.isEnabled = true
@@ -178,6 +193,10 @@ class DetailViewController: UIViewController {
// MARK: Actions
@IBAction func toggleArticleExtractor(_ sender: Any) {
coordinator.toggleArticleExtractor()
}
@IBAction func nextUnread(_ sender: Any) {
coordinator.selectNextUnread()
}
@@ -248,7 +267,7 @@ class DetailViewController: UIViewController {
// MARK: WKNavigationDelegate
extension DetailViewController: WKNavigationDelegate {
extension ArticleViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
@@ -284,7 +303,7 @@ extension DetailViewController: WKNavigationDelegate {
// MARK: Private
private extension DetailViewController {
private extension ArticleViewController {
func updateProgressIndicatorIfNeeded() {
if !(UIDevice.current.userInterfaceIdiom == .pad) {

View File

@@ -1,5 +1,5 @@
//
// DetailViewControllerWebViewProvider.swift
// ArticleViewControllerWebViewProvider.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/21/19.
@@ -11,9 +11,9 @@ import WebKit
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode.
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate {
class ArticleViewControllerWebViewProvider: NSObject, WKNavigationDelegate {
static let shared = DetailViewControllerWebViewProvider()
static let shared = ArticleViewControllerWebViewProvider()
private let minimumQueueDepth = 3
private let maximumQueueDepth = 6

View File

@@ -7,16 +7,16 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Detail-->
<!--Article-->
<scene sceneID="yUG-lL-AsK">
<objects>
<viewController storyboardIdentifier="DetailViewController" title="Detail" useStoryboardIdentifierAsRestorationIdentifier="YES" id="JEX-9P-axG" customClass="DetailViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="ArticleViewController" title="Detail" useStoryboardIdentifierAsRestorationIdentifier="YES" id="JEX-9P-axG" userLabel="Article" customClass="ArticleViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="svH-Pt-448">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DNb-lt-KzC">
<rect key="frame" x="0.0" y="44" width="414" height="769"/>
<rect key="frame" x="0.0" y="88" width="414" height="725"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
@@ -98,10 +98,20 @@
</connections>
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM"/>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM">
<barButtonItem key="rightBarButtonItem" style="plain" id="n8q-YO-ldL">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="n73-9B-rav" customClass="ArticleExtractorButton" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="362" y="5" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="articleExtractorOff"/>
</button>
</barButtonItem>
</navigationItem>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="actionBarButtonItem" destination="9Ut-5B-JKP" id="9bO-kz-cTz"/>
<outlet property="articleExtractorButton" destination="n73-9B-rav" id="zud-XU-Kkx"/>
<outlet property="browserBarButtonItem" destination="DMh-3X-ebd" id="PkT-Tn-8kG"/>
<outlet property="nextArticleBarButtonItem" destination="2qz-M5-Yhk" id="IQd-jx-qEr"/>
<outlet property="nextUnreadBarButtonItem" destination="2w5-e9-C2V" id="xJr-5y-p1N"/>
@@ -152,6 +162,7 @@
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" title="Timeline" largeTitleDisplayMode="never" id="wcC-1L-ug4"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="firstUnreadButton" destination="2v2-jD-C9k" id="8NP-Uc-3Fn"/>
@@ -203,7 +214,10 @@
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" title="Feeds" id="Zdf-7t-Un8">
<barButtonItem key="leftBarButtonItem" title="Item" image="gear" catalog="system" id="TlU-Pg-ATe">
<barButtonItem key="leftBarButtonItem" title="Settings" image="gear" catalog="system" id="TlU-Pg-ATe">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Settings"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="settings:" destination="7bK-jq-Zjz" id="Y8a-lz-Im7"/>
</connections>
@@ -225,6 +239,7 @@
<image name="arrow.down" catalog="system" width="58" height="64"/>
<image name="arrow.down.circle" catalog="system" width="64" height="62"/>
<image name="arrow.up" catalog="system" width="58" height="64"/>
<image name="articleExtractorOff" width="18" height="23"/>
<image name="circle" catalog="system" width="64" height="62"/>
<image name="gear" catalog="system" width="64" height="60"/>
<image name="safari" catalog="system" width="64" height="62"/>

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOff.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOff.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ArticleExtractorOn.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -27,6 +27,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private var activityManager = ActivityManager()
private var isShowingExtractedArticle = false
private var articleExtractor: ArticleExtractor? = nil
private var rootSplitViewController: RootSplitViewController!
private var masterNavigationController: UINavigationController!
private var masterFeedViewController: MasterFeedViewController!
@@ -36,17 +39,17 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return rootSplitViewController.children.last as? UISplitViewController
}
private var detailViewController: DetailViewController? {
if let detail = masterNavigationController.viewControllers.last as? DetailViewController {
private var articleViewController: ArticleViewController? {
if let detail = masterNavigationController.viewControllers.last as? ArticleViewController {
return detail
}
if let subSplit = subSplitViewController {
if let navController = subSplit.viewControllers.last as? UINavigationController {
return navController.topViewController as? DetailViewController
return navController.topViewController as? ArticleViewController
}
} else {
if let navController = rootSplitViewController.viewControllers.last as? UINavigationController {
return navController.topViewController as? DetailViewController
return navController.topViewController as? ArticleViewController
}
}
return nil
@@ -289,9 +292,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
masterFeedViewController.coordinator = self
masterNavigationController.pushViewController(masterFeedViewController, animated: false)
let detailViewController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self)
detailViewController.coordinator = self
let detailNavigationController = addNavControllerIfNecessary(detailViewController, showButton: false)
let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self)
articleViewController.coordinator = self
let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: false)
rootSplitViewController.showDetailViewController(detailNavigationController, sender: self)
configureThreePanelMode(for: size)
@@ -553,32 +556,37 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
func selectArticle(_ article: Article?, automated: Bool = true) {
guard article != currentArticle else { return }
articleExtractor?.cancel()
articleExtractor = nil
isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
currentArticle = article
activityManager.reading(currentArticle)
if article == nil {
if rootSplitViewController.isCollapsed {
if masterNavigationController.children.last is DetailViewController {
if masterNavigationController.children.last is ArticleViewController {
masterNavigationController.popViewController(animated: !automated)
}
} else {
detailViewController?.state = .noSelection
articleViewController?.state = .noSelection
}
masterTimelineViewController?.updateArticleSelection(animate: !automated)
return
}
if detailViewController == nil {
let detailViewController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self)
detailViewController.coordinator = self
installDetailController(detailViewController, automated: automated)
if articleViewController == nil {
let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self)
articleViewController.coordinator = self
installArticleController(articleViewController, automated: automated)
}
if automated {
masterTimelineViewController?.updateArticleSelection(animate: false)
}
detailViewController?.state = .article(article!)
articleViewController?.state = .article(article!)
if let article = currentArticle {
markArticles(Set([article]), statusKey: .read, flag: true)
@@ -686,8 +694,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
func scrollOrGoToNextUnread() {
if detailViewController?.canScrollDown() ?? false {
detailViewController?.scrollPageDown()
if articleViewController?.canScrollDown() ?? false {
articleViewController?.scrollPageDown()
} else {
selectNextUnread()
}
@@ -795,6 +803,40 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
masterFeedViewController.present(addViewController, animated: true)
}
func toggleArticleExtractor() {
guard let article = currentArticle else {
return
}
guard articleExtractor?.state != .processing else {
articleExtractor?.cancel()
articleExtractor = nil
isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article)
return
}
guard !isShowingExtractedArticle else {
isShowingExtractedArticle = false
articleViewController?.articleExtractorButtonState = .off
articleViewController?.state = .article(article)
return
}
if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article {
if currentArticle?.preferredLink == articleExtractor.articleLink {
isShowingExtractedArticle = true
articleViewController?.articleExtractorButtonState = .on
articleViewController?.state = .extracted(article, extractedArticle)
}
} else {
startArticleExtractorForCurrentLink()
}
}
func homePageURLForFeed(_ indexPath: IndexPath) -> URL? {
guard let node = nodeFor(indexPath),
let feed = node.representedObject as? Feed,
@@ -844,7 +886,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
func navigateToDetail() {
detailViewController?.focus()
articleViewController?.focus()
}
}
@@ -880,6 +922,24 @@ extension SceneCoordinator: UINavigationControllerDelegate {
}
// MARK: ArticleExtractorDelegate
extension SceneCoordinator: ArticleExtractorDelegate {
func articleExtractionDidFail(with: Error) {
articleViewController?.articleExtractorButtonState = .error
}
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
if let article = currentArticle, articleExtractor?.state != .cancelled {
isShowingExtractedArticle = true
articleViewController?.state = .extracted(article, extractedArticle)
articleViewController?.articleExtractorButtonState = .on
}
}
}
// MARK: Private
private extension SceneCoordinator {
@@ -1181,6 +1241,15 @@ private extension SceneCoordinator {
// MARK: Fetching Articles
func startArticleExtractorForCurrentLink() {
if let link = currentArticle?.preferredLink, let extractor = ArticleExtractor(link) {
extractor.delegate = self
extractor.process()
articleExtractor = extractor
articleViewController?.articleExtractorButtonState = .animated
}
}
func emptyTheTimeline() {
if !articles.isEmpty {
replaceArticles(with: Set<Article>(), animate: true)
@@ -1340,16 +1409,16 @@ private extension SceneCoordinator {
}
}
func installDetailController(_ detailController: UIViewController, automated: Bool) {
func installArticleController(_ articleController: UIViewController, automated: Bool) {
if let subSplit = subSplitViewController {
let controller = addNavControllerIfNecessary(detailController, showButton: false)
let controller = addNavControllerIfNecessary(articleController, showButton: false)
subSplit.showDetailViewController(controller, sender: self)
} else if rootSplitViewController.isCollapsed {
let controller = addNavControllerIfNecessary(detailController, showButton: false)
let controller = addNavControllerIfNecessary(articleController, showButton: false)
masterNavigationController.pushViewController(controller, animated: !automated)
} else {
let controller = addNavControllerIfNecessary(detailController, showButton: true)
let controller = addNavControllerIfNecessary(articleController, showButton: true)
rootSplitViewController.showDetailViewController(controller, sender: self)
}
@@ -1406,12 +1475,12 @@ private extension SceneCoordinator {
}
let controller: UIViewController = {
if let result = detailViewController {
if let result = articleViewController {
return result
} else {
let detailController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self)
detailController.coordinator = self
return detailController
let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self)
articleViewController.coordinator = self
return articleViewController
}
}()

View File

@@ -30,6 +30,7 @@ PROVISIONING_PROFILE_SPECIFIER =
// /Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig
//
#include? "../../SharedXcodeSettings/ProjectSettings.xcconfig"
#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig"
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES

View File

@@ -1,3 +1,5 @@
#include? "../../SharedXcodeSettings/ProjectSettings.xcconfig"
ALWAYS_SEARCH_USER_PATHS = NO
CLANG_ANALYZER_NONNULL = YES
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;

View File

@@ -1,4 +1,3 @@
#include? "../../SharedXcodeSettings/ReleaseSettings.xcconfig"
#include "./NetNewsWire_project.xcconfig"
COPY_PHASE_STRIP = NO