diff --git a/Zebra.xcodeproj/project.pbxproj b/Zebra.xcodeproj/project.pbxproj index ce4f6c118b..f44d153f3a 100644 --- a/Zebra.xcodeproj/project.pbxproj +++ b/Zebra.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 4E339B8027D0E45300F013C1 /* URL+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E339B7F27D0E45300F013C1 /* URL+Additions.swift */; }; 4E3B564F2853196D0058F096 /* SourceRefreshController+AppLifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B564E2853196D0058F096 /* SourceRefreshController+AppLifecycle.swift */; }; 4E5F41C6285C2952003658AA /* libzstd.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EB235B5283E1A7300713CBB /* libzstd.xcframework */; }; + 4E5F41CD285CB723003658AA /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4E5F41CC285CB723003658AA /* Kingfisher */; }; 4E6F94472856434D002F5BD9 /* CSProgress+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6F94462856434D002F5BD9 /* CSProgress+Additions.swift */; }; 4E6F945128564BD7002F5BD9 /* CSProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6F945028564BD7002F5BD9 /* CSProgress.swift */; }; 4E7DC8B128589B0C0007392D /* BaseListCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7DC8B028589B0C0007392D /* BaseListCollectionViewController.swift */; }; @@ -61,7 +62,6 @@ 4EA26EA627CCBD3B0019A5AA /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA26EA527CCBD3B0019A5AA /* LoadingViewController.swift */; }; 4EA26EB727CCDAC80019A5AA /* BaseSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA26EB627CCDAC80019A5AA /* BaseSceneDelegate.swift */; }; 4EA26EBA27CCEEBC0019A5AA /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA26EB927CCEEBC0019A5AA /* BrowseViewController.swift */; }; - 4EA26EC127CCF52E0019A5AA /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA26EC027CCF52E0019A5AA /* SDWebImage */; }; 4EA26EC427CCF6500019A5AA /* DepictionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA26EC327CCF6500019A5AA /* DepictionKit */; }; 4EA2F1DD27DA2A0D0080DC35 /* Installed.pack in Resources */ = {isa = PBXBuildFile; fileRef = 4EA2F1DC27DA2A0D0080DC35 /* Installed.pack */; }; 4EA893DD27DDABD600E2EC7A /* sandboxed.json in Resources */ = {isa = PBXBuildFile; fileRef = 4EA893DC27DDABD600E2EC7A /* sandboxed.json */; }; @@ -507,10 +507,10 @@ 4EBF6FBB2841084600639096 /* Plains.framework in Frameworks */, E1428E7927794FC4005B0885 /* Evander in Frameworks */, 4EA26EC427CCF6500019A5AA /* DepictionKit in Frameworks */, - 4EA26EC127CCF52E0019A5AA /* SDWebImage in Frameworks */, 4E87FBB52856DB96005C4704 /* libCSProgress.a in Frameworks */, 895071992239E414004AE338 /* libMobileGestalt.tbd in Frameworks */, 898C50432649F9E0002CDC09 /* MessageUI.framework in Frameworks */, + 4E5F41CD285CB723003658AA /* Kingfisher in Frameworks */, E193C67E277A91CA00B2469E /* WebKit.framework in Frameworks */, 89051722237239A9000F0A32 /* libc++.tbd in Frameworks */, ); @@ -1355,8 +1355,8 @@ name = Zebra; packageProductDependencies = ( E1428E7827794FC4005B0885 /* Evander */, - 4EA26EC027CCF52E0019A5AA /* SDWebImage */, 4EA26EC327CCF6500019A5AA /* DepictionKit */, + 4E5F41CC285CB723003658AA /* Kingfisher */, ); productName = Zebra; productReference = 89C5FDDD21B1B13700A10E58 /* Zebra.app */; @@ -1437,7 +1437,7 @@ mainGroup = 89C5FDD421B1B13700A10E58; packageReferences = ( E1428E7727794FC4005B0885 /* XCRemoteSwiftPackageReference "Evander" */, - 4EA26EBF27CCF52E0019A5AA /* XCRemoteSwiftPackageReference "SDWebImage" */, + 4E5F41CB285CB723003658AA /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = 89C5FDDE21B1B13700A10E58 /* Products */; projectDirPath = ""; @@ -2124,12 +2124,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 4EA26EBF27CCF52E0019A5AA /* XCRemoteSwiftPackageReference "SDWebImage" */ = { + 4E5F41CB285CB723003658AA /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SDWebImage/SDWebImage"; + repositoryURL = "https://github.com/onevcat/Kingfisher"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.0.0; + minimumVersion = 7.0.0; }; }; E1428E7727794FC4005B0885 /* XCRemoteSwiftPackageReference "Evander" */ = { @@ -2143,10 +2143,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 4EA26EC027CCF52E0019A5AA /* SDWebImage */ = { + 4E5F41CC285CB723003658AA /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = 4EA26EBF27CCF52E0019A5AA /* XCRemoteSwiftPackageReference "SDWebImage" */; - productName = SDWebImage; + package = 4E5F41CB285CB723003658AA /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; }; 4EA26EC327CCF6500019A5AA /* DepictionKit */ = { isa = XCSwiftPackageProductDependency; diff --git a/Zebra.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Zebra.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44b6e5c2fe..0dfa36b5ef 100644 --- a/Zebra.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Zebra.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -20,12 +20,12 @@ } }, { - "package": "SDWebImage", - "repositoryURL": "https://github.com/SDWebImage/SDWebImage", + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher", "state": { "branch": null, - "revision": "2e63d0061da449ad0ed130768d05dceb1496de44", - "version": "5.12.5" + "revision": "022e4eeb79f817748544b318b991d9a70036bbf8", + "version": "7.2.4" } } ] diff --git a/Zebra/Controllers/App/AppDelegate.swift b/Zebra/Controllers/App/AppDelegate.swift index a1662e12c9..b77e0b83c6 100644 --- a/Zebra/Controllers/App/AppDelegate.swift +++ b/Zebra/Controllers/App/AppDelegate.swift @@ -8,7 +8,7 @@ import UIKit import os.log -import SDWebImage +import Kingfisher @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -32,7 +32,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ = SourceRefreshController.shared - SDImageCache.shared.config.maxDiskAge = 1 * 24 * 60 * 60 // 1 day + KingfisherManager.shared.defaultOptions = [ + .transition(.fade(0.2)), + .cacheOriginalImage, + .backgroundDecode + ] + KingfisherManager.shared.downloader.sessionConfiguration = URLSession.image.configuration UITabBarItem.appearance().badgeColor = .badge diff --git a/Zebra/Extensions/UIImage+Additions.swift b/Zebra/Extensions/UIImage+Additions.swift index bbac76763d..9a9d5b7821 100644 --- a/Zebra/Extensions/UIImage+Additions.swift +++ b/Zebra/Extensions/UIImage+Additions.swift @@ -7,6 +7,56 @@ // import UIKit +import Kingfisher + +extension UIImageView { + + /// > Note: Wait till the image view has been laid out before calling this. If `frame.size == .zero`, + /// > the image load will be skipped. Call this from `layoutSubviews` to ensure relayout as needed. + func load(url: URL?, usingScale: Bool = false, fallbackImage: UIImage? = nil) { + let scale = window?.screen.scale ?? UIScreen.main.scale + var sources = [Kingfisher.Source]() + + if let url = url?.secureURL { + // Scaled image + if usingScale && scale != 1 { + let fileBaseName = url + .deletingPathExtension() + .lastPathComponent + .replacingOccurrences(regex: "@\\d+x$", with: "") + + let numberFormatter = NumberFormatter() + numberFormatter.maximumFractionDigits = 1 + + let scaledURL = url/".."/"\(fileBaseName)@\(numberFormatter.string(for: scale)!)x.\(url.pathExtension)" + if scaledURL != url { + sources += [.network(scaledURL)] + } + } + + // Originally supplied url + sources += [.network(url)] + } + + if sources.isEmpty || frame.size == .zero { + kf.cancelDownloadTask() + image = fallbackImage + return + } + + image = nil + let primarySource = sources.removeFirst() + kf.setImage(with: primarySource, + placeholder: fallbackImage, + options: [ + .keepCurrentImageWhileLoading, + .processor(DownsamplingImageProcessor(size: frame.size)), + .scaleFactor(scale), + .alternativeSources(sources) + ]) + } + +} extension UIImage.SymbolConfiguration { diff --git a/Zebra/Extensions/URL+Additions.swift b/Zebra/Extensions/URL+Additions.swift index 79ef107a2b..99593579d6 100644 --- a/Zebra/Extensions/URL+Additions.swift +++ b/Zebra/Extensions/URL+Additions.swift @@ -8,10 +8,47 @@ import Foundation +fileprivate let permittedInsecureDomains: [String] = { + if let ats = Bundle.main.object(forInfoDictionaryKey: "NSAppTransportSecurity") as? [String: Any], + let exceptions = ats["NSExceptionDomains"] as? [String: [String: Any]] { + return exceptions.compactMap { key, value in (value["NSExceptionAllowsInsecureHTTPLoads"] as? Bool) ?? false ? key : nil } + } + return [] +}() + extension URL { static func / (lhs: URL, rhs: String) -> URL { rhs == ".." ? lhs.deletingLastPathComponent() : lhs.appendingPathComponent(rhs) } + + /// Return a URL that can be loaded, or at least attempted to be loaded, within App Transport + /// Security restrictions. + var secureURL: URL? { + switch scheme { + case "http": + if let host = host, + permittedInsecureDomains.contains(host) { + return self + } + + guard var url = URLComponents(url: self, resolvingAgainstBaseURL: true) else { + return nil + } + url.scheme = "https" + return url.url + + case "https", "file": + return self + + default: + return nil + } + } + + /// Return a cleaned URL for display. + var displayString: String { + absoluteString.replacingOccurrences(regex: "^https?://|/$", with: "") + } } extension FileManager { diff --git a/Zebra/Extensions/URLSession+Additions.swift b/Zebra/Extensions/URLSession+Additions.swift index 7baf31cab6..159b5c6c44 100644 --- a/Zebra/Extensions/URLSession+Additions.swift +++ b/Zebra/Extensions/URLSession+Additions.swift @@ -11,20 +11,28 @@ import Foundation extension URLSession { static let standard: URLSession = { + let config = URLSessionConfiguration.default.copy() as! URLSessionConfiguration + config.httpCookieStorage = nil + config.httpCookieAcceptPolicy = .never + config.httpAdditionalHeaders = URLController.httpHeaders + config.tlsMinimumSupportedProtocolVersion = .TLSv12 + return URLSession(configuration: config) + }() + + static let image: URLSession = { let config = URLSessionConfiguration.ephemeral.copy() as! URLSessionConfiguration - // Disable setting or storing cookies. Requests made via zbra_standardSession shouldn’t be - // using cookies. + config.urlCache = nil config.httpCookieStorage = nil + config.httpCookieAcceptPolicy = .never config.httpAdditionalHeaders = URLController.httpHeaders config.tlsMinimumSupportedProtocolVersion = .TLSv12 return URLSession(configuration: config) }() static let download: URLSession = { - let config = URLSessionConfiguration.default.copy() as! URLSessionConfiguration - // Disable setting or storing cookies. Requests made via zbra_standardSession shouldn’t be - // using cookies. - config.httpMaximumConnectionsPerHost = 8 + let config = URLSessionConfiguration.ephemeral.copy() as! URLSessionConfiguration + config.httpCookieStorage = nil + config.httpCookieAcceptPolicy = .never config.httpAdditionalHeaders = URLController.aptHeaders config.tlsMinimumSupportedProtocolVersion = .TLSv12 return URLSession(configuration: config) diff --git a/Zebra/UI/Common/IconImageView.swift b/Zebra/UI/Common/IconImageView.swift index f75d2b3777..87bdf5de1f 100644 --- a/Zebra/UI/Common/IconImageView.swift +++ b/Zebra/UI/Common/IconImageView.swift @@ -7,21 +7,16 @@ // import UIKit -import SDWebImage class IconImageView: UIView { var image: UIImage? { get { imageView.image } set { - imageView.sd_cancelCurrentImageLoad() + imageView.load(url: nil) imageView.image = newValue } } - var imageURL: URL? { - didSet { updateImageURL() } - } - var fallbackImage: UIImage? private var backgroundView: UIView! private var imageView: UIImageView! @@ -44,7 +39,6 @@ class IconImageView: UIView { imageView.layer.cornerCurve = .continuous imageView.layer.minificationFilter = .trilinear imageView.layer.magnificationFilter = .trilinear - imageView.sd_imageTransition = .fade(duration: 0.2) addSubview(imageView) borderView = UIView(frame: bounds) @@ -78,53 +72,8 @@ class IconImageView: UIView { borderView.layer.borderColor = UIColor.separator.cgColor } - func setImageURL(_ imageURL: URL, fallbackImage: UIImage? = nil) { - self.fallbackImage = fallbackImage - self.imageURL = imageURL - } - - private func loadImage(url: URL, completion: SDExternalCompletionBlock? = nil) { - imageView.sd_imageTransition = .fade(duration: 0.2) - imageView.sd_setImage(with: url, - placeholderImage: fallbackImage, - options: [.delayPlaceholder, .decodeFirstFrameOnly, .scaleDownLargeImages], - completed: completion) - } - - private func updateImageURL() { - imageView.sd_cancelCurrentImageLoad() - - guard let imageURL = imageURL, - var url = URLComponents(url: imageURL, resolvingAgainstBaseURL: true) else { - imageView.image = fallbackImage - return - } - - imageView.image = nil - - // TODO: The retina scaling fallback is broken - let scale = window?.screen.scale ?? UIScreen.main.scale - if true||scale == 1 { - loadImage(url: imageURL) - } else { - // Try native scale first, falling back to original url. - var fileBaseName = (imageURL.lastPathComponent as NSString).deletingPathExtension - fileBaseName = fileBaseName.replacingOccurrences(regex: "@\\d+x$", with: "") - - let numberFormatter = NumberFormatter() - numberFormatter.maximumFractionDigits = 1 - - var pathComponents = imageURL.pathComponents - pathComponents.removeLast() - pathComponents.append("\(fileBaseName)@\(numberFormatter.string(for: scale)!)x.\(imageURL.pathExtension)") - url.path = pathComponents.joined(separator: "/") - loadImage(url: url.url!) { image, _, _, _ in - if image == nil { - // Fall back to original url. - self.imageView.sd_setImage(with: imageURL) - } - } - } + func setImageURL(_ url: URL?, usingScale: Bool = true, fallbackImage: UIImage? = nil) { + imageView.load(url: url, usingScale: usingScale, fallbackImage: fallbackImage) } } diff --git a/Zebra/UI/Sources/ZBSourceAddViewController.mm b/Zebra/UI/Sources/ZBSourceAddViewController.mm index 8f88ef61f4..7c73b49642 100644 --- a/Zebra/UI/Sources/ZBSourceAddViewController.mm +++ b/Zebra/UI/Sources/ZBSourceAddViewController.mm @@ -17,7 +17,6 @@ #import "Zebra-Swift.h" #import -#import @interface ZBSourceAddViewController () { UISearchController *searchController; @@ -275,7 +274,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.sourceLabel.hidden = NO; cell.sourceLabel.text = clipboardSource.origin; cell.urlLabel.text = NSLocalizedString(@"From your clipboard", @""); - [cell.iconImageView sd_setImageWithURL:clipboardSource.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; +// [cell.iconImageView sd_setImageWithURL:clipboardSource.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; } } else if (indexPath.section == 1 && searchTermIsEmpty) { if (indexPath.row == 0) { @@ -297,7 +296,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.sourceLabel.hidden = NO; cell.sourceLabel.text = manager[@"name"]; cell.urlLabel.text = manager[@"label"]; - [cell.iconImageView sd_setImageWithURL:[NSURL URLWithString:manager[@"icon"]] placeholderImage:[UIImage imageNamed:@"Unknown"]]; +// [cell.iconImageView sd_setImageWithURL:[NSURL URLWithString:manager[@"icon"]] placeholderImage:[UIImage imageNamed:@"Unknown"]]; } } else if (indexPath.section == 2 && searchTermIsURL) { if (enteredSource) { @@ -323,7 +322,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.sourceLabel.hidden = NO; cell.sourceLabel.text = enteredSource.origin; cell.urlLabel.text = enteredSource.repositoryURI; - [cell.iconImageView sd_setImageWithURL:enteredSource.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; +// [cell.iconImageView sd_setImageWithURL:enteredSource.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; } } } else if (indexPath.section == 3) { @@ -343,7 +342,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.sourceLabel.text = source.origin; cell.urlLabel.text = source.repositoryURI; - [cell.iconImageView sd_setImageWithURL:source.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; +// [cell.iconImageView sd_setImageWithURL:source.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; } return cell; diff --git a/Zebra/UI/Sources/ZBSourceImportViewController.mm b/Zebra/UI/Sources/ZBSourceImportViewController.mm index a9d74e2ad6..4188103c97 100644 --- a/Zebra/UI/Sources/ZBSourceImportViewController.mm +++ b/Zebra/UI/Sources/ZBSourceImportViewController.mm @@ -17,7 +17,6 @@ #import #import "Zebra-Swift.h" -#import @interface ZBSourceImportViewController () { double individualIncrement; @@ -225,7 +224,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.sourceLabel.text = self.titles[source.UUID]; cell.urlLabel.text = source.repositoryURI; - [cell.iconImageView sd_setImageWithURL:source.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; +// [cell.iconImageView sd_setImageWithURL:source.iconURL placeholderImage:[UIImage imageNamed:@"Unknown"]]; return cell; }