Bring in code from previous iOS release.

This commit is contained in:
Brent Simmons
2024-12-04 17:36:08 -08:00
parent 0874551cde
commit 4aaabacd24
10 changed files with 82 additions and 150 deletions

View File

@@ -216,7 +216,6 @@
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; };
519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; };
519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */; };
51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; };
51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; };
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; };
@@ -911,7 +910,6 @@
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; };
519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointViewController.swift; sourceTree = "<group>"; };
519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInspectorViewController.swift; sourceTree = "<group>"; };
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWIndowController.swift; path = AddFeed/AddFeedWIndowController.swift; sourceTree = "<group>"; };
51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = "<group>"; };
51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
@@ -1425,7 +1423,6 @@
children = (
516A09412361248000EAE89B /* Inspector.storyboard */,
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */,
519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */,
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */,
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */,
);
@@ -3199,7 +3196,6 @@
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */,
5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */,
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */,
51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */,
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */,
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,

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) {
@@ -62,5 +63,10 @@ class CloudKitAccountViewController: UITableViewController {
return super.tableView(tableView, viewForHeaderInSection: section)
}
}
@IBAction func openLimitationsAndSolutions(_ sender: Any) {
let vc = SFSafariViewController(url: CloudKitWebDocumentation.limitationsAndSolutionsURL)
vc.modalPresentationStyle = .pageSheet
present(vc, animated: true)
}
}

View File

@@ -12,10 +12,6 @@ import RSCore
import RSTree
import RSParser
enum AddFeedType {
case web
}
class AddFeedViewController: UITableViewController {
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@@ -37,12 +33,7 @@ class AddFeedViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
switch addFeedType {
default:
break
}
activityIndicator.isHidden = true
activityIndicator.color = .label

View File

@@ -28,7 +28,7 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
final class AppDefaults {
static let defaultThemeName = "Defaults"
static let defaultThemeName = "Default"
static let shared = AppDefaults()
private init() {}
@@ -41,7 +41,6 @@ final class AppDefaults {
struct Key {
static let userInterfaceColorPalette = "userInterfaceColorPalette"
static let activeExtensionPointIDs = "activeExtensionPointIDs"
static let lastImageCacheFlushDate = "lastImageCacheFlushDate"
static let firstRunDate = "firstRunDate"
static let timelineGroupByFeed = "timelineGroupByFeed"
@@ -114,15 +113,6 @@ final class AppDefaults {
}
}
var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? {
get {
return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]]
}
set {
UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs)
}
}
var useSystemBrowser: Bool {
get {
return UserDefaults.standard.bool(forKey: Key.useSystemBrowser)

View File

@@ -45,7 +45,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var webFeedIconDownloader: WebFeedIconDownloader!
var extensionContainersFile: ExtensionContainersFile!
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
var widgetDataEncoder: WidgetDataEncoder!
var unreadCount = 0 {
didSet {
if unreadCount != oldValue {
@@ -72,8 +73,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7)))
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil)
}
@@ -114,8 +113,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
extensionContainersFile = ExtensionContainersFile()
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
widgetDataEncoder = WidgetDataEncoder()
syncTimer = ArticleStatusSyncTimer()
#if DEBUG
syncTimer!.update()
#endif
@@ -175,9 +176,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
syncTimer?.invalidate()
scheduleBackgroundFeedRefresh()
syncArticleStatus()
widgetDataEncoder.encode()
waitForSyncTasksToFinish()
}
func prepareAccountsForForeground() {
extensionFeedAddRequestFile.resume()
syncTimer?.update()
@@ -293,7 +295,7 @@ private extension AppDelegate {
return
}
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning {
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning || widgetDataEncoder.isRunning {
os_log("Waiting for sync to finish...", log: self.log, type: .info)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.waitToComplete(completion: completion)
@@ -387,9 +389,9 @@ private extension AppDelegate {
/// - Parameter task: `BGAppRefreshTask`
/// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work.
func performBackgroundFeedRefresh(with task: BGAppRefreshTask) {
scheduleBackgroundFeedRefresh() // schedule next refresh
os_log("Woken to perform account refresh.", log: self.log, type: .info)
DispatchQueue.main.async {
@@ -398,26 +400,22 @@ private extension AppDelegate {
}
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in
if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self.suspendApplication()
os_log("Account refresh operation completed.", log: self.log, type: .info)
task.setTaskCompleted(success: true)
}
}
}
// set expiration handler
task.expirationHandler = { [weak task] in
DispatchQueue.main.sync {
self.suspendApplication()
}
os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info)
task?.setTaskCompleted(success: false)
DispatchQueue.main.async {
self.suspendApplication()
task?.setTaskCompleted(success: false)
}
}
}
}
// Handle Notification Actions
@@ -445,9 +443,6 @@ private extension AppDelegate {
self.prepareAccountsForBackground()
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self?.prepareAccountsForBackground()
self?.suspendApplication()
}
@@ -474,9 +469,6 @@ private extension AppDelegate {
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self?.prepareAccountsForBackground()
self?.suspendApplication()
}

View File

@@ -7,6 +7,7 @@
//
import UIKit
import SafariServices
import Account
class AccountInspectorViewController: UITableViewController {
@@ -16,7 +17,8 @@ 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,12 @@ class AccountInspectorViewController: UITableViewController {
present(alertController, animated: true)
}
@IBAction func openLimitationsAndSolutions(_ sender: Any) {
let vc = SFSafariViewController(url: CloudKitWebDocumentation.limitationsAndSolutionsURL)
vc.modalPresentationStyle = .pageSheet
present(vc, animated: true)
}
}
// MARK: Table View

View File

@@ -1,81 +0,0 @@
//
// ExtensionPointInspectorViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/16/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import UIKit
class ExtensionPointInspectorViewController: UITableViewController {
@IBOutlet weak var extensionDescription: UILabel!
var extensionPoint: ExtensionPoint?
override func viewDidLoad() {
super.viewDidLoad()
guard let extensionPoint = extensionPoint else { return }
navigationItem.title = extensionPoint.title
extensionDescription.attributedText = extensionPoint.description
tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
}
@IBAction func disable(_ sender: Any) {
guard let extensionPoint = extensionPoint else { return }
let title = NSLocalizedString("Deactivate Extension", comment: "Deactivate Extension")
let extensionPointTypeTitle = extensionPoint.extensionPointID.extensionPointType.title
let message = NSLocalizedString("Are you sure you want to deactivate the \(extensionPointTypeTitle) extension “\(extensionPoint.title)”?", comment: "Deactivate text")
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel)
alertController.addAction(cancelAction)
let markTitle = NSLocalizedString("Deactivate", comment: "Deactivate")
let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in
ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint.extensionPointID)
self?.navigationController?.popViewController(animated: true)
}
alertController.addAction(markAction)
alertController.preferredAction = markAction
present(alertController, animated: true)
}
}
// MARK: Table View
extension ExtensionPointInspectorViewController {
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let extensionPoint = extensionPoint else { return nil }
if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPoint.image
return headerView
} else {
return super.tableView(tableView, viewForHeaderInSection: section)
}
}
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if indexPath.section > 0 {
return true
}
return false
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
}
}

View File

@@ -47,9 +47,12 @@ class KeyboardManager {
static func createKeyCommand(title: String, action: String, input: String, modifiers: UIKeyModifierFlags) -> UIKeyCommand {
let selector = NSSelectorFromString(action)
return UIKeyCommand(title: title, image: nil, action: selector, input: input, modifierFlags: modifiers, propertyList: nil, alternates: [], discoverabilityTitle: nil, attributes: [], state: .on)
let keyCommand = UIKeyCommand(title: title, image: nil, action: selector, input: input, modifierFlags: modifiers, propertyList: nil, alternates: [], discoverabilityTitle: nil, attributes: [], state: .on)
if #available(iOS 15.0, *) {
keyCommand.wantsPriorityOverSystemBehavior = true
}
return keyCommand
}
}
private extension KeyboardManager {
@@ -58,14 +61,18 @@ private extension KeyboardManager {
guard let input = createKeyCommandInput(keyEntry: keyEntry) else { return nil }
let modifiers = createKeyModifierFlags(keyEntry: keyEntry)
let action = keyEntry["action"] as! String
if let title = keyEntry["title"] as? String {
return KeyboardManager.createKeyCommand(title: title, action: action, input: input, modifiers: modifiers)
} else {
return UIKeyCommand(input: input, modifierFlags: modifiers, action: NSSelectorFromString(action))
let keyCommand = UIKeyCommand(input: input, modifierFlags: modifiers, action: NSSelectorFromString(action))
if #available(iOS 15.0, *) {
keyCommand.wantsPriorityOverSystemBehavior = true
}
return keyCommand
}
}
static func createKeyCommandInput(keyEntry: [String: Any]) -> String? {
guard let key = keyEntry["key"] as? String else { return nil }

View File

@@ -72,7 +72,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(configureContextMenu(_:)), name: .ActiveExtensionPointsDidChange, object: nil)
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
@@ -504,6 +503,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
}
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)
@@ -608,6 +614,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
}
if let rowChanges = changes.rowChanges {
for rowChange in rowChanges {
if let reloads = rowChange.reloadIndexPaths, !reloads.isEmpty {
tableView.reloadRows(at: reloads, with: .none)
}
}
}
completion?()
}
@@ -635,7 +649,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
var menuItems: [UIAction] = []
let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
let addWebFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in
self.coordinator.showAddWebFeed()
}

View File

@@ -9,11 +9,11 @@
import Foundation
struct ShadowTableChanges {
struct Move: Hashable {
var from: Int
var to: Int
init(_ from: Int, _ to: Int) {
self.from = from
self.to = to
@@ -21,38 +21,44 @@ struct ShadowTableChanges {
}
struct RowChanges {
var section: Int
var deletes: Set<Int>?
var inserts: Set<Int>?
var reloads: Set<Int>?
var moves: Set<ShadowTableChanges.Move>?
var isEmpty: Bool {
return (deletes?.isEmpty ?? true) && (inserts?.isEmpty ?? true) && (moves?.isEmpty ?? true)
}
var deleteIndexPaths: [IndexPath]? {
guard let deletes = deletes else { return nil }
return deletes.map { IndexPath(row: $0, section: section) }
}
var insertIndexPaths: [IndexPath]? {
guard let inserts = inserts else { return nil }
return inserts.map { IndexPath(row: $0, section: section) }
}
var reloadIndexPaths: [IndexPath]? {
guard let reloads = reloads else { return nil }
return reloads.map { IndexPath(row: $0, section: section) }
}
var moveIndexPaths: [(IndexPath, IndexPath)]? {
guard let moves = moves else { return nil }
return moves.map { (IndexPath(row: $0.from, section: section), IndexPath(row: $0.to, section: section)) }
}
init(section: Int, deletes: Set<Int>?, inserts: Set<Int>?, moves: Set<Move>?) {
init(section: Int, deletes: Set<Int>?, inserts: Set<Int>?, reloads: Set<Int>?, moves: Set<Move>?) {
self.section = section
self.deletes = deletes
self.inserts = inserts
self.reloads = reloads
self.moves = moves
}
}
var deletes: Set<Int>?
@@ -66,5 +72,4 @@ struct ShadowTableChanges {
self.moves = moves
self.rowChanges = rowChanges
}
}