From a8eb75c20fbc3b16361fe5557cb24fe8f01b7f31 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 11 Nov 2024 22:03:32 -0800 Subject: [PATCH] Remove unneeded #available and @available. --- .../Detail/DetailWebViewController.swift | 14 - .../Accounts/AddAccountsView.swift | 61 +-- .../RSCore/AppKit/NSAppearance+RSCore.swift | 9 +- .../RSDarkModeAdaptingToolbarButton.swift | 14 +- .../Sources/RSCore/Shared/Data+RSCore.swift | 36 +- RSWeb/Sources/RSWeb/Reachability.swift | 423 +++++------------- Shared/Widget/WidgetDataEncoder.swift | 53 ++- iOS/KeyboardManager.swift | 8 +- .../Cell/MasterFeedTableViewCell.swift | 4 +- .../MasterFeedTableViewSectionHeader.swift | 4 +- iOS/MasterFeed/MasterFeedViewController.swift | 88 ++-- .../MasterTimelineTitleView.swift | 12 +- .../MasterTimelineViewController.swift | 14 +- 13 files changed, 204 insertions(+), 536 deletions(-) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 890b5be18..c0771170e 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -106,20 +106,6 @@ final class DetailWebViewController: NSViewController { view = webView - // Use the safe area layout guides if they are available. - if #available(OSX 11.0, *) { - // These constraints have been removed as they were unsatisfiable after removing NSBox. - } else { - let constraints = [ - webView.topAnchor.constraint(equalTo: view.topAnchor), - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - ] - NSLayoutConstraint.activate(constraints) - } - - // Hide the web view until the first reload (navigation) is complete (plus some delay) to avoid the awful white flash that happens on the initial display in dark mode. // See bug #901. webView.isHidden = true diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index c8a496ad2..745ba04ee 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -102,45 +102,24 @@ struct AddAccountsView: View { HStack(spacing: 12) { Spacer() - if #available(OSX 11.0, *) { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .help("Cancel") - .keyboardShortcut(.cancelAction) - - } else { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .accessibility(label: Text("Add Account")) - } - if #available(OSX 11.0, *) { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - .help("Add Account") - .keyboardShortcut(.defaultAction) - - } else { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - } + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .frame(width: 76) + }) + .help("Cancel") + .keyboardShortcut(.cancelAction) + + Button(action: { + addAccountDelegate?.presentSheetForAccount(selectedAccount) + parent?.dismiss(nil) + }, label: { + Text("Continue") + .frame(width: 76) + }) + .help("Add Account") + .keyboardShortcut(.defaultAction) } .padding(.top, 12) .padding(.bottom, 4) @@ -149,8 +128,8 @@ struct AddAccountsView: View { .fixedSize(horizontal: false, vertical: true) .frame(width: 420) .padding() - } - + } + var localAccount: some View { VStack(alignment: .leading) { Text("Local") diff --git a/RSCore/Sources/RSCore/AppKit/NSAppearance+RSCore.swift b/RSCore/Sources/RSCore/AppKit/NSAppearance+RSCore.swift index fd2ed73de..034cbd182 100644 --- a/RSCore/Sources/RSCore/AppKit/NSAppearance+RSCore.swift +++ b/RSCore/Sources/RSCore/AppKit/NSAppearance+RSCore.swift @@ -10,15 +10,10 @@ import AppKit extension NSAppearance { - + @objc(rsIsDarkMode) public var isDarkMode: Bool { - if #available(macOS 10.14, *) { - return self.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua - } - else { - return false - } + return self.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua } } #endif diff --git a/RSCore/Sources/RSCore/AppKit/RSDarkModeAdaptingToolbarButton.swift b/RSCore/Sources/RSCore/AppKit/RSDarkModeAdaptingToolbarButton.swift index be10f3bea..458b9e741 100644 --- a/RSCore/Sources/RSCore/AppKit/RSDarkModeAdaptingToolbarButton.swift +++ b/RSCore/Sources/RSCore/AppKit/RSDarkModeAdaptingToolbarButton.swift @@ -23,16 +23,14 @@ class RSDarkModeAdaptingToolbarButton: NSButton { override func layout() { // Always re-set the NSImage template state based on the current dark mode setting - if #available(macOS 10.14, *) { - if self.forceTemplateInDarkMode, let targetImage = self.image { - var newTemplateState: Bool = self.originalImageTemplateState + if self.forceTemplateInDarkMode, let targetImage = self.image { + var newTemplateState: Bool = self.originalImageTemplateState - if self.effectiveAppearance.isDarkMode { - newTemplateState = true - } - - targetImage.isTemplate = newTemplateState + if self.effectiveAppearance.isDarkMode { + newTemplateState = true } + + targetImage.isTemplate = newTemplateState } super.layout() diff --git a/RSCore/Sources/RSCore/Shared/Data+RSCore.swift b/RSCore/Sources/RSCore/Shared/Data+RSCore.swift index 18211d012..c6be90c9e 100644 --- a/RSCore/Sources/RSCore/Shared/Data+RSCore.swift +++ b/RSCore/Sources/RSCore/Shared/Data+RSCore.swift @@ -7,45 +7,17 @@ // import Foundation -#if canImport(CryptoKit) import CryptoKit -#endif -import CommonCrypto public extension Data { - /// The MD5 hash of the data. var md5Hash: Data { - - #if canImport(CryptoKit) - if #available(macOS 10.15, *) { - let digest = Insecure.MD5.hash(data: self) - return Data(digest) - } else { - return ccMD5Hash - } - #else - return ccMD5Hash - #endif - + let digest = Insecure.MD5.hash(data: self) + return Data(digest) } - @available(macOS, deprecated: 10.15) - @available(iOS, deprecated: 13.0) - private var ccMD5Hash: Data { - let len = Int(CC_MD5_DIGEST_LENGTH) - let md = UnsafeMutablePointer.allocate(capacity: len) - - let _ = self.withUnsafeBytes { - CC_MD5($0.baseAddress, numericCast($0.count), md) - } - - return Data(bytes: md, count: len) - } - - /// The MD5 has of the data, as a hexadecimal string. var md5String: String? { - return md5Hash.hexadecimalString + md5Hash.hexadecimalString } /// Image signature constants. @@ -174,7 +146,5 @@ public extension Data { } return reduce("") { $0 + String(format: "%02x", $1) } - } - } diff --git a/RSWeb/Sources/RSWeb/Reachability.swift b/RSWeb/Sources/RSWeb/Reachability.swift index 6c8ffa2e4..ac09d646a 100644 --- a/RSWeb/Sources/RSWeb/Reachability.swift +++ b/RSWeb/Sources/RSWeb/Reachability.swift @@ -29,37 +29,28 @@ import SystemConfiguration import Foundation public enum ReachabilityError: Error { - case failedToCreateWithAddress(sockaddr, Int32) - case failedToCreateWithHostname(String, Int32) - case unableToSetCallback(Int32) - case unableToSetDispatchQueue(Int32) - case unableToGetFlags(Int32) -} - -@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") -public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") - -public extension Notification.Name { - static let reachabilityChanged = Notification.Name("reachabilityChanged") + case failedToCreateWithHostname(String, Int32) + case unableToGetFlags(Int32) } public class Reachability { + /// Returns true if the internet is reachable. + /// + /// Uses apple.com as an indicator for the internet at large, + /// since it’s surely one of the easiest-to-reach domain names. + /// Added 6 April 2024. Not part of Ashley Mills’s original code. + public static var internetIsReachable: Bool { + + guard let reachability = try? Reachability(hostname: "apple.com") else { + return false + } + return reachability.connection != .unavailable + } + public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () - @available(*, unavailable, renamed: "Connection") - public enum NetworkStatus: CustomStringConvertible { - case notReachable, reachableViaWiFi, reachableViaWWAN - public var description: String { - switch self { - case .reachableViaWWAN: return "Cellular" - case .reachableViaWiFi: return "WiFi" - case .notReachable: return "No Connection" - } - } - } - public enum Connection: CustomStringConvertible { @available(*, deprecated, renamed: "unavailable") case none @@ -74,29 +65,10 @@ public class Reachability { } } - public var whenReachable: NetworkReachable? - public var whenUnreachable: NetworkUnreachable? - - @available(*, deprecated, renamed: "allowsCellularConnection") - public let reachableOnWWAN: Bool = true - /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) public var allowsCellularConnection: Bool - // The notification center on which "reachability changed" events are being posted - public var notificationCenter: NotificationCenter = NotificationCenter.default - - @available(*, deprecated, renamed: "connection.description") - public var currentReachabilityString: String { - return "\(connection)" - } - - @available(*, unavailable, renamed: "connection") - public var currentReachabilityStatus: Connection { - return connection - } - - public var connection: Connection { + public var connection: Connection { if flags == nil { try? setReachabilityFlags() } @@ -109,298 +81,127 @@ public class Reachability { } } - fileprivate var isRunningOnDevice: Bool = { - #if targetEnvironment(simulator) - return false - #else - return true - #endif - }() - fileprivate(set) var notifierRunning = false fileprivate let reachabilityRef: SCNetworkReachability fileprivate let reachabilitySerialQueue: DispatchQueue - fileprivate let notificationQueue: DispatchQueue? - fileprivate(set) var flags: SCNetworkReachabilityFlags? { - didSet { - guard flags != oldValue else { return } - notifyReachabilityChanged() - } - } + fileprivate(set) var flags: SCNetworkReachabilityFlags? - required public init(reachabilityRef: SCNetworkReachability, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) { - self.allowsCellularConnection = true - self.reachabilityRef = reachabilityRef - self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) - self.notificationQueue = notificationQueue - } + required public init(reachabilityRef: SCNetworkReachability, + queueQoS: DispatchQoS = .default, + targetQueue: DispatchQueue? = nil) { + self.allowsCellularConnection = true + self.reachabilityRef = reachabilityRef + self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) + } - public convenience init(hostname: String, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) throws { - guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { - throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) - } - self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) - } - - public convenience init(queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) throws { - var zeroAddress = sockaddr() - zeroAddress.sa_len = UInt8(MemoryLayout.size) - zeroAddress.sa_family = sa_family_t(AF_INET) - - guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { - throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError()) - } - - self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) - } - - deinit { - stopNotifier() - } + public convenience init(hostname: String, + queueQoS: DispatchQoS = .default, + targetQueue: DispatchQueue? = nil) throws { + guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { + throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) + } + self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue) + } } public extension Reachability { - // MARK: - *** Notifier methods *** - func startNotifier() throws { - guard !notifierRunning else { return } - - let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in - guard let info = info else { return } - - // `weakifiedReachability` is guaranteed to exist by virtue of our - // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`. - let weakifiedReachability = Unmanaged.fromOpaque(info).takeUnretainedValue() - - // The weak `reachability` _may_ no longer exist if the `Reachability` - // object has since been deallocated but a callback was already in flight. - weakifiedReachability.reachability?.flags = flags - } - - let weakifiedReachability = ReachabilityWeakifier(reachability: self) - let opaqueWeakifiedReachability = Unmanaged.passUnretained(weakifiedReachability).toOpaque() - - var context = SCNetworkReachabilityContext( - version: 0, - info: UnsafeMutableRawPointer(opaqueWeakifiedReachability), - retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - _ = unmanagedWeakifiedReachability.retain() - return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque()) - }, - release: { (info: UnsafeRawPointer) -> Void in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - unmanagedWeakifiedReachability.release() - }, - copyDescription: { (info: UnsafeRawPointer) -> Unmanaged in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue() - let description = weakifiedReachability.reachability?.description ?? "nil" - return Unmanaged.passRetained(description as CFString) - } - ) - - if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { - stopNotifier() - throw ReachabilityError.unableToSetCallback(SCError()) - } - - if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { - stopNotifier() - throw ReachabilityError.unableToSetDispatchQueue(SCError()) - } - - // Perform an initial check - try setReachabilityFlags() - - notifierRunning = true - } - - func stopNotifier() { - defer { notifierRunning = false } - - SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) - SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) - } - - // MARK: - *** Connection test methods *** - @available(*, deprecated, message: "Please use `connection != .none`") - var isReachable: Bool { - return connection != .unavailable - } - - @available(*, deprecated, message: "Please use `connection == .cellular`") - var isReachableViaWWAN: Bool { - // Check we're not on the simulator, we're REACHABLE and check we're on WWAN - return connection == .cellular - } - - @available(*, deprecated, message: "Please use `connection == .wifi`") - var isReachableViaWiFi: Bool { - return connection == .wifi - } - - var description: String { - return flags?.description ?? "unavailable flags" - } + var description: String { + return flags?.description ?? "unavailable flags" + } } fileprivate extension Reachability { - func setReachabilityFlags() throws { - try reachabilitySerialQueue.sync { [unowned self] in - var flags = SCNetworkReachabilityFlags() - if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) { - self.stopNotifier() - throw ReachabilityError.unableToGetFlags(SCError()) - } - - self.flags = flags - } - } - + func setReachabilityFlags() throws { + try reachabilitySerialQueue.sync { [unowned self] in + var flags = SCNetworkReachabilityFlags() + if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) { + throw ReachabilityError.unableToGetFlags(SCError()) + } - func notifyReachabilityChanged() { - let notify = { [weak self] in - guard let self = self else { return } - self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) - self.notificationCenter.post(name: .reachabilityChanged, object: self) - } - - // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`) - notificationQueue?.async(execute: notify) ?? notify() - } + self.flags = flags + } + } } extension SCNetworkReachabilityFlags { - typealias Connection = Reachability.Connection + typealias Connection = Reachability.Connection - var connection: Connection { - guard isReachableFlagSet else { return .unavailable } + var connection: Connection { + guard isReachableFlagSet else { return .unavailable } - // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi - #if targetEnvironment(simulator) - return .wifi - #else - var connection = Connection.unavailable + // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi + #if targetEnvironment(simulator) + return .wifi + #else + var connection = Connection.unavailable - if !isConnectionRequiredFlagSet { - connection = .wifi - } + if !isConnectionRequiredFlagSet { + connection = .wifi + } - if isConnectionOnTrafficOrDemandFlagSet { - if !isInterventionRequiredFlagSet { - connection = .wifi - } - } + if isConnectionOnTrafficOrDemandFlagSet { + if !isInterventionRequiredFlagSet { + connection = .wifi + } + } - if isOnWWANFlagSet { - connection = .cellular - } + if isOnWWANFlagSet { + connection = .cellular + } - return connection - #endif - } + return connection + #endif + } - var isOnWWANFlagSet: Bool { - #if os(iOS) - return contains(.isWWAN) - #else - return false - #endif - } - var isReachableFlagSet: Bool { - return contains(.reachable) - } - var isConnectionRequiredFlagSet: Bool { - return contains(.connectionRequired) - } - var isInterventionRequiredFlagSet: Bool { - return contains(.interventionRequired) - } - var isConnectionOnTrafficFlagSet: Bool { - return contains(.connectionOnTraffic) - } - var isConnectionOnDemandFlagSet: Bool { - return contains(.connectionOnDemand) - } - var isConnectionOnTrafficOrDemandFlagSet: Bool { - return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty - } - var isTransientConnectionFlagSet: Bool { - return contains(.transientConnection) - } - var isLocalAddressFlagSet: Bool { - return contains(.isLocalAddress) - } - var isDirectFlagSet: Bool { - return contains(.isDirect) - } - var isConnectionRequiredAndTransientFlagSet: Bool { - return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] - } + var isOnWWANFlagSet: Bool { + #if os(iOS) + return contains(.isWWAN) + #else + return false + #endif + } + var isReachableFlagSet: Bool { + return contains(.reachable) + } + var isConnectionRequiredFlagSet: Bool { + return contains(.connectionRequired) + } + var isInterventionRequiredFlagSet: Bool { + return contains(.interventionRequired) + } + var isConnectionOnTrafficFlagSet: Bool { + return contains(.connectionOnTraffic) + } + var isConnectionOnDemandFlagSet: Bool { + return contains(.connectionOnDemand) + } + var isConnectionOnTrafficOrDemandFlagSet: Bool { + return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty + } + var isTransientConnectionFlagSet: Bool { + return contains(.transientConnection) + } + var isLocalAddressFlagSet: Bool { + return contains(.isLocalAddress) + } + var isDirectFlagSet: Bool { + return contains(.isDirect) + } - var description: String { - let W = isOnWWANFlagSet ? "W" : "-" - let R = isReachableFlagSet ? "R" : "-" - let c = isConnectionRequiredFlagSet ? "c" : "-" - let t = isTransientConnectionFlagSet ? "t" : "-" - let i = isInterventionRequiredFlagSet ? "i" : "-" - let C = isConnectionOnTrafficFlagSet ? "C" : "-" - let D = isConnectionOnDemandFlagSet ? "D" : "-" - let l = isLocalAddressFlagSet ? "l" : "-" - let d = isDirectFlagSet ? "d" : "-" + var description: String { + let W = isOnWWANFlagSet ? "W" : "-" + let R = isReachableFlagSet ? "R" : "-" + let c = isConnectionRequiredFlagSet ? "c" : "-" + let t = isTransientConnectionFlagSet ? "t" : "-" + let i = isInterventionRequiredFlagSet ? "i" : "-" + let C = isConnectionOnTrafficFlagSet ? "C" : "-" + let D = isConnectionOnDemandFlagSet ? "D" : "-" + let l = isLocalAddressFlagSet ? "l" : "-" + let d = isDirectFlagSet ? "d" : "-" - return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" - } -} - -/** - `ReachabilityWeakifier` weakly wraps the `Reachability` class - in order to break retain cycles when interacting with CoreFoundation. - - CoreFoundation callbacks expect a pair of retain/release whenever an - opaque `info` parameter is provided. These callbacks exist to guard - against memory management race conditions when invoking the callbacks. - - #### Race Condition - - If we passed `SCNetworkReachabilitySetCallback` a direct reference to our - `Reachability` class without also providing corresponding retain/release - callbacks, then a race condition can lead to crashes when: - - `Reachability` is deallocated on thread X - - A `SCNetworkReachability` callback(s) is already in flight on thread Y - - #### Retain Cycle - - If we pass `Reachability` to CoreFoundtion while also providing retain/ - release callbacks, we would create a retain cycle once CoreFoundation - retains our `Reachability` class. This fixes the crashes and his how - CoreFoundation expects the API to be used, but doesn't play nicely with - Swift/ARC. This cycle would only be broken after manually calling - `stopNotifier()` — `deinit` would never be called. - - #### ReachabilityWeakifier - - By providing both retain/release callbacks and wrapping `Reachability` in - a weak wrapper, we: - - interact correctly with CoreFoundation, thereby avoiding a crash. - See "Memory Management Programming Guide for Core Foundation". - - don't alter the public API of `Reachability.swift` in any way - - still allow for automatic stopping of the notifier on `deinit`. - */ -private class ReachabilityWeakifier { - weak var reachability: Reachability? - init(reachability: Reachability) { - self.reachability = reachability - } + return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" + } } diff --git a/Shared/Widget/WidgetDataEncoder.swift b/Shared/Widget/WidgetDataEncoder.swift index e2ed90eab..3f36dde57 100644 --- a/Shared/Widget/WidgetDataEncoder.swift +++ b/Shared/Widget/WidgetDataEncoder.swift @@ -34,40 +34,37 @@ public final class WidgetDataEncoder { } func encode() { - if #available(iOS 14, *) { - isRunning = true - - flushSharedContainer() - os_log(.debug, log: log, "Starting encoding widget data.") - - DispatchQueue.main.async { - self.encodeWidgetData() { latestData in - guard let latestData = latestData else { - self.isRunning = false - return - } - - let encodedData = try? JSONEncoder().encode(latestData) - - os_log(.debug, log: self.log, "Finished encoding widget data.") - - if self.fileExists() { - try? FileManager.default.removeItem(at: self.dataURL!) - os_log(.debug, log: self.log, "Removed widget data from container.") - } - - if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) { - os_log(.debug, log: self.log, "Wrote widget data to container.") - WidgetCenter.shared.reloadAllTimelines() - } - + isRunning = true + + flushSharedContainer() + os_log(.debug, log: log, "Starting encoding widget data.") + + DispatchQueue.main.async { + self.encodeWidgetData() { latestData in + guard let latestData = latestData else { self.isRunning = false + return } + + let encodedData = try? JSONEncoder().encode(latestData) + + os_log(.debug, log: self.log, "Finished encoding widget data.") + + if self.fileExists() { + try? FileManager.default.removeItem(at: self.dataURL!) + os_log(.debug, log: self.log, "Removed widget data from container.") + } + + if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) { + os_log(.debug, log: self.log, "Wrote widget data to container.") + WidgetCenter.shared.reloadAllTimelines() + } + + self.isRunning = false } } } - @available(iOS 14, *) private func encodeWidgetData(completion: @escaping (WidgetData?) -> Void) { let dispatchGroup = DispatchGroup() var groupError: Error? = nil diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 63ea35faa..16857fc8e 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -48,9 +48,7 @@ class KeyboardManager { static func createKeyCommand(title: String, action: String, input: String, modifiers: UIKeyModifierFlags) -> UIKeyCommand { let selector = NSSelectorFromString(action) 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 - } + keyCommand.wantsPriorityOverSystemBehavior = true return keyCommand } @@ -67,9 +65,7 @@ private extension KeyboardManager { return KeyboardManager.createKeyCommand(title: title, action: action, input: input, modifiers: modifiers) } else { let keyCommand = UIKeyCommand(input: input, modifierFlags: modifiers, action: NSSelectorFromString(action)) - if #available(iOS 15.0, *) { - keyCommand.wantsPriorityOverSystemBehavior = true - } + keyCommand.wantsPriorityOverSystemBehavior = true return keyCommand } } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift index 4cb6f90e6..bf9c5a8da 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift @@ -197,9 +197,7 @@ private extension MasterFeedTableViewCell { disclosureButton?.tintColor = AppAssets.controlBackgroundColor disclosureButton?.imageView?.contentMode = .center disclosureButton?.imageView?.clipsToBounds = false - if #available(iOS 13.4, *) { - disclosureButton?.addInteraction(UIPointerInteraction()) - } + disclosureButton?.addInteraction(UIPointerInteraction()) addSubviewAtInit(disclosureButton!) } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift index dc5b21ed6..ee9cc296b 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift @@ -88,9 +88,7 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView { button.tintColor = UIColor.tertiaryLabel button.setImage(AppAssets.disclosureImage, for: .normal) button.contentMode = .center - if #available(iOS 13.4, *) { - button.addInteraction(UIPointerInteraction()) - } + button.addInteraction(UIPointerInteraction()) button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside) return button }() diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index db42ab424..2f26ceda9 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -19,11 +19,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { private var refreshProgressView: RefreshProgressView? @IBOutlet weak var addNewItemButton: UIBarButtonItem! { didSet { - if #available(iOS 14, *) { - addNewItemButton.primaryAction = nil - } else { - addNewItemButton.action = #selector(MasterFeedViewController.add(_:)) - } + addNewItemButton.primaryAction = nil } } @@ -428,37 +424,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } @IBAction func add(_ sender: UIBarButtonItem) { - - if #available(iOS 14, *) { - - } else { - let title = NSLocalizedString("Add Item", comment: "Add Item") - let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet) - - let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") - let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) - - let addFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed") - let addFeedAction = UIAlertAction(title: addFeedActionTitle, style: .default) { _ in - self.coordinator.showAddFeed() - } - - let addWebFolderdActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") - let addWebFolderAction = UIAlertAction(title: addWebFolderdActionTitle, style: .default) { _ in - self.coordinator.showAddFolder() - } - - alertController.addAction(addFeedAction) - - alertController.addAction(addWebFolderAction) - alertController.addAction(cancelAction) - - alertController.popoverPresentationController?.barButtonItem = sender - - present(alertController, animated: true) - } - - } @objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) { @@ -638,35 +603,32 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { @objc func configureContextMenu(_: Any? = nil) { - if #available(iOS 14.0, *) { - - /* - Context Menu Order: - 1. Add Feed - 3. Add Folder - */ - - var menuItems: [UIAction] = [] - - let addFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed") - let addFeedAction = UIAction(title: addFeedActionTitle, image: AppAssets.plus) { _ in - self.coordinator.showAddFeed() - } - menuItems.append(addFeedAction) - - let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") - let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in - self.coordinator.showAddFolder() - } - - menuItems.append(addWebFolderAction) - - let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed()) - - self.addNewItemButton.menu = contextMenu + /* + Context Menu Order: + 1. Add Feed + 3. Add Folder + */ + + var menuItems: [UIAction] = [] + + let addFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed") + let addFeedAction = UIAction(title: addFeedActionTitle, image: AppAssets.plus) { _ in + self.coordinator.showAddFeed() } + menuItems.append(addFeedAction) + + let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") + let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in + self.coordinator.showAddFolder() + } + + menuItems.append(addWebFolderAction) + + let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed()) + + self.addNewItemButton.menu = contextMenu } - + func focus() { becomeFirstResponder() } diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.swift b/iOS/MasterTimeline/MasterTimelineTitleView.swift index 9c81ac7ee..fcca1817d 100644 --- a/iOS/MasterTimeline/MasterTimelineTitleView.swift +++ b/iOS/MasterTimeline/MasterTimelineTitleView.swift @@ -14,7 +14,6 @@ class MasterTimelineTitleView: UIView { @IBOutlet weak var label: UILabel! @IBOutlet weak var unreadCountView: MasterTimelineUnreadCountView! - @available(iOS 13.4, *) private lazy var pointerInteraction: UIPointerInteraction = { UIPointerInteraction(delegate: self) }() @@ -35,24 +34,18 @@ class MasterTimelineTitleView: UIView { func buttonize() { heightAnchor.constraint(equalToConstant: 40.0).isActive = true accessibilityTraits = .button - if #available(iOS 13.4, *) { - addInteraction(pointerInteraction) - } + addInteraction(pointerInteraction) } func debuttonize() { heightAnchor.constraint(equalToConstant: 40.0).isActive = true accessibilityTraits.remove(.button) - if #available(iOS 13.4, *) { - removeInteraction(pointerInteraction) - } + removeInteraction(pointerInteraction) } - } extension MasterTimelineTitleView: UIPointerInteractionDelegate { - @available(iOS 13.4, *) func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { var rect = self.frame rect.origin.x = rect.origin.x - 10 @@ -60,5 +53,4 @@ extension MasterTimelineTitleView: UIPointerInteractionDelegate { return UIPointerStyle(effect: .automatic(UITargetedPreview(view: self)), shape: .roundedRect(rect)) } - } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index c9128acf0..2084e40a6 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -79,9 +79,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner // Configure the table tableView.dataSource = dataSource - if #available(iOS 15.0, *) { - tableView.isPrefetchingEnabled = false - } + tableView.isPrefetchingEnabled = false + numberOfTextLines = AppDefaults.shared.timelineNumberOfLines iconSize = AppDefaults.shared.timelineIconSize resetEstimatedRowHeight() @@ -104,13 +103,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } // Disable swipe back on iPad Mice - if #available(iOS 13.4, *) { - guard let gesture = self.navigationController?.interactivePopGestureRecognizer as? UIPanGestureRecognizer else { - return - } - gesture.allowedScrollTypesMask = [] + guard let gesture = self.navigationController?.interactivePopGestureRecognizer as? UIPanGestureRecognizer else { + return } - + gesture.allowedScrollTypesMask = [] } override func viewWillAppear(_ animated: Bool) {