Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
8fcc61b769
|
@ -29,6 +29,7 @@ public extension Notification.Name {
|
|||
static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles")
|
||||
static let AccountStateDidChange = Notification.Name(rawValue: "AccountStateDidChange")
|
||||
static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange")
|
||||
static let FeedMetadataDidChange = Notification.Name(rawValue: "FeedMetadataDidChange")
|
||||
}
|
||||
|
||||
public enum AccountType: Int {
|
||||
|
@ -401,6 +402,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
|
||||
}
|
||||
|
||||
public func resetAllFeedMetadata() {
|
||||
for feed in flattenedFeeds() {
|
||||
feed.metadata = feedMetadata(feedURL: feed.url, feedID: feed.feedID)
|
||||
}
|
||||
NotificationCenter.default.post(name: .FeedMetadataDidChange, object: self, userInfo: nil)
|
||||
}
|
||||
|
||||
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
return delegate.markArticles(for: self, articles: articles, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
|
|
@ -72,6 +72,14 @@ public final class AccountManager: UnreadCountProvider {
|
|||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
|
||||
public convenience init() {
|
||||
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
let accountsURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
|
||||
let accountsFolder = accountsURL!.appendingPathComponent("Accounts").absoluteString
|
||||
let accountsFolderPath = accountsFolder.suffix(from: accountsFolder.index(accountsFolder.startIndex, offsetBy: 7))
|
||||
self.init(accountsFolder: String(accountsFolderPath))
|
||||
}
|
||||
|
||||
public init(accountsFolder: String) {
|
||||
self.accountsFolder = accountsFolder
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ 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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -55,7 +56,6 @@ private extension AccountMetadataFile {
|
|||
os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription)
|
||||
}
|
||||
|
||||
account.metadata.delegate = account
|
||||
}
|
||||
|
||||
func saveCallback() {
|
||||
|
|
|
@ -10,6 +10,15 @@ import Foundation
|
|||
|
||||
public struct CredentialsManager {
|
||||
|
||||
private static var keychainGroup: String? = {
|
||||
guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else {
|
||||
return nil
|
||||
}
|
||||
let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String
|
||||
let appGroupSuffix = appGroup.suffix(appGroup.count - 6)
|
||||
return "\(appIdentifierPrefix)\(appGroupSuffix)"
|
||||
}()
|
||||
|
||||
public static func storeCredentials(_ credentials: Credentials, server: String) throws {
|
||||
|
||||
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
||||
|
@ -20,6 +29,10 @@ public struct CredentialsManager {
|
|||
query[kSecAttrSecurityDomain as String] = credentials.type.rawValue
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -59,6 +72,10 @@ public struct CredentialsManager {
|
|||
query[kSecAttrSecurityDomain as String] = type.rawValue
|
||||
}
|
||||
|
||||
if let securityGroup = keychainGroup {
|
||||
query[kSecAttrAccessGroup as String] = securityGroup
|
||||
}
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
|
||||
|
@ -93,6 +110,10 @@ public struct CredentialsManager {
|
|||
query[kSecAttrSecurityDomain as String] = type.rawValue
|
||||
}
|
||||
|
||||
if let securityGroup = keychainGroup {
|
||||
query[kSecAttrAccessGroup as String] = securityGroup
|
||||
}
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
guard status == errSecSuccess || status == errSecItemNotFound else {
|
||||
throw CredentialsError.unhandledError(status: status)
|
||||
|
|
|
@ -196,10 +196,11 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
}
|
||||
}
|
||||
|
||||
var metadata: FeedMetadata
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private let accountID: String // Used for hashing and equality; account may turn nil
|
||||
private let metadata: FeedMetadata
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ 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 }
|
||||
account.resetAllFeedMetadata()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -55,7 +57,6 @@ private extension FeedMetadataFile {
|
|||
os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription)
|
||||
}
|
||||
|
||||
account.feedMetadata.values.forEach { $0.delegate = account }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; };
|
||||
5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; };
|
||||
5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; };
|
||||
5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; };
|
||||
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; };
|
||||
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
|
||||
514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; };
|
||||
5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */; };
|
||||
|
@ -808,6 +810,8 @@
|
|||
5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = "<group>"; };
|
||||
5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = "<group>"; };
|
||||
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = "<group>"; };
|
||||
5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = "<group>"; };
|
||||
5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = "<group>"; };
|
||||
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
|
||||
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = "<group>"; };
|
||||
51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -1350,6 +1354,8 @@
|
|||
children = (
|
||||
51C4526E2265091600C03939 /* MasterTimelineViewController.swift */,
|
||||
51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */,
|
||||
5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */,
|
||||
5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */,
|
||||
51C4526F2265091600C03939 /* Cell */,
|
||||
);
|
||||
path = MasterTimeline;
|
||||
|
@ -2510,6 +2516,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
517630052336215100E15FFF /* main.js in Resources */,
|
||||
5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */,
|
||||
511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */,
|
||||
51C452862265093600C03939 /* Add.storyboard in Resources */,
|
||||
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
|
||||
|
@ -2800,6 +2807,7 @@
|
|||
51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */,
|
||||
51934CD023108953006127BE /* ActivityID.swift in Sources */,
|
||||
51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */,
|
||||
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */,
|
||||
513228FC233037630033D4ED /* Reachability.swift in Sources */,
|
||||
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
|
||||
519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "513C5CE5232571C2003D4054"
|
||||
BuildableName = "NetNewsWire iOS Share Extension.appex"
|
||||
BlueprintName = "NetNewsWire iOS Share Extension"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840D617B2029031C009BC708"
|
||||
BuildableName = "NetNewsWire.app"
|
||||
BlueprintName = "NetNewsWire-iOS"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840D617B2029031C009BC708"
|
||||
BuildableName = "NetNewsWire.app"
|
||||
BlueprintName = "NetNewsWire-iOS"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840D617B2029031C009BC708"
|
||||
BuildableName = "NetNewsWire.app"
|
||||
BlueprintName = "NetNewsWire-iOS"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -1,11 +1,20 @@
|
|||
// These mouse functions are used by NetNewsWire for Mac to display link previews
|
||||
function mouseDidEnterLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function mouseDidExitLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
// Add the mouse listeners for the above functions
|
||||
function linkHover() {
|
||||
document.querySelectorAll("a").forEach(element => {
|
||||
element.addEventListener("mouseenter", function() { mouseDidEnterLink(this) });
|
||||
element.addEventListener("mouseleave", function() { mouseDidExitLink(this) });
|
||||
});
|
||||
}
|
||||
|
||||
// Here we are making iframes responsive. Particularly useful for inline Youtube videos.
|
||||
function wrapFrames() {
|
||||
document.querySelectorAll("iframe").forEach(element => {
|
||||
var wrapper = document.createElement("div");
|
||||
|
@ -15,17 +24,19 @@ function wrapFrames() {
|
|||
});
|
||||
}
|
||||
|
||||
// Strip out all styling so that we have better control over layout
|
||||
function stripStyles() {
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove());
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style"));
|
||||
}
|
||||
|
||||
function linkHover() {
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener("mouseenter", function() { mouseDidEnterLink(this) });
|
||||
anchors[i].addEventListener("mouseleave", function() { mouseDidExitLink(this) });
|
||||
}
|
||||
// Add the playsinline attribute to any HTML5 videos that don't have it.
|
||||
// Without this attribute videos may autoplay and take over the whole screen
|
||||
// on an iphone when viewing an article.
|
||||
function inlineVideos() {
|
||||
document.querySelectorAll("video").forEach(element => {
|
||||
element.setAttribute("playsinline", true)
|
||||
});
|
||||
}
|
||||
|
||||
function error() {
|
||||
|
@ -35,9 +46,11 @@ function error() {
|
|||
function render(data) {
|
||||
document.getElementsByTagName("style")[0].innerHTML = data.style;
|
||||
document.body.innerHTML = data.body;
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
wrapFrames()
|
||||
stripStyles()
|
||||
linkHover()
|
||||
inlineVideos()
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
|||
|
||||
struct AppDefaults {
|
||||
|
||||
static var shared = UserDefaults.standard
|
||||
|
||||
struct Key {
|
||||
static let lastImageCacheFlushDate = "lastImageCacheFlushDate"
|
||||
static let firstRunDate = "firstRunDate"
|
||||
|
@ -21,7 +23,7 @@ struct AppDefaults {
|
|||
}
|
||||
|
||||
static let isFirstRun: Bool = {
|
||||
if let _ = UserDefaults.standard.object(forKey: Key.firstRunDate) as? Date {
|
||||
if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date {
|
||||
return false
|
||||
}
|
||||
firstRunDate = Date()
|
||||
|
@ -39,11 +41,11 @@ struct AppDefaults {
|
|||
|
||||
static var refreshInterval: RefreshInterval {
|
||||
get {
|
||||
let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval)
|
||||
let rawValue = AppDefaults.shared.integer(forKey: Key.refreshInterval)
|
||||
return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval)
|
||||
AppDefaults.shared.set(newValue.rawValue, forKey: Key.refreshInterval)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +91,7 @@ struct AppDefaults {
|
|||
Key.timelineGroupByFeed: false,
|
||||
Key.timelineNumberOfLines: 3,
|
||||
Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue]
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
AppDefaults.shared.register(defaults: defaults)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -106,27 +108,27 @@ private extension AppDefaults {
|
|||
}
|
||||
|
||||
static func bool(for key: String) -> Bool {
|
||||
return UserDefaults.standard.bool(forKey: key)
|
||||
return AppDefaults.shared.bool(forKey: key)
|
||||
}
|
||||
|
||||
static func setBool(for key: String, _ flag: Bool) {
|
||||
UserDefaults.standard.set(flag, forKey: key)
|
||||
AppDefaults.shared.set(flag, forKey: key)
|
||||
}
|
||||
|
||||
static func int(for key: String) -> Int {
|
||||
return UserDefaults.standard.integer(forKey: key)
|
||||
return AppDefaults.shared.integer(forKey: key)
|
||||
}
|
||||
|
||||
static func setInt(for key: String, _ x: Int) {
|
||||
UserDefaults.standard.set(x, forKey: key)
|
||||
AppDefaults.shared.set(x, forKey: key)
|
||||
}
|
||||
|
||||
static func date(for key: String) -> Date? {
|
||||
return UserDefaults.standard.object(forKey: key) as? Date
|
||||
return AppDefaults.shared.object(forKey: key) as? Date
|
||||
}
|
||||
|
||||
static func setDate(for key: String, _ date: Date?) {
|
||||
UserDefaults.standard.set(date, forKey: key)
|
||||
AppDefaults.shared.set(date, forKey: key)
|
||||
}
|
||||
|
||||
static func sortDirection(for key:String) -> ComparisonResult {
|
||||
|
|
|
@ -55,8 +55,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
|
|||
|
||||
// Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views
|
||||
let _ = DetailViewControllerWebViewProvider.shared
|
||||
|
||||
AccountManager.shared = AccountManager(accountsFolder: RSDataSubfolder(nil, "Accounts")!)
|
||||
AccountManager.shared = AccountManager()
|
||||
AppDefaults.shared = UserDefaults.init(suiteName: "group.\(Bundle.main.bundleIdentifier!)")!
|
||||
|
||||
registerBackgroundTasks()
|
||||
|
||||
|
|
|
@ -63,7 +63,16 @@ class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate {
|
|||
|
||||
private func replenishQueueIfNeeded() {
|
||||
while queue.count < minimumQueueDepth {
|
||||
let webView = WKWebView(frame: .zero)
|
||||
let preferences = WKPreferences()
|
||||
preferences.javaScriptCanOpenWindowsAutomatically = false
|
||||
preferences.javaScriptEnabled = true
|
||||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.preferences = preferences
|
||||
configuration.allowsInlineMediaPlayback = true
|
||||
configuration.mediaTypesRequiringUserActionForPlayback = .video
|
||||
|
||||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
||||
enqueueWebView(webView)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,10 @@ class MasterFeedTableViewCell : NNWTableViewCell {
|
|||
}()
|
||||
|
||||
private let faviconImageView: UIImageView = {
|
||||
return NonIntrinsicImageView(image: AppAssets.faviconTemplateImage)
|
||||
let imageView = NonIntrinsicImageView(image: AppAssets.faviconTemplateImage)
|
||||
imageView.layer.cornerRadius = MasterFeedTableViewCellLayout.faviconCornerRadius
|
||||
imageView.clipsToBounds = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private var unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
|
||||
|
|
|
@ -21,6 +21,8 @@ struct MasterFeedTableViewCellLayout {
|
|||
|
||||
private static let minRowHeight = CGFloat(integerLiteral: 44)
|
||||
|
||||
static let faviconCornerRadius = CGFloat(integerLiteral: 2)
|
||||
|
||||
let faviconRect: CGRect
|
||||
let titleRect: CGRect
|
||||
let unreadCountRect: CGRect
|
||||
|
|
|
@ -53,6 +53,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
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(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
|
@ -123,6 +124,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func feedMetadataDidChange(_ note: Notification) {
|
||||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// MasterFeedTitleView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 9/21/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class MasterTimelineTitleView: UIView {
|
||||
|
||||
@IBOutlet weak var imageView: UIImageView! {
|
||||
didSet {
|
||||
if let imageView = imageView {
|
||||
imageView.layer.cornerRadius = 2
|
||||
imageView.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="MasterTimelineTitleView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="190" height="38"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Rne-3c-G6E">
|
||||
<rect key="frame" x="0.0" y="9" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="FDb-aT-C3x"/>
|
||||
<constraint firstAttribute="width" constant="20" id="jOK-1W-SEO"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5F6-2v-qSS">
|
||||
<rect key="frame" x="28" y="9" width="162" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="5F6-2v-qSS" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="CeZ-D5-NOV"/>
|
||||
<constraint firstItem="5F6-2v-qSS" firstAttribute="leading" secondItem="Rne-3c-G6E" secondAttribute="trailing" constant="8" id="OUK-1k-4Gc"/>
|
||||
<constraint firstItem="Rne-3c-G6E" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="e1Z-hg-3Cc"/>
|
||||
<constraint firstItem="Rne-3c-G6E" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="h3i-G1-ays"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="5F6-2v-qSS" secondAttribute="trailing" id="w9X-65-lnV"/>
|
||||
</constraints>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="imageView" destination="Rne-3c-G6E" id="sHA-Vh-kck"/>
|
||||
<outlet property="label" destination="5F6-2v-qSS" id="ec7-8Y-PRv"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="14.492753623188406" y="-224.33035714285714"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -454,7 +454,12 @@ private extension MasterTimelineViewController {
|
|||
func resetUI() {
|
||||
|
||||
title = coordinator.timelineName
|
||||
navigationController?.title = coordinator.timelineName
|
||||
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
|
||||
titleView.imageView.image = coordinator.timelineFavicon
|
||||
titleView.label.text = coordinator.timelineName
|
||||
navigationItem.titleView = titleView
|
||||
}
|
||||
|
||||
|
||||
tableView.selectRow(at: nil, animated: false, scrollPosition: .top)
|
||||
if dataSource.snapshot().itemIdentifiers(inSection: 0).count > 0 {
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroup</key>
|
||||
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
<key>AppIdentifierPrefix</key>
|
||||
<string>$(AppIdentifierPrefix)</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.ranchero.NetNewsWire.FeedRefresh</string>
|
||||
|
|
|
@ -6,5 +6,9 @@
|
|||
<array>
|
||||
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -101,6 +101,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
|
||||
private(set) var currentFeedIndexPath: IndexPath?
|
||||
|
||||
var timelineFavicon: RSImage? {
|
||||
return (timelineFetcher as? SmallIconProvider)?.smallIcon
|
||||
}
|
||||
|
||||
var timelineName: String? {
|
||||
return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ struct SettingsView : View {
|
|||
|
||||
func buildAccountsSection() -> some View {
|
||||
Section(header: Text("ACCOUNTS").padding(.top, 22.0)) {
|
||||
ForEach(0..<viewModel.accounts.count) { index in
|
||||
ForEach(viewModel.accounts.indices, id: \.self) { index in
|
||||
NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(self.viewModel.accounts[index])), tag: index, selection: self.$accountAction) {
|
||||
Text(verbatim: self.viewModel.accounts[index].nameForDisplay)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroup</key>
|
||||
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
<key>AppIdentifierPrefix</key>
|
||||
<string>$(AppIdentifierPrefix)</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
|
|
|
@ -6,5 +6,9 @@
|
|||
<array>
|
||||
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import Account
|
||||
|
||||
protocol ShareFolderPickerControllerDelegate: class {
|
||||
func shareFolderPickerDidSelect(_ container: Container)
|
||||
func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String)
|
||||
}
|
||||
|
||||
class ShareFolderPickerController: UITableViewController {
|
||||
|
@ -37,14 +37,15 @@ class ShareFolderPickerController: UITableViewController {
|
|||
cell.textLabel?.text = pickerData?.containerNames[indexPath.row] ?? ""
|
||||
if pickerData?.containers[indexPath.row] === selectedContainer {
|
||||
cell.accessoryType = .checkmark
|
||||
} else {
|
||||
cell.accessoryType = .none
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let pickerData = pickerData else { return }
|
||||
delegate?.shareFolderPickerDidSelect(pickerData.containers[indexPath.row])
|
||||
navigationController?.popViewController(animated: true)
|
||||
delegate?.shareFolderPickerDidSelect(pickerData.containers[indexPath.row], pickerData.containerNames[indexPath.row])
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,10 +20,12 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
|||
|
||||
private var url: URL?
|
||||
private var container: Container?
|
||||
private var folderItem: SLComposeSheetConfigurationItem!
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
AccountManager.shared = AccountManager(accountsFolder: RSDataSubfolder(nil, "Accounts")!)
|
||||
AccountManager.shared = AccountManager()
|
||||
|
||||
pickerData = FlattenedAccountFolderPickerData()
|
||||
|
||||
if pickerData?.containers.count ?? 0 > 0 {
|
||||
|
@ -99,7 +101,6 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
|||
}
|
||||
|
||||
override func didSelectPost() {
|
||||
|
||||
var account: Account?
|
||||
if let containerAccount = container as? Account {
|
||||
account = containerAccount
|
||||
|
@ -126,16 +127,12 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
|
||||
|
||||
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
|
||||
}
|
||||
|
||||
func shareFolderPickerDidSelect(_ container: Container) {
|
||||
func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String) {
|
||||
self.container = container
|
||||
self.folderItem.value = selectionName
|
||||
self.popConfigurationViewController()
|
||||
}
|
||||
|
||||
override func configurationItems() -> [Any]! {
|
||||
|
@ -145,7 +142,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
|||
urlItem.title = "URL"
|
||||
urlItem.value = url?.absoluteString ?? ""
|
||||
|
||||
guard let folderItem = SLComposeSheetConfigurationItem() else { return nil }
|
||||
folderItem = SLComposeSheetConfigurationItem()
|
||||
folderItem.title = "Folder"
|
||||
|
||||
if let nameProvider = container as? DisplayNameProvider {
|
||||
|
@ -165,7 +162,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
|||
|
||||
}
|
||||
|
||||
return [folderItem, urlItem]
|
||||
return [folderItem!, urlItem]
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 960eaf60336f592306fb1bf6f5a62800a9c5050f
|
||||
Subproject commit ced48ad15ebc762fea50beb618eed3cf10721148
|
Loading…
Reference in New Issue