diff --git a/Caches/ImageCacheConfiguration.swift b/Caches/ImageCacheConfiguration.swift index c2de384..71ac819 100644 --- a/Caches/ImageCacheConfiguration.swift +++ b/Caches/ImageCacheConfiguration.swift @@ -1,7 +1,7 @@ // Copyright © 2021 Metabolist. All rights reserved. import Foundation -import Kingfisher +import SDWebImage import ServiceLayer struct ImageCacheConfiguration { @@ -14,19 +14,23 @@ struct ImageCacheConfiguration { extension ImageCacheConfiguration { func configure() throws { - KingfisherManager.shared.cache = try ImageCache( - name: Self.name, - cacheDirectoryURL: Self.directoryURL) - try KingfisherManager.shared.defaultOptions = [ - .cacheSerializer(ImageCacheSerializer(service: .init(environment: environment))) - ] + SDImageCache.defaultDiskCacheDirectory = Self.imageCacheDirectoryURL?.path + ImageDiskCache.service = try ImageSerializationService(environment: environment) + SDImageCacheConfig.default.diskCacheClass = ImageDiskCache.self + + if let legacyImageCacheDirectoryURL = Self.legacyImageCacheDirectoryURL, + FileManager.default.fileExists(atPath: legacyImageCacheDirectoryURL.path) { + try? FileManager.default.removeItem(at: legacyImageCacheDirectoryURL) + } } } private extension ImageCacheConfiguration { - static let name = "Images" - static let directoryURL = FileManager.default.containerURL( + static let cachesDirectoryURL = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: AppEnvironment.appGroup)? .appendingPathComponent("Library") .appendingPathComponent("Caches") + static let imageCacheDirectoryURL = cachesDirectoryURL?.appendingPathComponent("com.metabolist.metatext.images") + static let legacyImageCacheDirectoryURL = + cachesDirectoryURL?.appendingPathComponent("com.onevcat.Kingfisher.ImageCache.Images") } diff --git a/Caches/ImageCacheSerializer.swift b/Caches/ImageCacheSerializer.swift deleted file mode 100644 index 62604db..0000000 --- a/Caches/ImageCacheSerializer.swift +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2021 Metabolist. All rights reserved. - -import Foundation -import Kingfisher -import ServiceLayer - -struct ImageCacheSerializer { - private let service: ImageSerializationService - - init(service: ImageSerializationService) { - self.service = service - } -} - -extension ImageCacheSerializer: CacheSerializer { - func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { - guard let data = image.kf.data(format: original?.kf.imageFormat ?? .unknown) else { return nil } - - return try? service.serialize(data: data) - } - - func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { - guard let deserialized = try? service.deserialize(data: data) else { return nil } - - return KingfisherWrapper.image(data: deserialized, options: .init()) - } -} diff --git a/Caches/ImageDiskCache.swift b/Caches/ImageDiskCache.swift new file mode 100644 index 0000000..a9e0b0c --- /dev/null +++ b/Caches/ImageDiskCache.swift @@ -0,0 +1,39 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation +import SDWebImage +import ServiceLayer + +final class ImageDiskCache: SDDiskCache { + static var service: ImageSerializationService? + + private let cachePath: String + + required init?(cachePath: String, config: SDImageCacheConfig) { + self.cachePath = cachePath + + super.init(cachePath: cachePath, config: config) + } + + override func data(forKey key: String) -> Data? { + guard let data = super.data(forKey: key) else { return nil } + + return try? Self.service?.deserialize(data: data) + } + + override func setData(_ data: Data?, forKey key: String) { + guard let data = data else { + super.setData(nil, forKey: key) + + return + } + + super.setData(try? Self.service?.serialize(data: data), forKey: key) + } + + override func cachePath(forKey key: String) -> String? { + guard let service = Self.service else { return super.cachePath(forKey: key) } + + return (cachePath as NSString).appendingPathComponent(service.cacheKey(forKey: key)) + } +} diff --git a/Caches/PrefetchRequestModifier.swift b/Caches/PrefetchRequestModifier.swift deleted file mode 100644 index 7a415d1..0000000 --- a/Caches/PrefetchRequestModifier.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2021 Metabolist. All rights reserved. - -import Foundation -import Kingfisher - -struct PrefetchRequestModifier: ImageDownloadRequestModifier { - func modified(for request: URLRequest) -> URLRequest? { - var mutableRequest = request - - mutableRequest.allowsExpensiveNetworkAccess = false - mutableRequest.allowsConstrainedNetworkAccess = false - - return request - } -} diff --git a/Extensions/NSMutableAttributedString+Extensions.swift b/Extensions/NSMutableAttributedString+Extensions.swift index bac7e8f..3fd057b 100644 --- a/Extensions/NSMutableAttributedString+Extensions.swift +++ b/Extensions/NSMutableAttributedString+Extensions.swift @@ -1,6 +1,5 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import Mastodon import UIKit import ViewModels @@ -12,17 +11,15 @@ extension NSMutableAttributedString { while let tokenRange = string.range(of: token) { let attachment = AnimatedTextAttachment() - let url: URL if !identityContext.appPreferences.shouldReduceMotion, identityContext.appPreferences.animateCustomEmojis { - url = emoji.url + attachment.imageURL = emoji.url } else { - url = emoji.staticUrl + attachment.imageURL = emoji.staticUrl } attachment.accessibilityLabel = emoji.shortcode - attachment.kf.setImage(with: url, attributedView: view) replaceCharacters(in: NSRange(tokenRange, in: string), with: NSAttributedString(attachment: attachment)) } } diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index b0225e9..f7d3b14 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -35,16 +35,15 @@ D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07EC7E225B13DD3006DF726 /* EmojiContentConfiguration.swift */; }; D021A69525C3E4C1008A0C0D /* EmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07EC7F125B13E57006DF726 /* EmojiView.swift */; }; D021A6A625C3E584008A0C0D /* EditAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936E825AA3F3D00754FDF /* EditAttachmentView.swift */; }; - D025B14625C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */; }; - D025B14725C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */; }; D025B14D25C4E482001C69A8 /* ImageCacheConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */; }; D025B14E25C4E482001C69A8 /* ImageCacheConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */; }; D025B15B25C4EA7D001C69A8 /* ImageCacheConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */; }; - D025B16025C4EA81001C69A8 /* ImageCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */; }; D025B16A25C4EB18001C69A8 /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D025B16925C4EB18001C69A8 /* ServiceLayer */; }; - D025B17025C4EB58001C69A8 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D025B16F25C4EB58001C69A8 /* Kingfisher */; }; D025B17E25C500BC001C69A8 /* CapsuleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B17D25C500BC001C69A8 /* CapsuleButton.swift */; }; D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.swift */; }; + D035D8F925E4338D00E597C9 /* ImageDiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035D8F825E4338D00E597C9 /* ImageDiskCache.swift */; }; + D035D8FE25E4339800E597C9 /* ImageDiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035D8F825E4338D00E597C9 /* ImageDiskCache.swift */; }; + D035D90325E4388800E597C9 /* ImageDiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035D8F825E4338D00E597C9 /* ImageDiskCache.swift */; }; D035F86925B7F2ED00DC75ED /* MainNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */; }; D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */; }; D035F87D25B7F61600DC75ED /* TimelinesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */; }; @@ -60,9 +59,11 @@ D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */; }; D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7AD3825870B13005F5E2D /* UIVIewController+Extensions.swift */; }; D03D87F425C23C44004DCBB2 /* SecondaryNavigationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03D87F325C23C44004DCBB2 /* SecondaryNavigationTitleView.swift */; }; - D0477F1525C68BAC005C5368 /* PrefetchRequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F1425C68BAC005C5368 /* PrefetchRequestModifier.swift */; }; D0477F2C25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F2B25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift */; }; D0477F4625C72E50005C5368 /* CapsuleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F4525C72E50005C5368 /* CapsuleLabel.swift */; }; + D04F34B625E42ABE00714251 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = D04F34B525E42ABE00714251 /* SDWebImage */; }; + D04F34BC25E42ADC00714251 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = D04F34BB25E42ADC00714251 /* SDWebImage */; }; + D04F34C225E42AE500714251 /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = D04F34C125E42AE500714251 /* SDWebImage */; }; D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D04F9E8D259E9C950081B0C9 /* ViewModels */; }; D05936CF25A8D79800754FDF /* EditAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936CE25A8D79800754FDF /* EditAttachmentViewController.swift */; }; D05936D025A8D79800754FDF /* EditAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936CE25A8D79800754FDF /* EditAttachmentViewController.swift */; }; @@ -206,8 +207,6 @@ D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B12D251A97E400942152 /* TableViewController.swift */; }; D0F0B136251AA12700942152 /* CollectionItem+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */; }; D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F4362C25C10B9600E4F896 /* AddIdentityViewController.swift */; }; - D0F5880525A7E4C500E3A49C /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D0F5880425A7E4C500E3A49C /* Kingfisher */; }; - D0F5880F25A7E6CC00E3A49C /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D0F5880E25A7E6CC00E3A49C /* Kingfisher */; }; D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; }; D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; }; D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; }; @@ -273,10 +272,10 @@ D021A61925C36C1A008A0C0D /* IdentityContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityContentConfiguration.swift; sourceTree = ""; }; D021A62B25C38570008A0C0D /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; D021A63525C38ADB008A0C0D /* AcknowledgmentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgmentsView.swift; sourceTree = ""; }; - D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCacheSerializer.swift; sourceTree = ""; }; D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheConfiguration.swift; sourceTree = ""; }; D025B17D25C500BC001C69A8 /* CapsuleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleButton.swift; sourceTree = ""; }; D02E1F94250B13210071AD56 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; + D035D8F825E4338D00E597C9 /* ImageDiskCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDiskCache.swift; sourceTree = ""; }; D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationViewController.swift; sourceTree = ""; }; D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationView.swift; sourceTree = ""; }; D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesViewController.swift; sourceTree = ""; }; @@ -289,7 +288,6 @@ D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentConfiguration.swift; sourceTree = ""; }; D036AA16254CA823009094DF /* StatusBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBodyView.swift; sourceTree = ""; }; D03D87F325C23C44004DCBB2 /* SecondaryNavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationTitleView.swift; sourceTree = ""; }; - D0477F1425C68BAC005C5368 /* PrefetchRequestModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefetchRequestModifier.swift; sourceTree = ""; }; D0477F2B25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInDefaultBrowserActivity.swift; sourceTree = ""; }; D0477F4525C72E50005C5368 /* CapsuleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleLabel.swift; sourceTree = ""; }; D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -434,8 +432,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D0F5880525A7E4C500E3A49C /* Kingfisher in Frameworks */, D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */, + D04F34B625E42ABE00714251 /* SDWebImage in Frameworks */, D0FE7C8025C4C79F00203957 /* PreviewViewModels in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -451,7 +449,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D0F5880F25A7E6CC00E3A49C /* Kingfisher in Frameworks */, + D04F34BC25E42ADC00714251 /* SDWebImage in Frameworks */, D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -460,7 +458,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D025B17025C4EB58001C69A8 /* Kingfisher in Frameworks */, + D04F34C225E42AE500714251 /* SDWebImage in Frameworks */, D025B16A25C4EB18001C69A8 /* ServiceLayer in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -823,9 +821,8 @@ isa = PBXGroup; children = ( D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */, - D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */, D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */, - D0477F1425C68BAC005C5368 /* PrefetchRequestModifier.swift */, + D035D8F825E4338D00E597C9 /* ImageDiskCache.swift */, ); path = Caches; sourceTree = ""; @@ -852,8 +849,8 @@ name = Metatext; packageProductDependencies = ( D0E2C1D024FD97F000854680 /* ViewModels */, - D0F5880425A7E4C500E3A49C /* Kingfisher */, D0FE7C7F25C4C79F00203957 /* PreviewViewModels */, + D04F34B525E42ABE00714251 /* SDWebImage */, ); productName = "Metatext (iOS)"; productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */; @@ -894,7 +891,7 @@ name = "Share Extension"; packageProductDependencies = ( D04F9E8D259E9C950081B0C9 /* ViewModels */, - D0F5880E25A7E6CC00E3A49C /* Kingfisher */, + D04F34BB25E42ADC00714251 /* SDWebImage */, ); productName = "Share Extension"; productReference = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; @@ -915,7 +912,7 @@ name = "Notification Service Extension"; packageProductDependencies = ( D025B16925C4EB18001C69A8 /* ServiceLayer */, - D025B16F25C4EB58001C69A8 /* Kingfisher */, + D04F34C125E42AE500714251 /* SDWebImage */, ); productName = "Notification Service Extension"; productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; @@ -957,7 +954,7 @@ ); mainGroup = D047FA7F24C3E21000AF17C5; packageReferences = ( - D0F5880325A7E4C500E3A49C /* XCRemoteSwiftPackageReference "Kingfisher" */, + D04F34B425E42ABE00714251 /* XCRemoteSwiftPackageReference "SDWebImage" */, ); productRefGroup = D047FA8D24C3E21200AF17C5 /* Products */; projectDirPath = ""; @@ -1060,7 +1057,6 @@ D0477F2C25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift in Sources */, D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */, D07F4D9825D493E300F61133 /* MuteView.swift in Sources */, - D0477F1525C68BAC005C5368 /* PrefetchRequestModifier.swift in Sources */, D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */, D0CEC10125E337C900FEF5A6 /* AnimatedTextAttachment.swift in Sources */, D035F8B325B9616000DC75ED /* Timeline+Extensions.swift in Sources */, @@ -1080,6 +1076,7 @@ D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */, D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */, D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */, + D035D8F925E4338D00E597C9 /* ImageDiskCache.swift in Sources */, D0625E59250F092900502611 /* StatusTableViewCell.swift in Sources */, D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */, D05936FF25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift in Sources */, @@ -1136,7 +1133,6 @@ D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */, D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */, D035F86925B7F2ED00DC75ED /* MainNavigationViewController.swift in Sources */, - D025B14625C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */, D0B8510C25259E56004E0744 /* LoadMoreTableViewCell.swift in Sources */, D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */, D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */, @@ -1226,8 +1222,8 @@ D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */, D0E39B8725D9B7FD009C10F8 /* AutocompleteDataSource.swift in Sources */, D00CB23325C92F2D008EF267 /* Attachment+Extensions.swift in Sources */, + D035D8FE25E4339800E597C9 /* ImageDiskCache.swift in Sources */, D0D93ECA25D9C76500C622ED /* AutocompleteItemView.swift in Sources */, - D025B14725C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */, D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */, D021A6A625C3E584008A0C0D /* EditAttachmentView.swift in Sources */, D0D93ED925D9CBE200C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */, @@ -1241,9 +1237,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D035D90325E4388800E597C9 /* ImageDiskCache.swift in Sources */, D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */, D025B15B25C4EA7D001C69A8 /* ImageCacheConfiguration.swift in Sources */, - D025B16025C4EA81001C69A8 /* ImageCacheSerializer.swift in Sources */, D059376125ABE2E800754FDF /* XMLUnescaper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1642,12 +1638,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - D0F5880325A7E4C500E3A49C /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + D04F34B425E42ABE00714251 /* XCRemoteSwiftPackageReference "SDWebImage" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher"; + repositoryURL = "https://github.com/SDWebImage/SDWebImage"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 6.1.0; + minimumVersion = 5.10.4; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -1657,10 +1653,20 @@ isa = XCSwiftPackageProductDependency; productName = ServiceLayer; }; - D025B16F25C4EB58001C69A8 /* Kingfisher */ = { + D04F34B525E42ABE00714251 /* SDWebImage */ = { isa = XCSwiftPackageProductDependency; - package = D0F5880325A7E4C500E3A49C /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; + package = D04F34B425E42ABE00714251 /* XCRemoteSwiftPackageReference "SDWebImage" */; + productName = SDWebImage; + }; + D04F34BB25E42ADC00714251 /* SDWebImage */ = { + isa = XCSwiftPackageProductDependency; + package = D04F34B425E42ABE00714251 /* XCRemoteSwiftPackageReference "SDWebImage" */; + productName = SDWebImage; + }; + D04F34C125E42AE500714251 /* SDWebImage */ = { + isa = XCSwiftPackageProductDependency; + package = D04F34B425E42ABE00714251 /* XCRemoteSwiftPackageReference "SDWebImage" */; + productName = SDWebImage; }; D04F9E8D259E9C950081B0C9 /* ViewModels */ = { isa = XCSwiftPackageProductDependency; @@ -1670,16 +1676,6 @@ isa = XCSwiftPackageProductDependency; productName = ViewModels; }; - D0F5880425A7E4C500E3A49C /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = D0F5880325A7E4C500E3A49C /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; - D0F5880E25A7E6CC00E3A49C /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = D0F5880325A7E4C500E3A49C /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; D0FE7C7F25C4C79F00203957 /* PreviewViewModels */ = { isa = XCSwiftPackageProductDependency; productName = PreviewViewModels; diff --git a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b332169..a6648d8 100644 --- a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -20,12 +20,12 @@ } }, { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher", + "package": "SDWebImage", + "repositoryURL": "https://github.com/SDWebImage/SDWebImage", "state": { "branch": null, - "revision": "daebf8ddf974164d1b9a050c8231e263f3106b09", - "version": "6.1.0" + "revision": "a6b6e44eadf0d39250c10a7cc0e3b91d0bdb0e94", + "version": "5.10.4" } } ] diff --git a/Notification Service Extension/NotificationService.swift b/Notification Service Extension/NotificationService.swift index cfc5008..00dc3fe 100644 --- a/Notification Service Extension/NotificationService.swift +++ b/Notification Service Extension/NotificationService.swift @@ -1,17 +1,15 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import Mastodon +import SDWebImage import ServiceLayer import UserNotifications final class NotificationService: UNNotificationServiceExtension { - private let environment = AppEnvironment.live( - userNotificationCenter: .current(), - reduceMotion: { false }) - override init() { super.init() + + try? ImageCacheConfiguration(environment: Self.environment).configure() } var contentHandler: ((UNNotificationContent) -> Void)? @@ -25,7 +23,7 @@ final class NotificationService: UNNotificationServiceExtension { guard let bestAttemptContent = bestAttemptContent else { return } - let parsingService = PushNotificationParsingService(environment: environment) + let parsingService = PushNotificationParsingService(environment: Self.environment) let decryptedJSON: Data let identityId: Identity.Id let pushNotification: PushNotification @@ -43,14 +41,14 @@ final class NotificationService: UNNotificationServiceExtension { bestAttemptContent.title = pushNotification.title bestAttemptContent.body = XMLUnescaper(string: pushNotification.body).unescape() - let appPreferences = AppPreferences(environment: environment) + let appPreferences = AppPreferences(environment: Self.environment) if appPreferences.notificationSounds.contains(pushNotification.notificationType) { bestAttemptContent.sound = .default } if appPreferences.notificationAccountName, - let accountName = try? AllIdentitiesService(environment: environment).identity(id: identityId)?.handle { + let accountName = try? AllIdentitiesService(environment: Self.environment).identity(id: identityId)?.handle { bestAttemptContent.subtitle = accountName } @@ -71,6 +69,10 @@ final class NotificationService: UNNotificationServiceExtension { } private extension NotificationService { + private static let environment = AppEnvironment.live( + userNotificationCenter: .current(), + reduceMotion: { false }) + static func addImage(url: URL, bestAttemptContent: UNMutableNotificationContent, contentHandler: @escaping (UNNotificationContent) -> Void) { @@ -78,31 +80,17 @@ private extension NotificationService { let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) .appendingPathComponent(fileName) - KingfisherManager.shared.retrieveImage(with: url) { - switch $0 { - case let .success(result): - let format: ImageFormat - - switch fileURL.pathExtension.lowercased() { - case "jpg", "jpeg": - format = .JPEG - case "gif": - format = .GIF - case "png": - format = .PNG - default: - format = .unknown - } - + SDWebImageManager.shared.loadImage(with: url, options: [], progress: nil) { _, data, _, _, _, _ in + if let data = data { do { - try result.image.kf.data(format: format)?.write(to: fileURL) + try data.write(to: fileURL) bestAttemptContent.attachments = [try UNNotificationAttachment(identifier: fileName, url: fileURL)] contentHandler(bestAttemptContent) } catch { contentHandler(bestAttemptContent) } - case .failure: + } else { contentHandler(bestAttemptContent) } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/ImageSerializationService.swift b/ServiceLayer/Sources/ServiceLayer/Services/ImageSerializationService.swift index b1de102..bb8fe35 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/ImageSerializationService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/ImageSerializationService.swift @@ -1,5 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. +import Base16 import CryptoKit import Foundation import Secrets @@ -20,4 +21,8 @@ public extension ImageSerializationService { func deserialize(data: Data) throws -> Data { try ChaChaPoly.open(.init(combined: data), using: key) } + + func cacheKey(forKey key: String) -> String { + Data(SHA256.hash(data: Data(key.utf8))).base16EncodedString() + } } diff --git a/System/MetatextApp.swift b/System/MetatextApp.swift index dd09cc3..d6288f9 100644 --- a/System/MetatextApp.swift +++ b/System/MetatextApp.swift @@ -1,7 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. import AVKit -import Kingfisher import ServiceLayer import SwiftUI import ViewModels @@ -30,9 +29,4 @@ private extension MetatextApp { static let environment = AppEnvironment.live( userNotificationCenter: .current(), reduceMotion: { UIAccessibility.isReduceMotionEnabled }) - static let imageCacheName = "Images" - static let imageCacheDirectoryURL = FileManager.default.containerURL( - forSecurityApplicationGroupIdentifier: AppEnvironment.appGroup)? - .appendingPathComponent("Library") - .appendingPathComponent("Caches") } diff --git a/View Controllers/AddIdentityViewController.swift b/View Controllers/AddIdentityViewController.swift index 420dbe5..0d8b0ff 100644 --- a/View Controllers/AddIdentityViewController.swift +++ b/View Controllers/AddIdentityViewController.swift @@ -1,9 +1,9 @@ // Copyright © 2021 Metabolist. All rights reserved. import Combine -import Kingfisher import Mastodon import SafariServices +import SDWebImage import SwiftUI import ViewModels @@ -18,7 +18,7 @@ final class AddIdentityViewController: UIViewController { private let welcomeLabel = UILabel() private let instanceAndButtonsStackView = UIStackView() private let instanceStackView = UIStackView() - private let instanceImageView = AnimatedImageView() + private let instanceImageView = SDAnimatedImageView() private let instanceTitleLabel = UILabel() private let instanceURLLabel = UILabel() private let buttonsStackView = UIStackView() @@ -110,7 +110,7 @@ private extension AddIdentityViewController { instanceImageView.contentMode = .scaleAspectFill instanceImageView.layer.cornerRadius = .defaultCornerRadius instanceImageView.clipsToBounds = true - instanceImageView.kf.indicatorType = .activity + instanceImageView.sd_imageIndicator = SDWebImageActivityIndicator.large buttonsStackView.axis = .vertical buttonsStackView.spacing = .defaultSpacing @@ -259,7 +259,7 @@ private extension AddIdentityViewController { if let instance = instance { self.instanceTitleLabel.text = instance.title self.instanceURLLabel.text = instance.uri - self.instanceImageView.kf.setImage(with: instance.thumbnail) + self.instanceImageView.sd_setImage(with: instance.thumbnail) self.instanceStackView.isHidden_stackViewSafe = false if instance.registrations { diff --git a/View Controllers/ImageViewController.swift b/View Controllers/ImageViewController.swift index f53db52..168aa47 100644 --- a/View Controllers/ImageViewController.swift +++ b/View Controllers/ImageViewController.swift @@ -1,14 +1,14 @@ // Copyright © 2020 Metabolist. All rights reserved. import AVFoundation -import Kingfisher import Mastodon +import SDWebImage import UIKit import ViewModels final class ImageViewController: UIViewController { let scrollView = UIScrollView() - let imageView = AnimatedImageView() + let imageView = SDAnimatedImageView() let playerView = PlayerView() private let viewModel: AttachmentViewModel? @@ -55,7 +55,8 @@ final class ImageViewController: UIViewController { contentView.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit - imageView.kf.indicatorType = .activity + imageView.sd_imageIndicator = SDWebImageActivityIndicator.large + imageView.autoPlayAnimatedImage = false contentView.addSubview(playerView) playerView.translatesAutoresizingMaskIntoConstraints = false @@ -109,20 +110,11 @@ final class ImageViewController: UIViewController { case .image: imageView.tag = viewModel.tag playerView.isHidden = true - imageView.kf.setImage( - with: viewModel.attachment.previewUrl, - options: [.onlyFromCache], - completionHandler: { [weak self] in - guard let self = self else { return } - if case .success = $0 { - self.imageView.kf.indicatorType = .none - } + let placeholderKey = viewModel.attachment.previewUrl?.absoluteString + let placeholderImage = SDImageCache.shared.imageFromCache(forKey: placeholderKey) - self.imageView.kf.setImage( - with: viewModel.attachment.url, - options: [.keepCurrentImageWhileLoading]) - }) + imageView.sd_setImage(with: viewModel.attachment.url, placeholderImage: placeholderImage) case .gifv: playerView.tag = viewModel.tag imageView.isHidden = true @@ -143,7 +135,7 @@ final class ImageViewController: UIViewController { } else if let imageURL = imageURL { imageView.tag = imageURL.hashValue playerView.isHidden = true - imageView.kf.setImage(with: imageURL) + imageView.sd_setImage(with: imageURL) } contentView.accessibilityLabel = viewModel?.attachment.type.accessibilityName diff --git a/View Controllers/NewStatusViewController.swift b/View Controllers/NewStatusViewController.swift index fba5ef7..786b1b0 100644 --- a/View Controllers/NewStatusViewController.swift +++ b/View Controllers/NewStatusViewController.swift @@ -2,7 +2,6 @@ import AVFoundation import Combine -import Kingfisher import PhotosUI import SwiftUI import UniformTypeIdentifiers diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index f615430..45da44c 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -2,9 +2,9 @@ import AVKit import Combine -import Kingfisher import Mastodon import SafariServices +import SDWebImage import SwiftUI import ViewModels @@ -280,13 +280,8 @@ extension TableViewController: UITableViewDataSourcePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let urls = indexPaths.compactMap(dataSource.itemIdentifier(for:)) .reduce(Set()) { $0.union($1.mediaPrefetchURLs(identityContext: viewModel.identityContext)) } - var imageOptions = KingfisherManager.shared.defaultOptions - imageOptions.append(.requestModifier(PrefetchRequestModifier())) - - for url in urls { - KingfisherManager.shared.retrieveImage(with: url, completionHandler: nil) - } + SDWebImagePrefetcher.shared.prefetchURLs(Array(urls)) } } diff --git a/Views/SwiftUI/SecondaryNavigationView.swift b/Views/SwiftUI/SecondaryNavigationView.swift index 2299e65..c1762f5 100644 --- a/Views/SwiftUI/SecondaryNavigationView.swift +++ b/Views/SwiftUI/SecondaryNavigationView.swift @@ -1,6 +1,5 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import SwiftUI import ViewModels diff --git a/Views/SwiftUI/View Controller Representables/AddIdentityView.swift b/Views/SwiftUI/View Controller Representables/AddIdentityView.swift index 775ba41..b016bfa 100644 --- a/Views/SwiftUI/View Controller Representables/AddIdentityView.swift +++ b/Views/SwiftUI/View Controller Representables/AddIdentityView.swift @@ -1,6 +1,5 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import SwiftUI import ViewModels diff --git a/Views/SwiftUI/View Controller Representables/IdentitiesView.swift b/Views/SwiftUI/View Controller Representables/IdentitiesView.swift index 257c669..4dd29c8 100644 --- a/Views/SwiftUI/View Controller Representables/IdentitiesView.swift +++ b/Views/SwiftUI/View Controller Representables/IdentitiesView.swift @@ -1,6 +1,5 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import SwiftUI import ViewModels diff --git a/Views/UIKit/AccountHeaderView.swift b/Views/UIKit/AccountHeaderView.swift index 40e3897..b7da474 100644 --- a/Views/UIKit/AccountHeaderView.swift +++ b/Views/UIKit/AccountHeaderView.swift @@ -1,16 +1,16 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit import ViewModels // swiftlint:disable file_length final class AccountHeaderView: UIView { let headerImageBackgroundView = UIView() - let headerImageView = AnimatedImageView() + let headerImageView = SDAnimatedImageView() let headerButton = UIButton() let avatarBackgroundView = UIView() - let avatarImageView = AnimatedImageView() + let avatarImageView = SDAnimatedImageView() let avatarButton = UIButton() let relationshipButtonsStackView = UIStackView() let followButton = UIButton(type: .system) @@ -37,8 +37,8 @@ final class AccountHeaderView: UIView { var viewModel: ProfileViewModel { didSet { if let accountViewModel = viewModel.accountViewModel { - headerImageView.kf.setImage(with: accountViewModel.headerURL) { [weak self] in - if case let .success(result) = $0, result.image.size != Self.missingHeaderImageSize { + headerImageView.sd_setImage(with: accountViewModel.headerURL) { [weak self] image, _, _, _ in + if let image = image, image.size != Self.missingHeaderImageSize { self?.headerButton.isEnabled = true } } @@ -46,7 +46,7 @@ final class AccountHeaderView: UIView { headerButton.accessibilityLabel = String.localizedStringWithFormat( NSLocalizedString("account.header.accessibility-label-%@", comment: ""), accountViewModel.displayName) - avatarImageView.kf.setImage(with: accountViewModel.avatarURL(profile: true)) + avatarImageView.sd_setImage(with: accountViewModel.avatarURL(profile: true)) avatarImageView.tag = accountViewModel.avatarURL(profile: true).hashValue avatarButton.accessibilityLabel = String.localizedStringWithFormat( NSLocalizedString("account.avatar.accessibility-label-%@", comment: ""), diff --git a/Views/UIKit/AnimatedAttachmentLabel.swift b/Views/UIKit/AnimatedAttachmentLabel.swift index 2d89561..70d9dcd 100644 --- a/Views/UIKit/AnimatedAttachmentLabel.swift +++ b/Views/UIKit/AnimatedAttachmentLabel.swift @@ -1,6 +1,6 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { @@ -18,7 +18,7 @@ final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { guard let attributedText = attributedText else { return } - var attachmentImageViews = Set() + var attachmentImageViews = Set() attributedText.enumerateAttribute( .attachment, @@ -29,7 +29,7 @@ final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { } for subview in subviews { - guard let attachmentImageView = subview as? AnimatedImageView else { continue } + guard let attachmentImageView = subview as? SDAnimatedImageView else { continue } if !attachmentImageViews.contains(attachmentImageView) { attachmentImageView.removeFromSuperview() @@ -49,6 +49,7 @@ final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { animatedAttachment.imageView.image = animatedAttachment.image animatedAttachment.imageView.contentMode = .scaleAspectFit animatedAttachment.imageView.center.y = center.y + animatedAttachment.imageView.sd_setImage(with: animatedAttachment.imageURL) if animatedAttachment.imageView.superview != self { addSubview(animatedAttachment.imageView) diff --git a/Views/UIKit/AnimatedTextAttachment.swift b/Views/UIKit/AnimatedTextAttachment.swift index 7ed5a1a..78eb56a 100644 --- a/Views/UIKit/AnimatedTextAttachment.swift +++ b/Views/UIKit/AnimatedTextAttachment.swift @@ -1,10 +1,11 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class AnimatedTextAttachment: NSTextAttachment { - var imageView = AnimatedImageView() + var imageURL: URL? + var imageView = SDAnimatedImageView() var imageBounds: CGRect? override func image(forBounds imageBounds: CGRect, diff --git a/Views/UIKit/AnimatingLayoutManager.swift b/Views/UIKit/AnimatingLayoutManager.swift index 356d7cc..866044e 100644 --- a/Views/UIKit/AnimatingLayoutManager.swift +++ b/Views/UIKit/AnimatingLayoutManager.swift @@ -1,6 +1,6 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class AnimatingLayoutManager: NSLayoutManager { @@ -13,7 +13,7 @@ final class AnimatingLayoutManager: NSLayoutManager { return } - var attachmentImageViews = Set() + var attachmentImageViews = Set() textStorage.enumerateAttribute( .attachment, @@ -24,7 +24,7 @@ final class AnimatingLayoutManager: NSLayoutManager { } for subview in view?.subviews ?? [] { - guard let attachmentImageView = subview as? AnimatedImageView else { continue } + guard let attachmentImageView = subview as? SDAnimatedImageView else { continue } if !attachmentImageViews.contains(attachmentImageView) { attachmentImageView.removeFromSuperview() @@ -42,6 +42,7 @@ final class AnimatingLayoutManager: NSLayoutManager { animatedAttachment.imageView.frame = boundingRect(forGlyphRange: range, in: textContainer) animatedAttachment.imageView.image = animatedAttachment.image animatedAttachment.imageView.contentMode = .scaleAspectFit + animatedAttachment.imageView.sd_setImage(with: animatedAttachment.imageURL) if animatedAttachment.imageView.superview != view { view?.addSubview(animatedAttachment.imageView) diff --git a/Views/UIKit/AttachmentView.swift b/Views/UIKit/AttachmentView.swift index 1936489..c805819 100644 --- a/Views/UIKit/AttachmentView.swift +++ b/Views/UIKit/AttachmentView.swift @@ -2,13 +2,13 @@ import AVKit import Combine -import Kingfisher +import SDWebImage import UIKit import ViewModels final class AttachmentView: UIView { let playerView = PlayerView() - let imageView = AnimatedImageView() + let imageView = SDAnimatedImageView() let removeButton = UIButton(type: .close) let editIcon = UIImageView() let selectionButton = UIButton() @@ -105,6 +105,7 @@ private extension AttachmentView { imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true + imageView.autoPlayAnimatedImage = false imageView.tag = viewModel.tag let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial) @@ -168,11 +169,9 @@ private extension AttachmentView { switch viewModel.attachment.type { case .image, .video, .gifv: - imageView.kf.setImage( - with: viewModel.attachment.previewUrl, - completionHandler: { [weak self] _ in - self?.layoutSubviews() - }) + imageView.sd_setImage(with: viewModel.attachment.previewUrl) { [weak self] _, _, _, _ in + self?.layoutSubviews() + } case .audio: playImageView.image = UIImage(systemName: "waveform.circle", withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle)) diff --git a/Views/UIKit/CardView.swift b/Views/UIKit/CardView.swift index 3eca6d7..c6075f7 100644 --- a/Views/UIKit/CardView.swift +++ b/Views/UIKit/CardView.swift @@ -1,7 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import Mastodon +import SDWebImage import UIKit import ViewModels @@ -20,7 +20,7 @@ final class CardView: UIView { .appendingWithSeparator(viewModel.title) imageView.isHidden = viewModel.imageURL == nil - imageView.kf.setImage(with: viewModel.imageURL) + imageView.sd_setImage(with: viewModel.imageURL) titleLabel.text = viewModel.title descriptionLabel.text = viewModel.description diff --git a/Views/UIKit/CompositionView.swift b/Views/UIKit/CompositionView.swift index c567b4c..abe9e03 100644 --- a/Views/UIKit/CompositionView.swift +++ b/Views/UIKit/CompositionView.swift @@ -1,12 +1,12 @@ // Copyright © 2020 Metabolist. All rights reserved. import Combine -import Kingfisher +import SDWebImage import UIKit import ViewModels final class CompositionView: UIView { - let avatarImageView = AnimatedImageView() + let avatarImageView = SDAnimatedImageView() let changeIdentityButton = UIButton() let spoilerTextField = UITextField() let textView = ImagePastableTextView() @@ -199,7 +199,7 @@ private extension CompositionView { ? $0.identity.account?.avatar : $0.identity.account?.avatarStatic - self.avatarImageView.kf.setImage(with: avatarURL) + self.avatarImageView.sd_setImage(with: avatarURL) self.changeIdentityButton.accessibilityLabel = $0.identity.handle self.changeIdentityButton.accessibilityHint = NSLocalizedString("compose.change-identity-button.accessibility-hint", comment: "") @@ -290,10 +290,11 @@ private extension CompositionView { } func changeIdentityMenu(identities: [Identity]) -> UIMenu { - let processor = RoundCornerImageProcessor(radius: .widthFraction(0.5)) - var imageOptions = KingfisherManager.shared.defaultOptions - - imageOptions.append(.processor(processor)) + let imageTransformer = SDImageRoundCornerTransformer( + radius: .greatestFiniteMagnitude, + corners: .allCorners, + borderWidth: 0, + borderColor: nil) return UIMenu(children: identities.map { identity in UIDeferredMenuElement { completion in @@ -302,10 +303,12 @@ private extension CompositionView { } if let image = identity.image { - KingfisherManager.shared.retrieveImage(with: image, options: imageOptions) { - if case let .success(value) = $0 { - action.image = value.image - } + SDWebImageManager.shared.loadImage( + with: image, + options: [.transformAnimatedImage], + context: [.imageTransformer: imageTransformer], + progress: nil) { (image, _, _, _, _, _) in + action.image = image completion([action]) } diff --git a/Views/UIKit/Content Configurations/EmojiView.swift b/Views/UIKit/Content Configurations/EmojiView.swift index 0c070e1..6a1eb8a 100644 --- a/Views/UIKit/Content Configurations/EmojiView.swift +++ b/Views/UIKit/Content Configurations/EmojiView.swift @@ -1,10 +1,10 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class EmojiView: UIView { - private let imageView = AnimatedImageView() + private let imageView = SDAnimatedImageView() private let emojiLabel = UILabel() private var emojiConfiguration: EmojiContentConfiguration @@ -74,7 +74,8 @@ private extension EmojiView { imageView.isHidden = false emojiLabel.isHidden = true - imageView.kf.setImage(with: emoji.url) + // TODO: Use static URL if emoji animation preference is false + imageView.sd_setImage(with: emoji.url) accessibilityLabel = emoji.shortcode } else { imageView.isHidden = true diff --git a/Views/UIKit/Content Views/AccountView.swift b/Views/UIKit/Content Views/AccountView.swift index 23eb94f..59af735 100644 --- a/Views/UIKit/Content Views/AccountView.swift +++ b/Views/UIKit/Content Views/AccountView.swift @@ -1,12 +1,12 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import Mastodon +import SDWebImage import UIKit import ViewModels final class AccountView: UIView { - let avatarImageView = AnimatedImageView() + let avatarImageView = SDAnimatedImageView() let displayNameLabel = AnimatedAttachmentLabel() let accountLabel = UILabel() let noteTextView = TouchFallthroughTextView() @@ -199,7 +199,7 @@ private extension AccountView { func applyAccountConfiguration() { let viewModel = accountConfiguration.viewModel - avatarImageView.kf.setImage(with: viewModel.avatarURL(profile: false)) + avatarImageView.sd_setImage(with: viewModel.avatarURL(profile: false)) let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName) diff --git a/Views/UIKit/Content Views/AutocompleteItemView.swift b/Views/UIKit/Content Views/AutocompleteItemView.swift index faa951e..8f39ec9 100644 --- a/Views/UIKit/Content Views/AutocompleteItemView.swift +++ b/Views/UIKit/Content Views/AutocompleteItemView.swift @@ -1,10 +1,10 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class AutocompleteItemView: UIView { - private let imageView = AnimatedImageView() + private let imageView = SDAnimatedImageView() private let primaryLabel = AnimatedAttachmentLabel() private let secondaryLabel = UILabel() private let stackView = UIStackView() @@ -78,7 +78,7 @@ private extension AutocompleteItemView { ? account.avatar : account.avatarStatic - imageView.kf.setImage(with: avatarURL) + imageView.sd_setImage(with: avatarURL) imageView.isHidden = false let mutableDisplayName = NSMutableAttributedString(string: account.displayName) diff --git a/Views/UIKit/Content Views/IdentityView.swift b/Views/UIKit/Content Views/IdentityView.swift index 068293b..408f7a1 100644 --- a/Views/UIKit/Content Views/IdentityView.swift +++ b/Views/UIKit/Content Views/IdentityView.swift @@ -1,10 +1,10 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class IdentityView: UIView { - let imageView = AnimatedImageView() + let imageView = SDAnimatedImageView() let nameLabel = AnimatedAttachmentLabel() let secondaryLabel = UILabel() @@ -84,7 +84,7 @@ private extension IdentityView { func applyIdentityConfiguration() { let viewModel = identityConfiguration.viewModel - imageView.kf.setImage(with: viewModel.identity.image) + imageView.sd_setImage(with: viewModel.identity.image) imageView.autoPlayAnimatedImage = viewModel.identityContext.appPreferences.animateAvatars == .everywhere if let displayName = viewModel.identity.account?.displayName, diff --git a/Views/UIKit/Content Views/InstanceView.swift b/Views/UIKit/Content Views/InstanceView.swift index 4d9bcd9..b8b527e 100644 --- a/Views/UIKit/Content Views/InstanceView.swift +++ b/Views/UIKit/Content Views/InstanceView.swift @@ -1,10 +1,10 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit final class InstanceView: UIView { - private let imageView = AnimatedImageView() + private let imageView = SDAnimatedImageView() private let titleLabel = UILabel() private let uriLabel = UILabel() private var instanceConfiguration: InstanceContentConfiguration @@ -77,7 +77,8 @@ private extension InstanceView { func applyInstanceConfiguration() { let viewModel = instanceConfiguration.viewModel - imageView.kf.setImage(with: viewModel.instance.thumbnail) + imageView.sd_setImage(with: viewModel.instance.thumbnail) + imageView.autoPlayAnimatedImage = !UIAccessibility.isReduceMotionEnabled titleLabel.text = viewModel.instance.title uriLabel.text = viewModel.instance.uri diff --git a/Views/UIKit/Content Views/NotificationView.swift b/Views/UIKit/Content Views/NotificationView.swift index b04456b..e494f58 100644 --- a/Views/UIKit/Content Views/NotificationView.swift +++ b/Views/UIKit/Content Views/NotificationView.swift @@ -1,13 +1,13 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher import Mastodon +import SDWebImage import UIKit import ViewModels final class NotificationView: UIView { private let iconImageView = UIImageView() - private let avatarImageView = AnimatedImageView() + private let avatarImageView = SDAnimatedImageView() private let avatarButton = UIButton() private let typeLabel = AnimatedAttachmentLabel() private let timeLabel = UILabel() @@ -166,7 +166,7 @@ private extension NotificationView { func applyNotificationConfiguration() { let viewModel = notificationConfiguration.viewModel - avatarImageView.kf.setImage(with: viewModel.accountViewModel.avatarURL()) + avatarImageView.sd_setImage(with: viewModel.accountViewModel.avatarURL()) switch viewModel.type { case .follow: diff --git a/Views/UIKit/Content Views/StatusView.swift b/Views/UIKit/Content Views/StatusView.swift index bf67e78..e769da8 100644 --- a/Views/UIKit/Content Views/StatusView.swift +++ b/Views/UIKit/Content Views/StatusView.swift @@ -2,13 +2,13 @@ // swiftlint:disable file_length import Combine -import Kingfisher import Mastodon +import SDWebImage import UIKit import ViewModels final class StatusView: UIView { - let avatarImageView = AnimatedImageView() + let avatarImageView = SDAnimatedImageView() let avatarButton = UIButton() let infoIcon = UIImageView() let infoLabel = AnimatedAttachmentLabel() @@ -417,7 +417,7 @@ private extension StatusView { menuButton.menu = menu(viewModel: viewModel) - avatarImageView.kf.setImage(with: viewModel.avatarURL) + avatarImageView.sd_setImage(with: viewModel.avatarURL) avatarButton.accessibilityLabel = String.localizedStringWithFormat( NSLocalizedString("account.avatar.accessibility-label-%@", comment: ""), viewModel.accountViewModel.displayName) diff --git a/Views/UIKit/ConversationAvatarsView.swift b/Views/UIKit/ConversationAvatarsView.swift index 21abc3f..6fe3406 100644 --- a/Views/UIKit/ConversationAvatarsView.swift +++ b/Views/UIKit/ConversationAvatarsView.swift @@ -1,6 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit import ViewModels @@ -23,11 +23,11 @@ final class ConversationAvatarsView: UIView { rightStackView.isHidden = accountCount == 1 for (index, accountViewModel) in accountViewModels.enumerated() { - let imageView = AnimatedImageView() + let imageView = SDAnimatedImageView() imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true - imageView.kf.setImage(with: accountViewModel.avatarURL()) + imageView.sd_setImage(with: accountViewModel.avatarURL()) if accountCount == 2 && index == 1 || accountCount == 3 && index != 0 diff --git a/Views/UIKit/EditThumbnailView.swift b/Views/UIKit/EditThumbnailView.swift index 5a230c6..8316497 100644 --- a/Views/UIKit/EditThumbnailView.swift +++ b/Views/UIKit/EditThumbnailView.swift @@ -1,14 +1,14 @@ // Copyright © 2021 Metabolist. All rights reserved. import Combine -import Kingfisher +import SDWebImage import UIKit import ViewModels final class EditThumbnailView: UIView { let playerView = PlayerView() - let imageView = UIImageView() - let previewImageView = UIImageView() + let imageView = SDAnimatedImageView() + let previewImageView = SDAnimatedImageView() let promptBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial)) let thumbnailPromptLabel = UILabel() @@ -94,7 +94,7 @@ private extension EditThumbnailView { addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit - imageView.kf.indicatorType = .activity + imageView.sd_imageIndicator = SDWebImageActivityIndicator.large addSubview(playerView) playerView.translatesAutoresizingMaskIntoConstraints = false @@ -137,25 +137,16 @@ private extension EditThumbnailView { previewImageView.contentMode = .scaleAspectFill previewImageView.clipsToBounds = true previewImageView.layer.cornerRadius = .defaultCornerRadius - previewImageView.kf.setImage(with: viewModel.attachment.previewUrl) + previewImageView.sd_setImage(with: viewModel.attachment.previewUrl) switch viewModel.attachment.type { case .image: playerView.isHidden = true - imageView.kf.setImage( - with: viewModel.attachment.previewUrl, - options: [.onlyFromCache], - completionHandler: { [weak self] in - guard let self = self else { return } - if case .success = $0 { - self.imageView.kf.indicatorType = .none - } + let placeholderKey = viewModel.attachment.previewUrl?.absoluteString + let placeholderImage = SDImageCache.shared.imageFromCache(forKey: placeholderKey) - self.imageView.kf.setImage( - with: self.viewModel.attachment.url, - options: [.keepCurrentImageWhileLoading]) - }) + imageView.sd_setImage(with: viewModel.attachment.url, placeholderImage: placeholderImage) case .gifv: imageView.isHidden = true let player = PlayerCache.shared.player(url: viewModel.attachment.url) diff --git a/Views/UIKit/SecondaryNavigationButton.swift b/Views/UIKit/SecondaryNavigationButton.swift index 5067a32..66f8d3d 100644 --- a/Views/UIKit/SecondaryNavigationButton.swift +++ b/Views/UIKit/SecondaryNavigationButton.swift @@ -1,7 +1,7 @@ // Copyright © 2021 Metabolist. All rights reserved. import Combine -import Kingfisher +import SDWebImage import UIKit import ViewModels @@ -28,17 +28,18 @@ final class SecondaryNavigationButton: UIBarButtonItem { ]) viewModel.identityContext.$identity.sink { - button.kf.setImage( + button.sd_setImage( with: $0.image, for: .normal, - placeholder: UIImage(systemName: "line.horizontal.3")) + placeholderImage: UIImage(systemName: "line.horizontal.3")) } .store(in: &cancellables) - let processor = RoundCornerImageProcessor(radius: .widthFraction(0.5)) - var imageOptions = KingfisherManager.shared.defaultOptions - - imageOptions.append(.processor(processor)) + let imageTransformer = SDImageRoundCornerTransformer( + radius: .greatestFiniteMagnitude, + corners: .allCorners, + borderWidth: 0, + borderColor: nil) viewModel.$recentIdentities.sink { identities in button.menu = UIMenu(children: identities.map { identity in @@ -48,10 +49,12 @@ final class SecondaryNavigationButton: UIBarButtonItem { } if let image = identity.image { - KingfisherManager.shared.retrieveImage(with: image, options: imageOptions) { - if case let .success(value) = $0 { - action.image = value.image - } + SDWebImageManager.shared.loadImage( + with: image, + options: [.transformAnimatedImage], + context: [.imageTransformer: imageTransformer], + progress: nil) { (image, _, _, _, _, _) in + action.image = image completion([action]) } diff --git a/Views/UIKit/SecondaryNavigationTitleView.swift b/Views/UIKit/SecondaryNavigationTitleView.swift index df7a7b9..449af4c 100644 --- a/Views/UIKit/SecondaryNavigationTitleView.swift +++ b/Views/UIKit/SecondaryNavigationTitleView.swift @@ -1,12 +1,12 @@ // Copyright © 2021 Metabolist. All rights reserved. -import Kingfisher +import SDWebImage import UIKit import ViewModels final class SecondaryNavigationTitleView: UIView { private let viewModel: NavigationViewModel - private let avatarImageView = AnimatedImageView() + private let avatarImageView = SDAnimatedImageView() private let displayNameLabel = AnimatedAttachmentLabel() private let accountLabel = UILabel() private let stackView = UIStackView() @@ -70,7 +70,7 @@ private extension SecondaryNavigationTitleView { ? viewModel.identityContext.identity.account?.avatar : viewModel.identityContext.identity.account?.avatarStatic - avatarImageView.kf.setImage(with: avatarURL) + avatarImageView.sd_setImage(with: avatarURL) if let displayName = viewModel.identityContext.identity.account?.displayName, !displayName.isEmpty {