diff --git a/Shared/Components/FastSVGView.swift b/Shared/Components/FastSVGView.swift new file mode 100644 index 00000000..3a4c2f18 --- /dev/null +++ b/Shared/Components/FastSVGView.swift @@ -0,0 +1,33 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024 Jellyfin & Jellyfin Contributors +// + +import SVGKit +import SwiftUI + +// Note: SVGKit does not support the simulator and will appear blank. + +// This seemed necessary because using SwiftUI `Image(uiImage:)` would cause severe lag. +struct FastSVGView: UIViewRepresentable { + + let data: Data + + func makeUIView(context: Context) -> some UIView { + let imageView = SVGKFastImageView(svgkImage: SVGKImage(data: data))! + imageView.contentMode = .scaleAspectFit + imageView.clipsToBounds = true + + imageView.setContentHuggingPriority(.defaultLow, for: .vertical) + imageView.setContentHuggingPriority(.defaultLow, for: .horizontal) + imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + + return imageView + } + + func updateUIView(_ uiView: UIViewType, context: Context) {} +} diff --git a/Shared/Components/ImageView.swift b/Shared/Components/ImageView.swift index 48fc7c8e..73c86a20 100644 --- a/Shared/Components/ImageView.swift +++ b/Shared/Components/ImageView.swift @@ -7,16 +7,24 @@ // import BlurHashKit -import JellyfinAPI import Nuke import NukeUI import SwiftUI -import UIKit -private let imagePipeline = ImagePipeline(configuration: .withDataCache) +private let imagePipeline = { + + ImageDecoderRegistry.shared.register { context in + guard let mimeType = context.urlResponse?.mimeType else { return nil } + return mimeType.contains("svg") ? ImageDecoders.Empty() : nil + } + + return ImagePipeline(configuration: .withDataCache) +}() // TODO: Binding inits? // - instead of removing first source on failure, just safe index into sources +// TODO: currently SVGs are only supported for logos, which are only used in a few places. +// make it so when displaying an SVG there is a unified `image` caller modifier struct ImageView: View { @State @@ -42,8 +50,12 @@ struct ImageView: View { if state.isLoading { _placeholder(currentSource) } else if let _image = state.image { - image(_image.resizable()) - .eraseToAnyView() + if let data = state.imageContainer?.data { + FastSVGView(data: data) + } else { + image(_image.resizable()) + .eraseToAnyView() + } } else if state.error != nil { failure() .eraseToAnyView() @@ -103,7 +115,7 @@ extension ImageView { } } -// MARK: Extensions +// MARK: Modifiers extension ImageView { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e5e0ccd5..f0c1607a 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -221,6 +221,10 @@ E1153DAF2BBA734200424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DAE2BBA734200424D36 /* CollectionHStack */; }; E1153DB12BBA734C00424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DB02BBA734C00424D36 /* CollectionHStack */; }; E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DB22BBA80B400424D36 /* EmptyCard.swift */; }; + E1153DCC2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; }; + E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; }; + E1153DD02BBB634F00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DCF2BBB634F00424D36 /* SVGKit */; }; + E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DD12BBB649C00424D36 /* SVGKit */; }; E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */; }; E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; @@ -973,6 +977,7 @@ E1153D992BBA3E9800424D36 /* ErrorCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorCard.swift; sourceTree = ""; }; E1153D9B2BBA3E9D00424D36 /* LoadingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCard.swift; sourceTree = ""; }; E1153DB22BBA80B400424D36 /* EmptyCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyCard.swift; sourceTree = ""; }; + E1153DCB2BBB633B00424D36 /* FastSVGView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FastSVGView.swift; sourceTree = ""; }; E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = ""; }; E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = ""; }; E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = ""; }; @@ -1361,6 +1366,7 @@ E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, E1A7B1652B9A9F7800152546 /* PreferencesView in Frameworks */, E1153DA92BBA642A00424D36 /* CollectionVGrid in Frameworks */, + E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */, 62666E1527E501C800EC0ECD /* AVFoundation.framework in Frameworks */, E13AF3BC28A0C59E009093AB /* BlurHashKit in Frameworks */, E1153DB12BBA734C00424D36 /* CollectionHStack in Frameworks */, @@ -1402,6 +1408,7 @@ 62666E0C27E501A500EC0ECD /* OpenGLES.framework in Frameworks */, E19E6E0A28A0BEFF005C10C8 /* BlurHashKit in Frameworks */, E1FAD1C62A0375BA007F5521 /* UDPBroadcast in Frameworks */, + E1153DD02BBB634F00424D36 /* SVGKit in Frameworks */, E18D6AA62BAA96F000A0D167 /* CollectionHStack in Frameworks */, 62666E0127E5016900EC0ECD /* CoreFoundation.framework in Frameworks */, E14CB6862A9FF62A001586C6 /* JellyfinAPI in Frameworks */, @@ -2639,6 +2646,7 @@ children = ( E104DC952B9E7E29008F506D /* AssertionFailureView.swift */, E18E0203288749200022598C /* BlurView.swift */, + E1153DCB2BBB633B00424D36 /* FastSVGView.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, E1D37F472B9C648E00343D2B /* MaxHeightText.swift */, 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, @@ -2990,6 +2998,7 @@ E1392FEC2BA218A80034110D /* SwiftUIIntrospect */, E1153DA82BBA642A00424D36 /* CollectionVGrid */, E1153DB02BBA734C00424D36 /* CollectionHStack */, + E1153DD12BBB649C00424D36 /* SVGKit */, ); productName = "JellyfinPlayer tvOS"; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; @@ -3043,6 +3052,7 @@ E1153DA62BBA641000424D36 /* CollectionVGrid */, E1153DAB2BBA6AD200424D36 /* CollectionHStack */, E1153DAE2BBA734200424D36 /* CollectionHStack */, + E1153DCF2BBB634F00424D36 /* SVGKit */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -3114,6 +3124,7 @@ E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */, E1153DA52BBA641000424D36 /* XCRemoteSwiftPackageReference "CollectionVGrid" */, E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */, + E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -3435,6 +3446,7 @@ E12E30F5296392EC0022FAC9 /* EnumPickerView.swift in Sources */, E1575E72293E77B5001665B1 /* Utilities.swift in Sources */, E1575E84293E7A00001665B1 /* PrimaryAppIcon.swift in Sources */, + E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */, E1E6C45129B104850064123F /* Button.swift in Sources */, E1DC981A296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */, E1A1528B28FD22F600600579 /* TextPairView.swift in Sources */, @@ -3678,6 +3690,7 @@ E139CC1D28EC836F00688DE2 /* ChapterOverlay.swift in Sources */, E168BD14289A4162001A6922 /* LatestInLibraryView.swift in Sources */, E1E6C45029B104840064123F /* Button.swift in Sources */, + E1153DCC2BBB633B00424D36 /* FastSVGView.swift in Sources */, E1E5D5492783CDD700692DFE /* VideoPlayerSettingsView.swift in Sources */, E14EDEC52B8FB64E000F00A4 /* AnyItemFilter.swift in Sources */, E11245B728D97ED200D8A977 /* TopBarView.swift in Sources */, @@ -4398,6 +4411,14 @@ kind = branch; }; }; + E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SVGKit/SVGKit"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; @@ -4595,6 +4616,16 @@ package = E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */; productName = CollectionHStack; }; + E1153DCF2BBB634F00424D36 /* SVGKit */ = { + isa = XCSwiftPackageProductDependency; + package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */; + productName = SVGKit; + }; + E1153DD12BBB649C00424D36 /* SVGKit */ = { + isa = XCSwiftPackageProductDependency; + package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */; + productName = SVGKit; + }; E12186DD2718F1C50010884C /* Defaults */ = { isa = XCSwiftPackageProductDependency; package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */; diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 179cd155..b3822c78 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "1.2.0" } }, + { + "identity" : "cocoalumberjack", + "kind" : "remoteSourceControl", + "location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git", + "state" : { + "revision" : "4b8714a7fb84d42393314ce897127b3939885ec3", + "version" : "3.8.5" + } + }, { "identity" : "collectionhstack", "kind" : "remoteSourceControl", @@ -135,6 +144,15 @@ "revision" : "6dda57096e16020342b36ebea86dc4bdf6783426" } }, + { + "identity" : "svgkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SVGKit/SVGKit", + "state" : { + "revision" : "58152b9f7c85eab239160b36ffdfd364aa43d666", + "version" : "3.0.0" + } + }, { "identity" : "swift-algorithms", "kind" : "remoteSourceControl",