diff --git a/Model/Cache/BaseCacheModel.swift b/Model/Cache/BaseCacheModel.swift index 9b021fce..7fa12c6a 100644 --- a/Model/Cache/BaseCacheModel.swift +++ b/Model/Cache/BaseCacheModel.swift @@ -10,6 +10,8 @@ struct BaseCacheModel { static let jsonFromDataTransformer: (Data) -> JSON = { try! JSON(data: $0) } static let jsonTransformer = Transformer(toData: jsonToDataTransformer, fromData: jsonFromDataTransformer) + static let imageCache = URLCache(memoryCapacity: 512 * 1000 * 1000, diskCapacity: 10 * 1000 * 1000 * 1000) + var models: [CacheModel] { [ FeedCacheModel.shared, @@ -23,10 +25,12 @@ struct BaseCacheModel { func clear() { models.forEach { $0.clear() } + + Self.imageCache.removeAllCachedResponses() } var totalSize: Int { - models.compactMap { $0.storage?.totalDiskStorageSize }.reduce(0, +) + models.compactMap { $0.storage?.totalDiskStorageSize }.reduce(0, +) + Self.imageCache.currentDiskUsage } var totalSizeFormatted: String { diff --git a/Model/ThumbnailsModel.swift b/Model/ThumbnailsModel.swift index 86a71d04..cb6efacd 100644 --- a/Model/ThumbnailsModel.swift +++ b/Model/ThumbnailsModel.swift @@ -7,7 +7,9 @@ final class ThumbnailsModel: ObservableObject { @Published var unloadable = Set() func insertUnloadable(_ url: URL) { - unloadable.insert(url) + DispatchQueue.main.async { + self.unloadable.insert(url) + } } func isUnloadable(_ url: URL!) -> Bool { diff --git a/Shared/Videos/ThumbnailView.swift b/Shared/Videos/ThumbnailView.swift index fb1741bb..ee2f01b0 100644 --- a/Shared/Videos/ThumbnailView.swift +++ b/Shared/Videos/ThumbnailView.swift @@ -1,3 +1,4 @@ +import CachedAsyncImage import SDWebImageSwiftUI import SwiftUI @@ -6,6 +7,28 @@ struct ThumbnailView: View { private let thumbnails = ThumbnailsModel.shared var body: some View { + viewForThumbnailExtension + } + + var thumbnailExtension: String? { + guard let url else { return nil } + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + return urlComponents?.path.components(separatedBy: ".").last + } + + @ViewBuilder var viewForThumbnailExtension: some View { + if thumbnailExtension != nil { + if thumbnailExtension == "webp" { + webImage + } else { + asyncImageIfAvailable + } + } else { + asyncImageIfAvailable + } + } + + var webImage: some View { WebImage(url: url) .resizable() .onFailure { _ in @@ -13,8 +36,31 @@ struct ThumbnailView: View { thumbnails.insertUnloadable(url) } } - .placeholder { - Rectangle().fill(Color("PlaceholderColor")) + .placeholder { placeholder } + } + + @ViewBuilder var asyncImageIfAvailable: some View { + if #available(iOS 15, macOS 12, *) { + CachedAsyncImage(url: url, urlCache: BaseCacheModel.imageCache) { phase in + switch phase { + case let .success(image): + image + .resizable() + case .failure: + placeholder.onAppear { + guard let url else { return } + thumbnails.insertUnloadable(url) + } + default: + placeholder + } } + } else { + webImage + } + } + + var placeholder: some View { + Rectangle().fill(Color("PlaceholderColor")) } } diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 7e0d374a..c1f6b4db 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -188,6 +188,9 @@ 371AC09F294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; 371AC0A0294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; 371AC0A1294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; + 371AC0AC294D1A490085989E /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 371AC0AB294D1A490085989E /* CachedAsyncImage */; }; + 371AC0B2294D1C230085989E /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 371AC0B1294D1C230085989E /* CachedAsyncImage */; }; + 371AC0B4294D1C290085989E /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 371AC0B3294D1C290085989E /* CachedAsyncImage */; }; 371B7E5C27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; 371B7E5D27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; 371B7E5E27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; @@ -1511,6 +1514,7 @@ buildActionMask = 2147483647; files = ( 37C2212B27ADA43700305B41 /* VideoToolbox.framework in Frameworks */, + 371AC0AC294D1A490085989E /* CachedAsyncImage in Frameworks */, 3736A214286BB72300C9E5EE /* libswscale.xcframework in Frameworks */, 3736A216286BB72300C9E5EE /* libavfilter.xcframework in Frameworks */, 3799AC0928B03CED001376F9 /* ActiveLabel in Frameworks */, @@ -1579,6 +1583,7 @@ 3703206227D2BB1B007A0CB8 /* SDWebImagePINPlugin in Frameworks */, 370F4FCB27CC16CB001B35DC /* libxcb.1.1.0.dylib in Frameworks */, 3703206427D2BB30007A0CB8 /* Logging in Frameworks */, + 371AC0B2294D1C230085989E /* CachedAsyncImage in Frameworks */, 370F4FD027CC16CB001B35DC /* libharfbuzz.0.dylib in Frameworks */, 370F4FCC27CC16CB001B35DC /* libxcb-xfixes.0.0.0.dylib in Frameworks */, 3703206627D2BB35007A0CB8 /* PINCache in Frameworks */, @@ -1635,6 +1640,7 @@ 3736A213286BB72300C9E5EE /* libswresample.xcframework in Frameworks */, 377F9F76294403880043F856 /* Cache in Frameworks */, 37FB285427220D8400A57617 /* SDWebImagePINPlugin in Frameworks */, + 371AC0B4294D1C290085989E /* CachedAsyncImage in Frameworks */, 3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */, 3772003927E8EEB700CB2475 /* AVFoundation.framework in Frameworks */, 3736A20F286BB72300C9E5EE /* libass.xcframework in Frameworks */, @@ -2564,6 +2570,7 @@ 375B8AB028B57F4200397B31 /* KeychainAccess */, 3797104828D3D10600D5F53C /* SDWebImageSwiftUI */, 377F9F73294403770043F856 /* Cache */, + 371AC0AB294D1A490085989E /* CachedAsyncImage */, ); productName = "Yattee (iOS)"; productReference = 37D4B0C92671614900C925CA /* Yattee.app */; @@ -2601,6 +2608,7 @@ 375B8AB628B583BD00397B31 /* KeychainAccess */, 3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */, 374D11E62943C56300CB4350 /* Cache */, + 371AC0B1294D1C230085989E /* CachedAsyncImage */, ); productName = "Yattee (macOS)"; productReference = 37D4B0CF2671614900C925CA /* Yattee.app */; @@ -2679,6 +2687,7 @@ 3732BFCF28B83763009F3F4D /* KeychainAccess */, 3797104C28D3D19100D5F53C /* SDWebImageSwiftUI */, 377F9F75294403880043F856 /* Cache */, + 371AC0B3294D1C290085989E /* CachedAsyncImage */, ); productName = Yattee; productReference = 37D4B158267164AE00C925CA /* Yattee.app */; @@ -2788,6 +2797,7 @@ 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */, 3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, 374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */, + 371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */, ); productRefGroup = 37D4B0CA2671614900C925CA /* Products */; projectDirPath = ""; @@ -4679,6 +4689,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/lorenzofiamingo/swiftui-cached-async-image"; + requirement = { + branch = main; + kind = branch; + }; + }; 372915E22687E33E00F5A35B /* XCRemoteSwiftPackageReference "Defaults" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/Defaults"; @@ -4858,6 +4876,21 @@ package = 37BADCA32699FB72009BE4FB /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + 371AC0AB294D1A490085989E /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = 371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; + productName = CachedAsyncImage; + }; + 371AC0B1294D1C230085989E /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = 371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; + productName = CachedAsyncImage; + }; + 371AC0B3294D1C290085989E /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = 371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; + productName = CachedAsyncImage; + }; 372915E32687E33E00F5A35B /* Defaults */ = { isa = XCSwiftPackageProductDependency; package = 372915E22687E33E00F5A35B /* XCRemoteSwiftPackageReference "Defaults" */; diff --git a/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bb7177a9..7e068d2d 100644 --- a/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Yattee.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -144,6 +144,15 @@ "version" : "1.4.4" } }, + { + "identity" : "swiftui-cached-async-image", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image", + "state" : { + "branch" : "main", + "revision" : "f94be38411297c71aa8df69c6875127e6d8d7a92" + } + }, { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl",