Merge branch 'ios-candidate'

This commit is contained in:
Maurice Parker 2022-09-22 20:50:35 -05:00
commit 0c95428b31
16 changed files with 264 additions and 91 deletions

View File

@ -711,6 +711,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), completion: completion)
}
public func fetchCountForStarredArticles() throws -> Int {
return try database.fetchStarredArticlesCount(flattenedWebFeeds().webFeedIDs())
}
public func fetchUnreadArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
database.fetchUnreadArticleIDsAsync(completion: completion)
}

View File

@ -393,6 +393,17 @@ public final class AccountManager: UnreadCountProvider {
}
}
}
// MARK: - Fetching Article Counts
public func fetchCountForStarredArticles() throws -> Int {
precondition(Thread.isMainThread)
var count = 0
for account in activeAccounts {
count += try account.fetchCountForStarredArticles()
}
return count
}
// MARK: - Caches

View File

@ -0,0 +1,12 @@
//
// File.swift
//
//
// Created by Maurice Parker on 9/22/22.
//
import Foundation
public struct CloudKitWebDocumentation {
public static let limitationsAndSolutions = "https://netnewswire.com/help/iCloud"
}

View File

@ -114,6 +114,10 @@ public final class ArticlesDatabase {
return try articlesTable.fetchStarredArticles(webFeedIDs, limit)
}
public func fetchStarredArticlesCount(_ webFeedIDs: Set<String>) throws -> Int {
return try articlesTable.fetchStarredArticlesCount(webFeedIDs)
}
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
}

View File

@ -32,7 +32,8 @@ final class ArticlesTable: DatabaseTable {
let articleCutoffDate = Date().bySubtracting(days: 90)
private typealias ArticlesFetchMethod = (FMDatabase) -> Set<Article>
private typealias ArticlesCountFetchMethod = (FMDatabase) -> Int
init(name: String, accountID: String, queue: DatabaseQueue, retentionStyle: ArticlesDatabase.RetentionStyle) {
self.name = name
@ -103,6 +104,10 @@ final class ArticlesTable: DatabaseTable {
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, limit, $0) }, completion)
}
func fetchStarredArticlesCount(_ webFeedIDs: Set<String>) throws -> Int {
return try fetchArticlesCount{ self.fetchStarredArticlesCount(webFeedIDs, $0) }
}
// MARK: - Fetching Search Articles
func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
@ -671,6 +676,23 @@ private extension ArticlesTable {
return articles
}
private func fetchArticlesCount(_ fetchMethod: @escaping ArticlesCountFetchMethod) throws -> Int {
var articlesCount = 0
var error: DatabaseError? = nil
queue.runInDatabaseSync { databaseResult in
switch databaseResult {
case .success(let database):
articlesCount = fetchMethod(database)
case .failure(let databaseError):
error = databaseError
}
}
if let error = error {
throw(error)
}
return articlesCount
}
private func fetchArticlesAsync(_ fetchMethod: @escaping ArticlesFetchMethod, _ completion: @escaping ArticleSetResultBlock) {
queue.runInDatabase { databaseResult in
@ -745,6 +767,19 @@ private extension ArticlesTable {
return articlesWithSQL(sql, parameters, database)
}
func fetchArticleCountsWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]) -> Int {
let sql = "select count(*) from articles natural join statuses where \(whereClause);"
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
return 0
}
var articlesCount = 0
if resultSet.next() {
articlesCount = resultSet.long(forColumnIndex: 0)
}
resultSet.close()
return articlesCount
}
func fetchArticlesMatching(_ searchString: String, _ database: FMDatabase) -> Set<Article> {
let sql = "select rowid from search where search match ?;"
let sqlSearchString = sqliteSearchString(with: searchString)
@ -840,20 +875,31 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
var whereClause = "feedID in \(placeholders) and starred=1"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
var whereClause = "feedID in \(placeholders) and starred=1"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchStarredArticlesCount(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Int {
// select count from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty {
return 0
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and starred=1"
return fetchArticleCountsWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
let articles = fetchArticlesMatching(searchString, database)
// TODO: include the feedIDs in the SQL rather than filtering here.

View File

@ -19,23 +19,50 @@ public final class WidgetDataEncoder: Logging {
private let fetchLimit = 7
private var backgroundTaskID: UIBackgroundTaskIdentifier!
private lazy var appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
private lazy var containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
private lazy var imageContainer = containerURL?.appendingPathComponent("widgetImages", isDirectory: true)
private lazy var dataURL = containerURL?.appendingPathComponent("widget-data.json")
static let shared = WidgetDataEncoder()
private init () {
private let encodeWidgetDataQueue = CoalescingQueue(name: "Encode the Widget Data", interval: 5.0)
init () {
if imageContainer != nil {
try? FileManager.default.createDirectory(at: imageContainer!, withIntermediateDirectories: true, attributes: nil)
}
if #available(iOS 14, *) {
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
}
}
func encodeIfNecessary() {
encodeWidgetDataQueue.performCallsImmediately()
}
@available(iOS 14, *)
@objc func statusesDidChange(_ note: Notification) {
encodeWidgetDataQueue.add(self, #selector(performEncodeWidgetData))
}
@available(iOS 14, *)
@objc private func performEncodeWidgetData() {
// We will be on the Main Thread when the encodeIfNecessary function is called. We want
// block the main thread in that case so that the widget data is encoded. If it is on
// a background Thread, it was called by the CoalescingQueue. In that case we need to
// move it to the Main Thread and want to execute it async.
if Thread.isMainThread {
encodeWidgetData()
} else {
DispatchQueue.main.async {
self.encodeWidgetData()
}
}
}
@available(iOS 14, *)
func encodeWidgetData() throws {
private func encodeWidgetData() {
flushSharedContainer()
logger.debug("Started encoding widget data.")
logger.debug("Starting encoding widget data.")
do {
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending)
@ -78,7 +105,7 @@ public final class WidgetDataEncoder: Logging {
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
currentStarredCount: try AccountManager.shared.fetchCountForStarredArticles(),
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
@ -88,10 +115,6 @@ public final class WidgetDataEncoder: Logging {
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
}
let encodedData = try? JSONEncoder().encode(latestData)
self.logger.debug("Finished encoding widget data.")
@ -103,14 +126,11 @@ public final class WidgetDataEncoder: Logging {
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
self.logger.debug("Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines()
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
} else {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
}
}
} catch {
logger.error("WidgetDataEncoder failed to write the widget data.")
}
}

View File

@ -1,5 +1,18 @@
# iOS Release Notes
### 6.1 TestFlight build 6105 = 6 July 2022
Write widget icons to the shared container
Make crashes slightly less likely when building up widget data
### 6.1 TestFlight build 6104 - 6 April 2022
Building on a new machine and making sure alls well
Moved built-in themes to the app bundle so theyre always up to date
Fixed a crash in the Feeds list related to updating the feed image
Fixed a layout bug that could happen on returning to the Feeds list
Fixed a bug where go-to-feed might not properly expand disclosure triangles
### 6.1 TestFlight build 6103 - 25 Jan 2022
* Fixed regression with keyboard shortcuts.

View File

@ -15,14 +15,14 @@ struct ArticleItemView: View {
var article: LatestArticle
var deepLink: URL
@State private var iconImage: UIImage?
@State private var iconImage: Image?
var body: some View {
Link(destination: deepLink, label: {
HStack(alignment: .top, spacing: nil, content: {
// Feed Icon
if iconImage != nil {
Image(uiImage: iconImage!)
iconImage!
.resizable()
.frame(width: 30, height: 30)
.cornerRadius(4)
@ -51,22 +51,23 @@ struct ArticleItemView: View {
}
})
}).onAppear {
guard let feedIconPath = article.feedIconPath else {
iconImage = thumbnail(nil)
return
}
let path = URL(fileURLWithPath: feedIconPath)
let data = try? Data(contentsOf: path)
iconImage = thumbnail(data)
iconImage = thumbnail(from: article.feedIconPath)
}
}
func thumbnail(_ data: Data?) -> UIImage {
if data == nil {
return UIImage(systemName: "globe")!
} else {
return UIImage(data: data!)!
}
func thumbnail(from path: String?) -> Image? {
guard let imagePath = path else {
return Image(uiImage: UIImage(systemName: "globe")!)
}
let url = URL(fileURLWithPath: imagePath)
guard let data = try? Data(contentsOf: url),
let uiImage = UIImage(data: data) else {
return Image(uiImage: UIImage(systemName: "globe")!)
}
return Image(uiImage: uiImage)
}
func pubDate(_ dateString: String) -> String {

View File

@ -13,7 +13,7 @@
<objects>
<navigationController storyboardIdentifier="LocalAccountNavigationViewController" id="TMY-HB-vAu" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="p8g-7e-3f4">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -29,7 +29,7 @@
<objects>
<navigationController storyboardIdentifier="FeedbinAccountNavigationViewController" id="sFg-MZ-PqJ" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="wq6-np-tNn">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -314,7 +314,7 @@ Dont have a Feedbin account?</string>
<objects>
<navigationController storyboardIdentifier="NewsBlurAccountNavigationViewController" id="eE3-pu-HdL" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Fsp-NG-hoR">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -678,7 +678,7 @@ Dont have a Reader account?</string>
<objects>
<navigationController storyboardIdentifier="CloudKitAccountNavigationViewController" id="LhW-Dq-qqj" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="MVG-BZ-ALL">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -706,10 +706,21 @@ Dont have a Reader account?</string>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vXG-7q-4qg">
<rect key="frame" x="75" y="50.5" width="264" height="30"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="iCloud Syncing Limitations &amp; Solutions"/>
<connections>
<action selector="openLimitationsAndSolutions:" destination="qj9-Vr-VIU" eventType="touchUpInside" id="JZ5-hQ-PLl"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
<constraints>
<constraint firstItem="vXG-7q-4qg" firstAttribute="centerX" secondItem="iYz-ri-yys" secondAttribute="centerX" id="8Pg-BU-zIj"/>
<constraint firstItem="aFS-Y0-2MH" firstAttribute="leading" secondItem="iYz-ri-yys" secondAttribute="leading" constant="20" symbolic="YES" id="E97-lo-arw"/>
<constraint firstItem="vXG-7q-4qg" firstAttribute="top" secondItem="aFS-Y0-2MH" secondAttribute="bottom" id="TEh-B3-9Ci"/>
<constraint firstAttribute="trailing" secondItem="aFS-Y0-2MH" secondAttribute="trailing" constant="21" id="XUo-oQ-MbK"/>
<constraint firstItem="aFS-Y0-2MH" firstAttribute="top" secondItem="iYz-ri-yys" secondAttribute="top" constant="8" id="xpj-LW-4l7"/>
</constraints>
@ -773,7 +784,7 @@ Dont have a Reader account?</string>
<objects>
<navigationController storyboardIdentifier="ReaderAPIAccountNavigationViewController" id="Son-xT-GLx" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="sdL-X8-E6K">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>

View File

@ -7,6 +7,7 @@
//
import UIKit
import SafariServices
import Account
enum CloudKitAccountViewControllerError: LocalizedError {
@ -30,7 +31,7 @@ class CloudKitAccountViewController: UITableViewController {
}
private func setupFooter() {
footerLabel.text = NSLocalizedString("Feeds in your iCloud account will be synced across your Mac and iOS devices.\n\nImportant note: while NetNewsWire itself is very fast, iCloud syncing is sometimes very slow. This can happen after adding a number of feeds and when setting it up on a new device.\n\nIf that happens to you, it may appear stuck. But dont worry — its not. Just let it run.", comment: "iCloud")
footerLabel.text = NSLocalizedString("NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices.", comment: "iCloud")
}
@IBAction func cancel(_ sender: Any) {
@ -63,4 +64,10 @@ class CloudKitAccountViewController: UITableViewController {
}
}
@IBAction func openLimitationsAndSolutions(_ sender: Any) {
let vc = SFSafariViewController(url: URL(string: CloudKitWebDocumentation.limitationsAndSolutions)!)
vc.modalPresentationStyle = .pageSheet
present(vc, animated: true)
}
}

View File

@ -42,6 +42,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var webFeedIconDownloader: WebFeedIconDownloader!
var extensionContainersFile: ExtensionContainersFile!
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
var widgetDataEncoder: WidgetDataEncoder!
var unreadCount = 0 {
didSet {
@ -111,6 +112,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
extensionContainersFile = ExtensionContainersFile()
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
widgetDataEncoder = WidgetDataEncoder()
syncTimer = ArticleStatusSyncTimer()
#if DEBUG
@ -169,6 +172,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func prepareAccountsForBackground() {
extensionFeedAddRequestFile.suspend()
widgetDataEncoder.encodeIfNecessary()
syncTimer?.invalidate()
scheduleBackgroundFeedRefresh()
syncArticleStatus()
@ -395,7 +399,6 @@ private extension AppDelegate {
}
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self.suspendApplication()
self.logger.info("Account refresh operation completed.")
task.setTaskCompleted(success: true)
@ -440,7 +443,6 @@ private extension AppDelegate {
self.prepareAccountsForBackground()
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
}
@ -467,7 +469,6 @@ private extension AppDelegate {
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
}

View File

@ -7,6 +7,7 @@
//
import UIKit
import SafariServices
import Account
class AccountInspectorViewController: UITableViewController {
@ -16,6 +17,7 @@ class AccountInspectorViewController: UITableViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var activeSwitch: UISwitch!
@IBOutlet weak var deleteAccountButton: VibrantButton!
@IBOutlet weak var limitationsAndSolutionsButton: UIButton!
var isModal = false
weak var account: Account?
@ -36,6 +38,10 @@ class AccountInspectorViewController: UITableViewController {
deleteAccountButton.setTitle(NSLocalizedString("Remove Account", comment: "Remove Account"), for: .normal)
}
if account.type != .cloudKit {
limitationsAndSolutionsButton.isHidden = true
}
if isModal {
let doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done))
navigationItem.leftBarButtonItem = doneBarButtonItem
@ -115,6 +121,13 @@ class AccountInspectorViewController: UITableViewController {
present(alertController, animated: true)
}
@IBAction func openLimitationsAndSolutions(_ sender: Any) {
let vc = SFSafariViewController(url: URL(string: CloudKitWebDocumentation.limitationsAndSolutions)!)
vc.modalPresentationStyle = .pageSheet
present(vc, animated: true)
}
}
// MARK: Table View

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -16,21 +16,41 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="xcc-i3-tPS">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<view key="tableFooterView" contentMode="scaleToFill" id="3V2-Cm-ezj">
<rect key="frame" x="0.0" y="282" width="414" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dgD-uX-vcx">
<rect key="frame" x="75" y="7" width="264" height="30"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="iCloud Syncing Limitations &amp; Solutions"/>
<connections>
<action selector="openLimitationsAndSolutions:" destination="1m3-fZ-n7g" eventType="touchUpInside" id="DKt-dF-a6L"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="dgD-uX-vcx" firstAttribute="centerX" secondItem="3V2-Cm-ezj" secondAttribute="centerX" id="IDg-5p-aZs"/>
<constraint firstItem="dgD-uX-vcx" firstAttribute="centerY" secondItem="3V2-Cm-ezj" secondAttribute="centerY" id="w0c-Qa-ADC"/>
</constraints>
</view>
<sections>
<tableViewSection id="vec-ab-Ylg">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="4Ue-UW-e0l" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="18" width="374" height="44"/>
<rect key="frame" x="20" y="18" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4Ue-UW-e0l" id="9E1-ww-kYn">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="19" translatesAutoresizingMaskIntoConstraints="NO" id="mQa-0W-eVS">
<rect key="frame" x="20" y="12.5" width="334" height="18.5"/>
<rect key="frame" x="20" y="11" width="334" height="22"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name (Optional)" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="LUW-uv-piz">
<rect key="frame" x="0.0" y="0.0" width="334" height="18.5"/>
<rect key="frame" x="0.0" y="0.0" width="334" height="22"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
</textField>
@ -45,14 +65,14 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="zQY-gY-BOY" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="62" width="374" height="44"/>
<rect key="frame" x="20" y="61.5" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="zQY-gY-BOY" id="dBp-J5-ZsY">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Active" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zf0-Gm-p4F">
<rect key="frame" x="20" y="13.5" width="40" height="17"/>
<rect key="frame" x="20" y="11.5" width="47.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -75,14 +95,14 @@
<tableViewSection id="9UW-s0-NPI">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="FsT-vH-rTo" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="142" width="374" height="44"/>
<rect key="frame" x="20" y="141" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FsT-vH-rTo" id="rJW-6J-9DM">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TYD-py-8IF" customClass="VibrantButton" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="-0.5" width="374" height="44.5"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="WmU-wG-RLQ"/>
</constraints>
@ -107,14 +127,14 @@
<tableViewSection id="mgY-wW-xiO">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="lgY-im-vCo" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="222" width="374" height="44"/>
<rect key="frame" x="20" y="220.5" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lgY-im-vCo" id="fIH-xP-nza">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="obv-a5-Pl6" customClass="VibrantButton" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="-0.5" width="374" height="44.5"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="WtN-fp-Ldt"/>
</constraints>
@ -154,6 +174,7 @@
<connections>
<outlet property="activeSwitch" destination="6YV-K0-yPS" id="d9M-GP-aTR"/>
<outlet property="deleteAccountButton" destination="obv-a5-Pl6" id="idW-gm-BIJ"/>
<outlet property="limitationsAndSolutionsButton" destination="dgD-uX-vcx" id="5ti-AM-xms"/>
<outlet property="nameTextField" destination="LUW-uv-piz" id="e2P-Hq-guh"/>
</connections>
</tableViewController>
@ -179,7 +200,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="ZdA-rl-9eP">
<rect key="frame" x="20" y="13" width="334" height="18.5"/>
<rect key="frame" x="20" y="11" width="334" height="22"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
</textField>
@ -199,7 +220,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notify About New Articles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YV2-gG-lMP">
<rect key="frame" x="24" y="13.5" width="167.5" height="17"/>
<rect key="frame" x="24" y="11.5" width="196.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -229,7 +250,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Always Use Reader View" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bf4-3X-Rfr">
<rect key="frame" x="24" y="13.5" width="158.5" height="17"/>
<rect key="frame" x="24" y="11.5" width="187" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -256,20 +277,20 @@
<tableViewSection headerTitle="Home Page" id="dTd-6q-SZd">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="0zc-o6-Sjh" customClass="VibrantBasicTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="198.5" width="374" height="43.5"/>
<rect key="frame" x="20" y="204.5" width="374" height="22.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0zc-o6-Sjh" id="vJs-XK-ebf">
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="22.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HUP-Cu-FGT">
<rect key="frame" x="20" y="11" width="301" height="22"/>
<rect key="frame" x="20" y="11" width="301" height="0.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="safari" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="p82-kn-lfh">
<rect key="frame" x="329" y="10" width="25" height="24"/>
<rect key="frame" x="329" y="-1" width="25" height="24.5"/>
<color key="tintColor" name="primaryAccentColor"/>
<constraints>
<constraint firstAttribute="width" constant="25" id="Suu-bu-Lar"/>
@ -300,14 +321,14 @@
<tableViewSection headerTitle="Feed URL" id="MtQ-oG-lrU">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="fKD-Vi-B8O">
<rect key="frame" x="20" y="292" width="374" height="43.5"/>
<rect key="frame" x="20" y="283" width="374" height="22.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fKD-Vi-B8O" id="2G0-9f-qwN">
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="22.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rOV-XS-bNW" customClass="InteractiveLabel" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="11" width="334" height="22"/>
<rect key="frame" x="20" y="11" width="334" height="0.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -353,7 +374,7 @@
<objects>
<navigationController storyboardIdentifier="FeedInspectorNavigationViewController" id="Ybm-En-qAg" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="YQK-Se-EBX">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -369,7 +390,7 @@
<objects>
<navigationController storyboardIdentifier="AccountInspectorNavigationViewController" id="5wr-gz-V2t" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="3Os-JI-n4e">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -391,14 +412,14 @@
<tableViewSection id="hGI-fH-ovr">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="hx7-HU-HV2" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="18" width="374" height="44"/>
<rect key="frame" x="20" y="18" width="374" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hx7-HU-HV2" id="fEV-cR-i6h">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YJL-5w-N2S">
<rect key="frame" x="20" y="11" width="334" height="22"/>
<rect key="frame" x="20" y="11" width="334" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -417,14 +438,14 @@
<tableViewSection id="OcC-FF-jGv">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="k4j-va-uaO" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="98" width="374" height="44"/>
<rect key="frame" x="20" y="97" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="k4j-va-uaO" id="bQ8-mc-QAj">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IhW-3B-PM7" customClass="VibrantButton" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<rect key="frame" x="0.0" y="-0.5" width="374" height="44.5"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="qwy-Nb-VrG"/>
</constraints>
@ -474,8 +495,8 @@
<designable name="rOV-XS-bNW"/>
</designables>
<resources>
<image name="safari" catalog="system" width="128" height="121"/>
<image name="safari.fill" catalog="system" width="128" height="121"/>
<image name="safari" catalog="system" width="128" height="123"/>
<image name="safari.fill" catalog="system" width="128" height="123"/>
<namedColor name="deleteBackgroundColor">
<color red="1" green="0.23100003600120544" blue="0.18799999356269836" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
@ -485,6 +506,9 @@
<namedColor name="secondaryAccentColor">
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemRedColor">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>

View File

@ -459,6 +459,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
}
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponder.delete(_:)) {
return isFirstResponder
}
return super.canPerformAction(action, withSender: sender)
}
@objc func expandSelectedRows(_ sender: Any?) {
if let indexPath = coordinator.currentFeedIndexPath, let node = coordinator.nodeFor(indexPath) {
coordinator.expand(node)

View File

@ -66,7 +66,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging {
}
func sceneDidEnterBackground(_ scene: UIScene) {
try? WidgetDataEncoder.shared.encodeWidgetData()
ArticleStringFormatter.emptyCaches()
appDelegate.prepareAccountsForBackground()
}

View File

@ -1,7 +1,7 @@
// High Level Settings common to both the iOS application and any extensions we bundle with it
MARKETING_VERSION = 6.1
CURRENT_PROJECT_VERSION = 6103
CURRENT_PROJECT_VERSION = 6105
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon