This commit is contained in:
Andrew Brehaut 2019-09-23 18:57:34 +12:00
commit 8fcc61b769
27 changed files with 314 additions and 41 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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() {

View File

@ -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)

View File

@ -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

View File

@ -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 }
}

View File

@ -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 */,

View File

@ -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>

View File

@ -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()
}

View File

@ -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 {

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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!
}

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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)
}

View File

@ -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>

View File

@ -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>

View File

@ -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])
}
}

View File

@ -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