Add blurHash + send passthrough to JellyfinClientController
This commit is contained in:
parent
2e3c12fed3
commit
62a8e5704e
@ -12,9 +12,10 @@
|
||||
3D1015DE2B27F79900F5C29A /* DatamodelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015DD2B27F79900F5C29A /* DatamodelController.swift */; };
|
||||
3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015E32B28000E00F5C29A /* AuthStateController.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 */; };
|
||||
3D77093D2B29350700199889 /* ConsoleSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D77093C2B29350700199889 /* ConsoleSheetView.swift */; };
|
||||
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063CA2B279A310063DD2A /* JelApp.swift */; };
|
||||
3D9063CD2B279A310063DD2A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063CC2B279A310063DD2A /* ContentView.swift */; };
|
||||
3D9063CF2B279A320063DD2A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3D9063CE2B279A320063DD2A /* Assets.xcassets */; };
|
||||
@ -69,7 +70,8 @@
|
||||
3D1015DD2B27F79900F5C29A /* DatamodelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatamodelController.swift; sourceTree = "<group>"; };
|
||||
3D1015E32B28000E00F5C29A /* AuthStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthStateController.swift; sourceTree = "<group>"; };
|
||||
3D41D1F42B2C962500E58234 /* AppearancePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePicker.swift; sourceTree = "<group>"; };
|
||||
3D77093C2B29350700199889 /* ConsoleSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleSheetView.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
3D9063CC2B279A310063DD2A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
@ -126,7 +128,6 @@
|
||||
3DDD67902B293B780026781E /* Dashboard */,
|
||||
3DDD67942B29E27A0026781E /* Settings */,
|
||||
3D91FDC52B28C28900919017 /* SignIn */,
|
||||
3DDD67912B293B9E0026781E /* Utility */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -145,11 +146,20 @@
|
||||
3D1015E02B27FE5700F5C29A /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D41D1F62B2CAD2400E58234 /* BlurHashDecode.swift */,
|
||||
3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3D41D1F82B2CADF500E58234 /* Library */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */,
|
||||
);
|
||||
path = Library;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3D9063BE2B279A310063DD2A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -225,19 +235,12 @@
|
||||
3DDD67902B293B780026781E /* Dashboard */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D41D1F82B2CADF500E58234 /* Library */,
|
||||
3DDD67922B293BC40026781E /* DashBoardView.swift */,
|
||||
);
|
||||
path = Dashboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3DDD67912B293B9E0026781E /* Utility */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D77093C2B29350700199889 /* ConsoleSheetView.swift */,
|
||||
);
|
||||
path = Utility;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3DDD67942B29E27A0026781E /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -392,12 +395,13 @@
|
||||
3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */,
|
||||
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */,
|
||||
3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */,
|
||||
3D77093D2B29350700199889 /* ConsoleSheetView.swift in Sources */,
|
||||
3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */,
|
||||
3D91FDC92B28C62800919017 /* SignInView.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 */,
|
||||
3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */,
|
||||
3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */,
|
||||
3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */,
|
||||
|
@ -62,6 +62,25 @@ class JellyfinClientController: ObservableObject {
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult func send<T>(
|
||||
_ request: Request<T>,
|
||||
delegate: URLSessionDataDelegate? = nil,
|
||||
configure: ((inout URLRequest) throws -> Void)? = nil
|
||||
) async throws -> Response<T> where T : Decodable {
|
||||
return try await self.api.send(request, delegate: delegate, configure: configure)
|
||||
}
|
||||
|
||||
@discardableResult func send(
|
||||
_ request: Request<Void>,
|
||||
delegate: URLSessionDataDelegate? = nil,
|
||||
configure: ((inout URLRequest) throws -> Void)? = nil
|
||||
) async throws -> Response<Void> {
|
||||
return try await self.api.send(request, delegate: delegate, configure: configure)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension JellyfinClientController {
|
||||
func getPublicServerInfo() async -> PublicSystemInfo? {
|
||||
let request = Paths.getPublicSystemInfo
|
||||
do {
|
||||
@ -86,3 +105,4 @@ class JellyfinClientController: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
146
Jel/Models/BlurHashDecode.swift
Normal file
146
Jel/Models/BlurHashDecode.swift
Normal file
@ -0,0 +1,146 @@
|
||||
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<Type: BinaryInteger>(_ 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<Int>) -> Substring {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return self[start...end]
|
||||
}
|
||||
|
||||
subscript (bounds: CountableRange<Int>) -> Substring {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return self[start..<end]
|
||||
}
|
||||
}
|
18
Jel/Views/Dashboard/Library/LibraryIconView.swift
Normal file
18
Jel/Views/Dashboard/Library/LibraryIconView.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// LibraryIconView.swift
|
||||
// Jel
|
||||
//
|
||||
// Created by zerocool on 12/15/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LibraryIconView: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LibraryIconView()
|
||||
}
|
@ -17,8 +17,10 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
Section() {
|
||||
AppearancePicker()
|
||||
} header: {
|
||||
Text("Accessibility")
|
||||
}
|
||||
|
||||
Section {
|
||||
|
@ -1,35 +0,0 @@
|
||||
//
|
||||
// ConsoleSheetView.swift
|
||||
// Jel
|
||||
//
|
||||
// Created by zerocool on 12/12/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PulseUI
|
||||
|
||||
struct ConsoleSheetView: View {
|
||||
@Binding var showingConsoleSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ConsoleView()
|
||||
.closeButtonHidden()
|
||||
.navigationTitle("Debug consoles")
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button {
|
||||
showingConsoleSheet.toggle()
|
||||
} label: {
|
||||
Text("Done")
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ConsoleSheetView(showingConsoleSheet: .constant(true))
|
||||
}
|
Loading…
Reference in New Issue
Block a user