From 5ed7d2551ccaefc19d9e0b934ef17583dca202b2 Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Tue, 12 Dec 2023 01:22:43 -0500 Subject: [PATCH] Add server page --- Info.plist | 11 ++ Jel.xcodeproj/project.pbxproj | 95 +++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 25 ++++ .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 + .../xcschemes/xcschememanagement.plist | 10 ++ Jel/ContentView.swift | 19 ++- Jel/Controllers/AuthStateController.swift | 38 ++++++ Jel/Controllers/DatamodelController.swift | 25 ++++ .../JellyfinClientController.swift | 30 +++++ Jel/JelApp.swift | 13 ++- .../Model.xcdatamodel/contents | 2 + Jel/Views/AddServerView.swift | 109 ++++++++++++++++++ 12 files changed, 374 insertions(+), 9 deletions(-) create mode 100644 Info.plist create mode 100644 Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 Jel/Controllers/AuthStateController.swift create mode 100644 Jel/Controllers/DatamodelController.swift create mode 100644 Jel/Controllers/JellyfinClientController.swift create mode 100644 Jel/Model.xcdatamodeld/Model.xcdatamodel/contents create mode 100644 Jel/Views/AddServerView.swift diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..6a6654d --- /dev/null +++ b/Info.plist @@ -0,0 +1,11 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index 09f7665..85470fb 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015D82B27F57400F5C29A /* AddServerView.swift */; }; + 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 */; }; 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 */; }; @@ -14,6 +18,8 @@ 3D9063DD2B279A320063DD2A /* JelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063DC2B279A320063DD2A /* JelTests.swift */; }; 3D9063E72B279A320063DD2A /* JelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E62B279A320063DD2A /* JelUITests.swift */; }; 3D9063E92B279A320063DD2A /* JelUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */; }; + 3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3D9064582B27E4C70063DD2A /* JellyfinKit */; }; + 3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,7 +39,25 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3D9064142B279A450063DD2A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 3D1015D42B27F49000F5C29A /* JellyfinKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = JellyfinKit; path = ../JellyfinKit; sourceTree = ""; }; + 3D1015D82B27F57400F5C29A /* AddServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddServerView.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; 3D9063CC2B279A310063DD2A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -45,6 +69,8 @@ 3D9063E22B279A320063DD2A /* JelUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JelUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3D9063E62B279A320063DD2A /* JelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelUITests.swift; sourceTree = ""; }; 3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelUITestsLaunchTests.swift; sourceTree = ""; }; + 3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinClientController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -52,6 +78,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,12 +99,39 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3D1015D72B27F54A00F5C29A /* Views */ = { + isa = PBXGroup; + children = ( + 3D1015D82B27F57400F5C29A /* AddServerView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 3D1015DF2B27F8EE00F5C29A /* Controllers */ = { + isa = PBXGroup; + children = ( + 3D1015DD2B27F79900F5C29A /* DatamodelController.swift */, + 3D1015E32B28000E00F5C29A /* AuthStateController.swift */, + 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 3D1015E02B27FE5700F5C29A /* Models */ = { + isa = PBXGroup; + children = ( + ); + path = Models; + sourceTree = ""; + }; 3D9063BE2B279A310063DD2A = { isa = PBXGroup; children = ( + 3DC0E5802B2832B9001CCE96 /* Info.plist */, 3D9063C92B279A310063DD2A /* Jel */, 3D9063DB2B279A320063DD2A /* JelTests */, 3D9063E52B279A320063DD2A /* JelUITests */, + 3D1015D42B27F49000F5C29A /* JellyfinKit */, 3D9063C82B279A310063DD2A /* Products */, ); sourceTree = ""; @@ -95,11 +149,15 @@ 3D9063C92B279A310063DD2A /* Jel */ = { isa = PBXGroup; children = ( + 3D1015E02B27FE5700F5C29A /* Models */, + 3D1015DF2B27F8EE00F5C29A /* Controllers */, + 3D1015D72B27F54A00F5C29A /* Views */, 3D9063CA2B279A310063DD2A /* JelApp.swift */, 3D9063CC2B279A310063DD2A /* ContentView.swift */, 3D9063CE2B279A320063DD2A /* Assets.xcassets */, 3D9063D02B279A320063DD2A /* Jel.entitlements */, 3D9063D12B279A320063DD2A /* Preview Content */, + 3D1015DA2B27F5D300F5C29A /* Model.xcdatamodeld */, ); path = Jel; sourceTree = ""; @@ -139,12 +197,16 @@ 3D9063C32B279A310063DD2A /* Sources */, 3D9063C42B279A310063DD2A /* Frameworks */, 3D9063C52B279A310063DD2A /* Resources */, + 3D9064142B279A450063DD2A /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Jel; + packageProductDependencies = ( + 3D9064582B27E4C70063DD2A /* JellyfinKit */, + ); productName = Jel; productReference = 3D9063C72B279A310063DD2A /* Jel.app */; productType = "com.apple.product-type.application"; @@ -217,6 +279,8 @@ Base, ); mainGroup = 3D9063BE2B279A310063DD2A; + packageReferences = ( + ); productRefGroup = 3D9063C82B279A310063DD2A /* Products */; projectDirPath = ""; projectRoot = ""; @@ -259,8 +323,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3D1015DE2B27F79900F5C29A /* DatamodelController.swift in Sources */, 3D9063CD2B279A310063DD2A /* ContentView.swift in Sources */, + 3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */, + 3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */, 3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */, + 3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */, + 3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -353,6 +422,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -407,6 +477,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; @@ -414,6 +485,7 @@ 3D9063ED2B279A320063DD2A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Jel/Jel.entitlements; @@ -424,6 +496,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -452,6 +525,7 @@ 3D9063EE2B279A320063DD2A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Jel/Jel.entitlements; @@ -462,6 +536,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -617,6 +692,26 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3D9064582B27E4C70063DD2A /* JellyfinKit */ = { + isa = XCSwiftPackageProductDependency; + productName = JellyfinKit; + }; +/* End XCSwiftPackageProductDependency section */ + +/* Begin XCVersionGroup section */ + 3D1015DA2B27F5D300F5C29A /* Model.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 3D1015DB2B27F5D300F5C29A /* Model.xcdatamodel */, + ); + currentVersion = 3D1015DB2B27F5D300F5C29A /* Model.xcdatamodel */; + path = Model.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 3D9063BF2B279A310063DD2A /* Project object */; } diff --git a/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..e005bb5 --- /dev/null +++ b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "Get", + "repositoryURL": "https://github.com/kean/Get", + "state": { + "branch": null, + "revision": "12830cc64f31789ae6f4352d2d51d03a25fc3741", + "version": "2.1.6" + } + }, + { + "package": "URLQueryEncoder", + "repositoryURL": "https://github.com/CreateAPI/URLQueryEncoder", + "state": { + "branch": null, + "revision": "4ce950479707ea109f229d7230ec074a133b15d7", + "version": "0.2.1" + } + } + ] + }, + "version": 1 +} diff --git a/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..ab4ca7e --- /dev/null +++ b/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcschemes/xcschememanagement.plist b/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcschemes/xcschememanagement.plist index df6f3c3..27d21d3 100644 --- a/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Jel.xcodeproj/xcuserdata/zerocool.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,16 @@ orderHint 0 + JellyfinClient.xcscheme_^#shared#^_ + + orderHint + 1 + + JellyfinKit.xcscheme_^#shared#^_ + + orderHint + 2 + diff --git a/Jel/ContentView.swift b/Jel/ContentView.swift index 69609c1..2c388be 100644 --- a/Jel/ContentView.swift +++ b/Jel/ContentView.swift @@ -8,17 +8,24 @@ import SwiftUI struct ContentView: View { - var body: some View { + @ObservedObject var authState: AuthStateController + + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + if !authState.loggedIn { + AddServerView(authState: authState) + } else { + Text("Logged in") + Button("Log out") { + authState.loggedIn = false + authState.save() + } + } } .padding() } } #Preview { - ContentView() + ContentView(authState: AuthStateController()) } diff --git a/Jel/Controllers/AuthStateController.swift b/Jel/Controllers/AuthStateController.swift new file mode 100644 index 0000000..1629556 --- /dev/null +++ b/Jel/Controllers/AuthStateController.swift @@ -0,0 +1,38 @@ +// +// AuthStateController.swift +// Jel +// +// Created by zerocool on 12/11/23. +// + +import Foundation + +class AuthStateController: ObservableObject { + @Published var loggedIn: Bool + @Published var serverUrl: URL? + @Published var authToken: String? + + private let defaults = UserDefaults.standard + + init(loggedIn: Bool = false, serverUrl: URL? = nil, authToken: String? = nil) { + self.loggedIn = loggedIn + self.serverUrl = serverUrl + self.authToken = authToken + } + + func load() { + self.loggedIn = defaults.bool(forKey: "AuthState_loggedIn") + if let oldServerUrl = defaults.url(forKey: "AuthState_serverUrl") { + self.serverUrl = oldServerUrl + } + if let oldAuthToken = defaults.string(forKey: "AuthState_authToken") { + self.authToken = oldAuthToken + } + } + + func save() { + defaults.set(self.loggedIn, forKey: "AuthState_loggedIn") + defaults.set(self.serverUrl, forKey: "AuthState_serverUrl") + defaults.set(self.authToken, forKey: "AuthState_authToken") + } +} diff --git a/Jel/Controllers/DatamodelController.swift b/Jel/Controllers/DatamodelController.swift new file mode 100644 index 0000000..4beb173 --- /dev/null +++ b/Jel/Controllers/DatamodelController.swift @@ -0,0 +1,25 @@ +// +// DatamodelController.swift +// Jel +// +// Created by zerocool on 12/11/23. +// + +import CoreData + +struct DatamodelController { + static let shared = DatamodelController() + + let container: NSPersistentContainer + + init() { + container = NSPersistentContainer(name: "Model") + + container.loadPersistentStores(completionHandler: {(storeDescription, error) in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + container.viewContext.automaticallyMergesChangesFromParent = true + } +} diff --git a/Jel/Controllers/JellyfinClientController.swift b/Jel/Controllers/JellyfinClientController.swift new file mode 100644 index 0000000..343efe1 --- /dev/null +++ b/Jel/Controllers/JellyfinClientController.swift @@ -0,0 +1,30 @@ +// +// JellyfinClientController.swift +// Jel +// +// Created by zerocool on 12/12/23. +// + +import Foundation +import Get +import JellyfinKit + +class JellyfinClientController { + let api: APIClient + + init(serverUrl: URL) { + self.api = APIClient( + baseURL: serverUrl + ) + } + + func isJellyfinServer() async -> Bool { + let request = Paths.getPublicUsers + do { + try await api.send(request) + } catch { + return false + } + return true + } +} diff --git a/Jel/JelApp.swift b/Jel/JelApp.swift index ddff76b..d70e444 100644 --- a/Jel/JelApp.swift +++ b/Jel/JelApp.swift @@ -9,9 +9,16 @@ import SwiftUI @main struct JelApp: App { - var body: some Scene { - WindowGroup { - ContentView() + let datamodelController = DatamodelController.shared + let authStateController = AuthStateController() + var body: some Scene { + WindowGroup { + ContentView(authState: authStateController) + .environment(\.managedObjectContext, + datamodelController.container.viewContext) + .task { + authStateController.load() } } + } } diff --git a/Jel/Model.xcdatamodeld/Model.xcdatamodel/contents b/Jel/Model.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 0000000..38ef213 --- /dev/null +++ b/Jel/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Jel/Views/AddServerView.swift b/Jel/Views/AddServerView.swift new file mode 100644 index 0000000..beab5e7 --- /dev/null +++ b/Jel/Views/AddServerView.swift @@ -0,0 +1,109 @@ +// +// AddServerView.swift +// Jel +// +// Created by zerocool on 12/11/23. +// + +import SwiftUI + +struct AddServerView: View { + @ObservedObject var authState: AuthStateController + + @State var serverUrlString: String = "" + @State var urlHasError: Bool = false + @State var currentErrorMessage: String = "" + @State var loading: Bool = false + + @FocusState var serverUrlIsFocused: Bool + + var body: some View { + VStack { + Text("Connect to a server") + .font(.title) + HStack { + + TextField(text: $serverUrlString) { + Text("http://jellyfin.example.com") + .foregroundStyle(.placeholder) + } + .keyboardType(.URL) + .textContentType(.URL) + .textFieldStyle(.roundedBorder) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .focused($serverUrlIsFocused) + .onChange(of: serverUrlIsFocused) { + if serverUrlIsFocused { + urlHasError = false + } + } + .onSubmit { + Task { + await checkServerUrl() + } + } + + + if !loading { + Button(action: { + Task { + await checkServerUrl() + } + }) { + Label("Connect", systemImage: "arrow.right") + .labelStyle(.iconOnly) + } + .buttonStyle(.bordered) + .disabled(urlHasError) + } else { + ProgressView() + .progressViewStyle(.circular) + .padding() + } + } + .padding() + + if urlHasError { + Text(currentErrorMessage) + .font(.callout) + .foregroundStyle(.red) + } + } + } + + func checkServerUrl() async { + loading = true + serverUrlIsFocused = false + if isValidUrl(data: serverUrlString) { + let url = URL(string: serverUrlString)! + if await JellyfinClientController(serverUrl: url).isJellyfinServer() { + authState.serverUrl = url + urlHasError = false + } else { + urlHasError = true + currentErrorMessage = "Server not responding" + } + + } else { + urlHasError = true + currentErrorMessage = "Invalid url" + } + + loading = false + } + + func isValidUrl(data: String) -> Bool { + if let url = URL(string: data) { + if UIApplication.shared.canOpenURL(url) { + return true + } + } + return false + } + +} + +#Preview { + AddServerView(authState: AuthStateController()) +}