diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index be9aad1..374ee5d 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -11,8 +11,9 @@ 3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015DA2B27F5D300F5C29A /* Model.xcdatamodeld */; }; 3D1015DE2B27F79900F5C29A /* DatamodelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015DD2B27F79900F5C29A /* DatamodelController.swift */; }; 3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015E32B28000E00F5C29A /* AuthStateController.swift */; }; + 3D16FC3A2B2CC22A00E6D8B3 /* BlurHashKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3D16FC392B2CC22A00E6D8B3 /* BlurHashKit */; }; + 3D16FC3C2B2CDFB500E6D8B3 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D16FC3B2B2CDFB500E6D8B3 /* LibraryView.swift */; }; 3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F42B2C962500E58234 /* AppearancePicker.swift */; }; - 3D41D1F72B2CAD2400E58234 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F62B2CAD2400E58234 /* BlurHashDecode.swift */; }; 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */; }; 3D7709392B29139700199889 /* Pulse in Frameworks */ = {isa = PBXBuildFile; productRef = 3D7709382B29139700199889 /* Pulse */; }; 3D77093B2B29139700199889 /* PulseUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3D77093A2B29139700199889 /* PulseUI */; }; @@ -27,8 +28,9 @@ 3D91FDC92B28C62800919017 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDC82B28C62800919017 /* SignInView.swift */; }; 3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */; }; 3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */; }; + 3DAA71C62B31E19200D5FB33 /* AsyncImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAA71C52B31E19200D5FB33 /* AsyncImageView.swift */; }; 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */; }; - 3DDD67932B293BC40026781E /* DashBoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67922B293BC40026781E /* DashBoardView.swift */; }; + 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67922B293BC40026781E /* DashboardView.swift */; }; 3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67952B29E28B0026781E /* SettingsView.swift */; }; 3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */; }; /* End PBXBuildFile section */ @@ -69,8 +71,8 @@ 3D1015DB2B27F5D300F5C29A /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; 3D1015DD2B27F79900F5C29A /* DatamodelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatamodelController.swift; sourceTree = ""; }; 3D1015E32B28000E00F5C29A /* AuthStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthStateController.swift; sourceTree = ""; }; + 3D16FC3B2B2CDFB500E6D8B3 /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; 3D41D1F42B2C962500E58234 /* AppearancePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePicker.swift; sourceTree = ""; }; - 3D41D1F62B2CAD2400E58234 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BlurHashDecode.swift; path = Jel/Models/BlurHashDecode.swift; sourceTree = SOURCE_ROOT; }; 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryIconView.swift; sourceTree = ""; }; 3D9063C72B279A310063DD2A /* Jel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jel.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3D9063CA2B279A310063DD2A /* JelApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelApp.swift; sourceTree = ""; }; @@ -86,9 +88,10 @@ 3D91FDC82B28C62800919017 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInToServerView.swift; sourceTree = ""; }; 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinDateFormatter.swift; sourceTree = ""; }; + 3DAA71C52B31E19200D5FB33 /* AsyncImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncImageView.swift; sourceTree = ""; }; 3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = ""; }; - 3DDD67922B293BC40026781E /* DashBoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashBoardView.swift; sourceTree = ""; }; + 3DDD67922B293BC40026781E /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; 3DDD67952B29E28B0026781E /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinClientController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -98,6 +101,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3D16FC3A2B2CC22A00E6D8B3 /* BlurHashKit in Frameworks */, 3D77093B2B29139700199889 /* PulseUI in Frameworks */, 3D7709392B29139700199889 /* Pulse in Frameworks */, 3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */, @@ -124,6 +128,7 @@ 3D1015D72B27F54A00F5C29A /* Views */ = { isa = PBXGroup; children = ( + 3DAA71C42B31E11D00D5FB33 /* Utility */, 3D9063CC2B279A310063DD2A /* ContentView.swift */, 3DDD67902B293B780026781E /* Dashboard */, 3DDD67942B29E27A0026781E /* Settings */, @@ -146,7 +151,6 @@ 3D1015E02B27FE5700F5C29A /* Models */ = { isa = PBXGroup; children = ( - 3D41D1F62B2CAD2400E58234 /* BlurHashDecode.swift */, 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */, ); path = Models; @@ -155,6 +159,7 @@ 3D41D1F82B2CADF500E58234 /* Library */ = { isa = PBXGroup; children = ( + 3D16FC3B2B2CDFB500E6D8B3 /* LibraryView.swift */, 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */, ); path = Library; @@ -232,11 +237,19 @@ path = SignIn; sourceTree = ""; }; + 3DAA71C42B31E11D00D5FB33 /* Utility */ = { + isa = PBXGroup; + children = ( + 3DAA71C52B31E19200D5FB33 /* AsyncImageView.swift */, + ); + path = Utility; + sourceTree = ""; + }; 3DDD67902B293B780026781E /* Dashboard */ = { isa = PBXGroup; children = ( 3D41D1F82B2CADF500E58234 /* Library */, - 3DDD67922B293BC40026781E /* DashBoardView.swift */, + 3DDD67922B293BC40026781E /* DashboardView.swift */, ); path = Dashboard; sourceTree = ""; @@ -271,6 +284,7 @@ 3D9064582B27E4C70063DD2A /* JellyfinKit */, 3D7709382B29139700199889 /* Pulse */, 3D77093A2B29139700199889 /* PulseUI */, + 3D16FC392B2CC22A00E6D8B3 /* BlurHashKit */, ); productName = Jel; productReference = 3D9063C72B279A310063DD2A /* Jel.app */; @@ -346,6 +360,7 @@ mainGroup = 3D9063BE2B279A310063DD2A; packageReferences = ( 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */, + 3D16FC382B2CC22A00E6D8B3 /* XCRemoteSwiftPackageReference "BlurHashKit" */, ); productRefGroup = 3D9063C82B279A310063DD2A /* Products */; projectDirPath = ""; @@ -397,11 +412,12 @@ 3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */, 3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */, 3D91FDC92B28C62800919017 /* SignInView.swift in Sources */, - 3DDD67932B293BC40026781E /* DashBoardView.swift in Sources */, + 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */, 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */, 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */, 3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */, - 3D41D1F72B2CAD2400E58234 /* BlurHashDecode.swift in Sources */, + 3D16FC3C2B2CDFB500E6D8B3 /* LibraryView.swift in Sources */, + 3DAA71C62B31E19200D5FB33 /* AsyncImageView.swift in Sources */, 3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */, 3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */, 3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */, @@ -769,6 +785,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 3D16FC382B2CC22A00E6D8B3 /* XCRemoteSwiftPackageReference "BlurHashKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/LePips/BlurHashKit"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 1.2.0; + }; + }; 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/Pulse"; @@ -780,6 +804,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 3D16FC392B2CC22A00E6D8B3 /* BlurHashKit */ = { + isa = XCSwiftPackageProductDependency; + package = 3D16FC382B2CC22A00E6D8B3 /* XCRemoteSwiftPackageReference "BlurHashKit" */; + productName = BlurHashKit; + }; 3D7709382B29139700199889 /* Pulse */ = { isa = XCSwiftPackageProductDependency; package = 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */; diff --git a/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e1e75ae..71669bf 100644 --- a/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "BlurHashKit", + "repositoryURL": "https://github.com/LePips/BlurHashKit", + "state": { + "branch": null, + "revision": "c0bd7423398de68cbeb3f99bff70f79c38bf36ab", + "version": "1.2.0" + } + }, { "package": "Get", "repositoryURL": "https://github.com/kean/Get", diff --git a/Jel/Controllers/JellyfinClientController.swift b/Jel/Controllers/JellyfinClientController.swift index 11389c9..08b74d0 100644 --- a/Jel/Controllers/JellyfinClientController.swift +++ b/Jel/Controllers/JellyfinClientController.swift @@ -40,10 +40,13 @@ class JellyfinClientController: ObservableObject { self.api = APIClient(baseURL: serverUrl) self.setUrl(url: serverUrl) + + self.setToken(token: self.authState.authToken ?? "") } func setToken(token: String) { self.authHeaders.Token = token + self.setUrl(url: self.authState.serverUrl) } func setUrl(url: URL?) { @@ -62,6 +65,10 @@ class JellyfinClientController: ObservableObject { }) } + func getUrl() -> URL? { + return self.api.configuration.baseURL + } + @discardableResult func send( _ request: Request, delegate: URLSessionDataDelegate? = nil, @@ -101,7 +108,10 @@ extension JellyfinClientController { DispatchQueue.main.async { self.authState.loggedIn = true self.authState.authToken = res.value.accessToken + self.authState.userId = res.value.user?.id self.authState.save() + + self.setToken(token: self.authState.authToken ?? "") } } } diff --git a/Jel/JelApp.swift b/Jel/JelApp.swift index 3040b5c..ab7a74d 100644 --- a/Jel/JelApp.swift +++ b/Jel/JelApp.swift @@ -26,6 +26,8 @@ struct JelApp: App { .task { AuthStateController.shared.load() SettingsController.shared.load() + jellyfinClientController.setUrl(url: AuthStateController.shared.serverUrl) + jellyfinClientController.setToken(token: AuthStateController.shared.authToken ?? "") } } } diff --git a/Jel/Models/BlurHashDecode.swift b/Jel/Models/BlurHashDecode.swift deleted file mode 100644 index 7fe3b39..0000000 --- a/Jel/Models/BlurHashDecode.swift +++ /dev/null @@ -1,146 +0,0 @@ -import UIKit - -extension UIImage { - public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) { - guard blurHash.count >= 6 else { return nil } - - let sizeFlag = String(blurHash[0]).decode83() - let numY = (sizeFlag / 9) + 1 - let numX = (sizeFlag % 9) + 1 - - let quantisedMaximumValue = String(blurHash[1]).decode83() - let maximumValue = Float(quantisedMaximumValue + 1) / 166 - - guard blurHash.count == 4 + 2 * numX * numY else { return nil } - - let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in - if i == 0 { - let value = String(blurHash[2 ..< 6]).decode83() - return decodeDC(value) - } else { - let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83() - return decodeAC(value, maximumValue: maximumValue * punch) - } - } - - let width = Int(size.width) - let height = Int(size.height) - let bytesPerRow = width * 3 - guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil } - CFDataSetLength(data, bytesPerRow * height) - guard let pixels = CFDataGetMutableBytePtr(data) else { return nil } - - for y in 0 ..< height { - for x in 0 ..< width { - var r: Float = 0 - var g: Float = 0 - var b: Float = 0 - - for j in 0 ..< numY { - for i in 0 ..< numX { - let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height)) - let colour = colours[i + j * numX] - r += colour.0 * basis - g += colour.1 * basis - b += colour.2 * basis - } - } - - let intR = UInt8(linearTosRGB(r)) - let intG = UInt8(linearTosRGB(g)) - let intB = UInt8(linearTosRGB(b)) - - pixels[3 * x + 0 + y * bytesPerRow] = intR - pixels[3 * x + 1 + y * bytesPerRow] = intG - pixels[3 * x + 2 + y * bytesPerRow] = intB - } - } - - let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) - - guard let provider = CGDataProvider(data: data) else { return nil } - guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow, - space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil } - - self.init(cgImage: cgImage) - } -} - -private func decodeDC(_ value: Int) -> (Float, Float, Float) { - let intR = value >> 16 - let intG = (value >> 8) & 255 - let intB = value & 255 - return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)) -} - -private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) { - let quantR = value / (19 * 19) - let quantG = (value / 19) % 19 - let quantB = value % 19 - - let rgb = ( - signPow((Float(quantR) - 9) / 9, 2) * maximumValue, - signPow((Float(quantG) - 9) / 9, 2) * maximumValue, - signPow((Float(quantB) - 9) / 9, 2) * maximumValue - ) - - return rgb -} - -private func signPow(_ value: Float, _ exp: Float) -> Float { - return copysign(pow(abs(value), exp), value) -} - -private func linearTosRGB(_ value: Float) -> Int { - let v = max(0, min(1, value)) - if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } - else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } -} - -private func sRGBToLinear(_ value: Type) -> Float { - let v = Float(Int64(value)) / 255 - if v <= 0.04045 { return v / 12.92 } - else { return pow((v + 0.055) / 1.055, 2.4) } -} - -private let encodeCharacters: [String] = { - return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } -}() - -private let decodeCharacters: [String: Int] = { - var dict: [String: Int] = [:] - for (index, character) in encodeCharacters.enumerated() { - dict[character] = index - } - return dict -}() - -extension String { - func decode83() -> Int { - var value: Int = 0 - for character in self { - if let digit = decodeCharacters[String(character)] { - value = value * 83 + digit - } - } - return value - } -} - -private extension String { - subscript (offset: Int) -> Character { - return self[index(startIndex, offsetBy: offset)] - } - - subscript (bounds: CountableClosedRange) -> Substring { - let start = index(startIndex, offsetBy: bounds.lowerBound) - let end = index(startIndex, offsetBy: bounds.upperBound) - return self[start...end] - } - - subscript (bounds: CountableRange) -> Substring { - let start = index(startIndex, offsetBy: bounds.lowerBound) - let end = index(startIndex, offsetBy: bounds.upperBound) - return self[start..