From ce0be87c7f39e9763d835a685cb65eb38c7e68c3 Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Wed, 21 Feb 2024 12:20:24 -0500 Subject: Cleanup + change SignInView to be a sheet --- Jel.xcodeproj/project.pbxproj | 32 +++-- Jel/Controllers/AuthStateController.swift | 4 + Jel/Controllers/UIScreenCurrent.swift | 28 ---- Jel/Extensions/BindingNot.swift | 18 +++ Jel/Extensions/BlurHashDecode.swift | 153 +++++++++++++++++++++ Jel/Extensions/JellyfinDateFormatter.swift | 33 +++++ Jel/Extensions/JellyfinKitExtensions.swift | 28 ++++ Jel/Extensions/UIScreenCurrent.swift | 28 ++++ Jel/Extensions/UIScreenWidthExtension.swift | 15 ++ Jel/Extensions/ViewExtensions.swift | 31 +++++ Jel/Models/BlurHashDecode.swift | 153 --------------------- Jel/Models/JellyfinDateFormatter.swift | 33 ----- Jel/Models/JellyfinKitExtensions.swift | 28 ---- Jel/Models/UIScreenWidthExtension.swift | 15 -- Jel/Models/ViewExtensions.swift | 31 ----- Jel/Views/ContentView.swift | 33 +++-- Jel/Views/Dashboard/DashboardLibraryView.swift | 2 +- Jel/Views/Dashboard/DashboardView.swift | 5 + Jel/Views/Item/ItemGenresView.swift | 2 +- Jel/Views/Item/Person/ItemPersonDetailView.swift | 2 +- Jel/Views/Item/Person/ItemPersonIconView.swift | 2 +- Jel/Views/Item/Series/ItemSeriesEpisodesView.swift | 2 +- Jel/Views/Item/Series/ItemSeriesSeasonsView.swift | 2 +- Jel/Views/Library/LibraryDetailView.swift | 2 +- Jel/Views/Settings/SettingsView.swift | 8 +- Jel/Views/SignIn/AddServerView.swift | 12 +- Jel/Views/SignIn/SignInToServerView.swift | 2 +- Jel/Views/SignIn/SignInView.swift | 9 +- 28 files changed, 382 insertions(+), 331 deletions(-) delete mode 100644 Jel/Controllers/UIScreenCurrent.swift create mode 100644 Jel/Extensions/BindingNot.swift create mode 100644 Jel/Extensions/BlurHashDecode.swift create mode 100644 Jel/Extensions/JellyfinDateFormatter.swift create mode 100644 Jel/Extensions/JellyfinKitExtensions.swift create mode 100644 Jel/Extensions/UIScreenCurrent.swift create mode 100644 Jel/Extensions/UIScreenWidthExtension.swift create mode 100644 Jel/Extensions/ViewExtensions.swift delete mode 100644 Jel/Models/BlurHashDecode.swift delete mode 100644 Jel/Models/JellyfinDateFormatter.swift delete mode 100644 Jel/Models/JellyfinKitExtensions.swift delete mode 100644 Jel/Models/UIScreenWidthExtension.swift delete mode 100644 Jel/Models/ViewExtensions.swift diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index 6855a6e..e98363e 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 3DBAC9E42B4C7404005F8764 /* UIScreenCurrent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAC9E32B4C7404005F8764 /* UIScreenCurrent.swift */; }; 3DBAC9EA2B4C8927005F8764 /* ItemPersonIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAC9E92B4C8927005F8764 /* ItemPersonIconView.swift */; }; 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */; }; + 3DD6850C2B85A6A8002FAA1A /* BindingNot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD6850B2B85A6A8002FAA1A /* BindingNot.swift */; }; 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67922B293BC40026781E /* DashboardView.swift */; }; 3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67952B29E28B0026781E /* SettingsView.swift */; }; 3DDF35D52B7D384000423923 /* ItemSeriesEpisodesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDF35D42B7D384000423923 /* ItemSeriesEpisodesView.swift */; }; @@ -129,6 +130,7 @@ 3DBAC9E92B4C8927005F8764 /* ItemPersonIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPersonIconView.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 = ""; }; + 3DD6850B2B85A6A8002FAA1A /* BindingNot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingNot.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 = ""; }; 3DDF35D42B7D384000423923 /* ItemSeriesEpisodesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSeriesEpisodesView.swift; sourceTree = ""; }; @@ -189,23 +191,10 @@ 3D1015E32B28000E00F5C29A /* AuthStateController.swift */, 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */, 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */, - 3DBAC9E32B4C7404005F8764 /* UIScreenCurrent.swift */, ); path = Controllers; sourceTree = ""; }; - 3D1015E02B27FE5700F5C29A /* Models */ = { - isa = PBXGroup; - children = ( - 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */, - 3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */, - 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */, - 3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */, - 3D74AE112B7D4EB000C17F2E /* UIScreenWidthExtension.swift */, - ); - path = Models; - sourceTree = ""; - }; 3D13F95D2B375DAC00E91913 /* Item */ = { isa = PBXGroup; children = ( @@ -274,7 +263,7 @@ 3D9063C92B279A310063DD2A /* Jel */ = { isa = PBXGroup; children = ( - 3D1015E02B27FE5700F5C29A /* Models */, + 3DD6850A2B85A654002FAA1A /* Extensions */, 3D1015DF2B27F8EE00F5C29A /* Controllers */, 3D1015D72B27F54A00F5C29A /* Views */, 3D9063CA2B279A310063DD2A /* JelApp.swift */, @@ -341,6 +330,20 @@ path = Person; sourceTree = ""; }; + 3DD6850A2B85A654002FAA1A /* Extensions */ = { + isa = PBXGroup; + children = ( + 3DBAC9E32B4C7404005F8764 /* UIScreenCurrent.swift */, + 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */, + 3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */, + 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */, + 3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */, + 3D74AE112B7D4EB000C17F2E /* UIScreenWidthExtension.swift */, + 3DD6850B2B85A6A8002FAA1A /* BindingNot.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 3DDD67902B293B780026781E /* Dashboard */ = { isa = PBXGroup; children = ( @@ -522,6 +525,7 @@ 3DAFA8EF2B3B707B00D71AD1 /* ItemMovieView.swift in Sources */, 3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */, 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */, + 3DD6850C2B85A6A8002FAA1A /* BindingNot.swift in Sources */, 3D13F9612B37637500E91913 /* ItemMediaView.swift in Sources */, 3D2552492B7A8B3100192879 /* ItemSeriesSeasonsView.swift in Sources */, 3DFE7AF72B5260FF005461FE /* ItemPersonView.swift in Sources */, diff --git a/Jel/Controllers/AuthStateController.swift b/Jel/Controllers/AuthStateController.swift index d117820..11aacbf 100644 --- a/Jel/Controllers/AuthStateController.swift +++ b/Jel/Controllers/AuthStateController.swift @@ -14,6 +14,8 @@ class AuthStateController: ObservableObject { @Published var userId: String? @Published var username: String? + @Published var loaded: Bool = false + private let defaults = UserDefaults.standard static let shared = AuthStateController() @@ -40,6 +42,8 @@ class AuthStateController: ObservableObject { if let oldUsername = defaults.string(forKey: "AuthState_username") { self.username = oldUsername } + + self.loaded = true } func save() { diff --git a/Jel/Controllers/UIScreenCurrent.swift b/Jel/Controllers/UIScreenCurrent.swift deleted file mode 100644 index 86d4ca0..0000000 --- a/Jel/Controllers/UIScreenCurrent.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// UIScreenCurrent.swift -// Jel -// -// Created by zerocool on 1/8/24. -// - -import Foundation -import SwiftUI - -extension UIWindow { - static var current: UIWindow? { - for scene in UIApplication.shared.connectedScenes { - guard let windowScene = scene as? UIWindowScene else { continue } - for window in windowScene.windows { - if window.isKeyWindow { return window } - } - } - return nil - } -} - - -extension UIScreen { - static var current: UIScreen? { - UIWindow.current?.screen - } -} diff --git a/Jel/Extensions/BindingNot.swift b/Jel/Extensions/BindingNot.swift new file mode 100644 index 0000000..92951b5 --- /dev/null +++ b/Jel/Extensions/BindingNot.swift @@ -0,0 +1,18 @@ +// +// BindingNot.swift +// Jel +// +// Created by zerocool on 2/20/24. +// + +import Foundation +import SwiftUI + +extension Binding where Value == Bool { + var not: Binding { + Binding( + get: { !self.wrappedValue }, + set: { self.wrappedValue = !$0 } + ) + } +} diff --git a/Jel/Extensions/BlurHashDecode.swift b/Jel/Extensions/BlurHashDecode.swift new file mode 100644 index 0000000..93c4896 --- /dev/null +++ b/Jel/Extensions/BlurHashDecode.swift @@ -0,0 +1,153 @@ +// +// BlurHashDecode.swift +// Jel +// +// Created by zerocool on 12/22/23. +// + +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.. String? { + let formatter: DateComponentsFormatter = { + let localFormatter = DateComponentsFormatter() + localFormatter.unitsStyle = .brief + localFormatter.allowedUnits = [.hour, .minute] + + return localFormatter + }() + + if let runTimeTicks = self.runTimeTicks { + let text = formatter.string(from: Double(runTimeTicks / 10_000_000)) + return text + } + + return nil + } +} diff --git a/Jel/Extensions/UIScreenCurrent.swift b/Jel/Extensions/UIScreenCurrent.swift new file mode 100644 index 0000000..86d4ca0 --- /dev/null +++ b/Jel/Extensions/UIScreenCurrent.swift @@ -0,0 +1,28 @@ +// +// UIScreenCurrent.swift +// Jel +// +// Created by zerocool on 1/8/24. +// + +import Foundation +import SwiftUI + +extension UIWindow { + static var current: UIWindow? { + for scene in UIApplication.shared.connectedScenes { + guard let windowScene = scene as? UIWindowScene else { continue } + for window in windowScene.windows { + if window.isKeyWindow { return window } + } + } + return nil + } +} + + +extension UIScreen { + static var current: UIScreen? { + UIWindow.current?.screen + } +} diff --git a/Jel/Extensions/UIScreenWidthExtension.swift b/Jel/Extensions/UIScreenWidthExtension.swift new file mode 100644 index 0000000..6f2f5de --- /dev/null +++ b/Jel/Extensions/UIScreenWidthExtension.swift @@ -0,0 +1,15 @@ +// +// UIScreenWidthExtension.swift +// Jel +// +// Created by zerocool on 2/14/24. +// + +import Foundation +import UIKit + +extension UIScreen{ + static let screenWidth = UIScreen.main.bounds.size.width + static let screenHeight = UIScreen.main.bounds.size.height + static let screenSize = UIScreen.main.bounds.size +} diff --git a/Jel/Extensions/ViewExtensions.swift b/Jel/Extensions/ViewExtensions.swift new file mode 100644 index 0000000..7f54865 --- /dev/null +++ b/Jel/Extensions/ViewExtensions.swift @@ -0,0 +1,31 @@ +// +// ViewExtensions.swift +// Jel +// +// Created by zerocool on 12/25/23. +// + +import SwiftUI + +extension View { + /// Applies the given transform if the given condition evaluates to `true`. + @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { + if condition() { + transform(self) + } else { + self + } + } +} + +extension View { + /// Applies an inverse mask to the given view + public func inverseMask(_ mask: Content) -> some View { + let inverseMask = mask + .foregroundStyle(.black) + .background(.white) + .compositingGroup() + .luminanceToAlpha() + return self.mask(inverseMask) + } +} diff --git a/Jel/Models/BlurHashDecode.swift b/Jel/Models/BlurHashDecode.swift deleted file mode 100644 index 93c4896..0000000 --- a/Jel/Models/BlurHashDecode.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// BlurHashDecode.swift -// Jel -// -// Created by zerocool on 12/22/23. -// - -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.. String? { - let formatter: DateComponentsFormatter = { - let localFormatter = DateComponentsFormatter() - localFormatter.unitsStyle = .brief - localFormatter.allowedUnits = [.hour, .minute] - - return localFormatter - }() - - if let runTimeTicks = self.runTimeTicks { - let text = formatter.string(from: Double(runTimeTicks / 10_000_000)) - return text - } - - return nil - } -} diff --git a/Jel/Models/UIScreenWidthExtension.swift b/Jel/Models/UIScreenWidthExtension.swift deleted file mode 100644 index 6f2f5de..0000000 --- a/Jel/Models/UIScreenWidthExtension.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// UIScreenWidthExtension.swift -// Jel -// -// Created by zerocool on 2/14/24. -// - -import Foundation -import UIKit - -extension UIScreen{ - static let screenWidth = UIScreen.main.bounds.size.width - static let screenHeight = UIScreen.main.bounds.size.height - static let screenSize = UIScreen.main.bounds.size -} diff --git a/Jel/Models/ViewExtensions.swift b/Jel/Models/ViewExtensions.swift deleted file mode 100644 index 7f54865..0000000 --- a/Jel/Models/ViewExtensions.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ViewExtensions.swift -// Jel -// -// Created by zerocool on 12/25/23. -// - -import SwiftUI - -extension View { - /// Applies the given transform if the given condition evaluates to `true`. - @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { - if condition() { - transform(self) - } else { - self - } - } -} - -extension View { - /// Applies an inverse mask to the given view - public func inverseMask(_ mask: Content) -> some View { - let inverseMask = mask - .foregroundStyle(.black) - .background(.white) - .compositingGroup() - .luminanceToAlpha() - return self.mask(inverseMask) - } -} diff --git a/Jel/Views/ContentView.swift b/Jel/Views/ContentView.swift index 6c5ec59..3983392 100644 --- a/Jel/Views/ContentView.swift +++ b/Jel/Views/ContentView.swift @@ -9,22 +9,39 @@ import SwiftUI import PulseUI struct ContentView: View { - @EnvironmentObject var jellyfinClient: JellyfinClientController - - @StateObject var settingsController: SettingsController = SettingsController.shared - @StateObject var authState: AuthStateController = AuthStateController.shared - @State var showingConsoleSheet: Bool = false + @State var isSignedIn: Bool = true + var body: some View { VStack { - if !authState.loggedIn { - SignInView() - } else { + if isSignedIn { NavigationStack { DashboardView() } + } else { + VStack { + Text("You are not currently signed into a Jellyfin instance.") + .padding() + Button { + // toggle logged in so that it invalidates isSignedIn + authState.loggedIn = true + authState.loggedIn = false + } label: { + Text("Sign in") + } + } } } + .sheet(isPresented: $isSignedIn.not) { + SignInView() + .interactiveDismissDisabled() + } + .onChange(of: authState.loggedIn, { + isSignedIn = authState.loggedIn + }) + .onChange(of: authState.loaded, { + isSignedIn = authState.loggedIn + }) } } diff --git a/Jel/Views/Dashboard/DashboardLibraryView.swift b/Jel/Views/Dashboard/DashboardLibraryView.swift index 0510113..dc5189e 100644 --- a/Jel/Views/Dashboard/DashboardLibraryView.swift +++ b/Jel/Views/Dashboard/DashboardLibraryView.swift @@ -11,7 +11,7 @@ import JellyfinKit struct DashboardLibraryView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var libraries: [BaseItemDto] = [] @State var loading: Bool = true diff --git a/Jel/Views/Dashboard/DashboardView.swift b/Jel/Views/Dashboard/DashboardView.swift index 5ad25f8..931a884 100644 --- a/Jel/Views/Dashboard/DashboardView.swift +++ b/Jel/Views/Dashboard/DashboardView.swift @@ -10,6 +10,8 @@ import JellyfinKit struct DashboardView: View { @State var showingSettingsSheet: Bool = false + @ObservedObject var authState: AuthStateController = AuthStateController.shared + @State var refresh: Bool = true var body: some View { ScrollView { @@ -37,6 +39,9 @@ struct DashboardView: View { .sheet(isPresented: $showingSettingsSheet) { SettingsView(showingSettingsView: $showingSettingsSheet) } + .onChange(of: authState.loggedIn, { + refresh = !authState.loggedIn + }) } } diff --git a/Jel/Views/Item/ItemGenresView.swift b/Jel/Views/Item/ItemGenresView.swift index 4e8321f..d775257 100644 --- a/Jel/Views/Item/ItemGenresView.swift +++ b/Jel/Views/Item/ItemGenresView.swift @@ -11,7 +11,7 @@ import JellyfinKit struct ItemGenresView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared var item: BaseItemDto @State var libraryItems: [BaseItemDto]? = [] diff --git a/Jel/Views/Item/Person/ItemPersonDetailView.swift b/Jel/Views/Item/Person/ItemPersonDetailView.swift index 5e0bddf..cfb222a 100644 --- a/Jel/Views/Item/Person/ItemPersonDetailView.swift +++ b/Jel/Views/Item/Person/ItemPersonDetailView.swift @@ -9,7 +9,7 @@ import SwiftUI import JellyfinKit struct ItemPersonDetailView: View { - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @EnvironmentObject var jellyfinClient: JellyfinClientController var person: BaseItemPerson diff --git a/Jel/Views/Item/Person/ItemPersonIconView.swift b/Jel/Views/Item/Person/ItemPersonIconView.swift index b839deb..5b4c33c 100644 --- a/Jel/Views/Item/Person/ItemPersonIconView.swift +++ b/Jel/Views/Item/Person/ItemPersonIconView.swift @@ -25,7 +25,7 @@ struct ItemPersonIconPlaceholderView: View { } struct ItemPersonIconView: View { - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @EnvironmentObject var jellyfinClient: JellyfinClientController var person: BaseItemPerson diff --git a/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift b/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift index ae473ef..2588515 100644 --- a/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift +++ b/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift @@ -10,7 +10,7 @@ import JellyfinKit struct ItemSeriesEpisodesView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared var item: BaseItemDto diff --git a/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift b/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift index a559174..d5c269b 100644 --- a/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift +++ b/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift @@ -12,7 +12,7 @@ struct ItemSeriesSeasonsView: View { var item: BaseItemDto @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var seriesItems: [BaseItemDto] = [] diff --git a/Jel/Views/Library/LibraryDetailView.swift b/Jel/Views/Library/LibraryDetailView.swift index c75cf39..dbc03d3 100644 --- a/Jel/Views/Library/LibraryDetailView.swift +++ b/Jel/Views/Library/LibraryDetailView.swift @@ -10,7 +10,7 @@ import JellyfinKit struct LibraryDetailView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var library: BaseItemDto @State var items: [BaseItemDto]? = [] diff --git a/Jel/Views/Settings/SettingsView.swift b/Jel/Views/Settings/SettingsView.swift index cff1fc3..8cd5a8a 100644 --- a/Jel/Views/Settings/SettingsView.swift +++ b/Jel/Views/Settings/SettingsView.swift @@ -11,9 +11,9 @@ import PulseUI struct SettingsView: View { @Binding var showingSettingsView: Bool - @StateObject var authState: AuthStateController = AuthStateController.shared - + @ObservedObject var authState: AuthStateController = AuthStateController.shared @ObservedObject var settingsController: SettingsController = SettingsController.shared + var body: some View { NavigationStack { Form { @@ -35,7 +35,11 @@ struct SettingsView: View { Button(role: .destructive) { authState.loggedIn = false + authState.authToken = nil authState.save() + + showingSettingsView.toggle() + settingsController.save() } label: { Text("Sign out") } diff --git a/Jel/Views/SignIn/AddServerView.swift b/Jel/Views/SignIn/AddServerView.swift index 40c2312..01d4a49 100644 --- a/Jel/Views/SignIn/AddServerView.swift +++ b/Jel/Views/SignIn/AddServerView.swift @@ -12,7 +12,7 @@ struct AddServerView: View { @ObservedObject var authState: AuthStateController = AuthStateController.shared @Binding var serverUrlIsValid: Bool - @State var serverUrlString: String = "http://" + @State var serverUrlString: String = "" @State var urlHasError: Bool = false @State var currentErrorMessage: String = "" @State var isLoading: Bool = false @@ -25,7 +25,7 @@ struct AddServerView: View { .font(.title) HStack { TextField(text: $serverUrlString) { - Text("http://jellyfin.example.com") + Text(verbatim: "https://jellyfin.example.com") } .keyboardType(.URL) .textContentType(.URL) @@ -102,7 +102,7 @@ struct AddServerView: View { } -#Preview { - AddServerView(serverUrlIsValid: .constant(false)) - -} +//#Preview { +// AddServerView(serverUrlIsValid: .constant(false)) +// +//} diff --git a/Jel/Views/SignIn/SignInToServerView.swift b/Jel/Views/SignIn/SignInToServerView.swift index 259e54d..18907a5 100644 --- a/Jel/Views/SignIn/SignInToServerView.swift +++ b/Jel/Views/SignIn/SignInToServerView.swift @@ -10,7 +10,7 @@ import SwiftUI struct SignInToServerView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var username: String = "" @State var password: String = "" diff --git a/Jel/Views/SignIn/SignInView.swift b/Jel/Views/SignIn/SignInView.swift index 729b34b..f3e2675 100644 --- a/Jel/Views/SignIn/SignInView.swift +++ b/Jel/Views/SignIn/SignInView.swift @@ -11,9 +11,8 @@ import PulseUI struct SignInView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @StateObject var authState: AuthStateController = AuthStateController.shared + @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var serverUrlIsValid: Bool = false - @State var showingConsoleSheet: Bool = false var body: some View { NavigationStack { @@ -40,6 +39,6 @@ struct SignInView: View { } } -#Preview { - SignInView() -} +//#Preview { +// SignInView() +//} -- cgit v1.2.3