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())
+}