Remove unneeded #available and @available.
This commit is contained in:
parent
8a611f4109
commit
a8eb75c20f
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<CUnsignedChar>.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) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<sockaddr>.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<ReachabilityWeakifier>.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<ReachabilityWeakifier>.passUnretained(weakifiedReachability).toOpaque()
|
||||
|
||||
var context = SCNetworkReachabilityContext(
|
||||
version: 0,
|
||||
info: UnsafeMutableRawPointer(opaqueWeakifiedReachability),
|
||||
retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in
|
||||
let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
|
||||
_ = unmanagedWeakifiedReachability.retain()
|
||||
return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque())
|
||||
},
|
||||
release: { (info: UnsafeRawPointer) -> Void in
|
||||
let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
|
||||
unmanagedWeakifiedReachability.release()
|
||||
},
|
||||
copyDescription: { (info: UnsafeRawPointer) -> Unmanaged<CFString> in
|
||||
let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.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)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue