Implement signIn flow
This commit is contained in:
parent
02fc87fe25
commit
fbb3756746
@ -19,6 +19,9 @@
|
|||||||
3D9063E72B279A320063DD2A /* JelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E62B279A320063DD2A /* JelUITests.swift */; };
|
3D9063E72B279A320063DD2A /* JelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E62B279A320063DD2A /* JelUITests.swift */; };
|
||||||
3D9063E92B279A320063DD2A /* JelUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */; };
|
3D9063E92B279A320063DD2A /* JelUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */; };
|
||||||
3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3D9064582B27E4C70063DD2A /* JellyfinKit */; };
|
3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3D9064582B27E4C70063DD2A /* JellyfinKit */; };
|
||||||
|
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 */; };
|
||||||
3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */; };
|
3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@ -69,6 +72,9 @@
|
|||||||
3D9063E22B279A320063DD2A /* JelUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JelUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
3D9063E62B279A320063DD2A /* JelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelUITests.swift; sourceTree = "<group>"; };
|
||||||
3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
3D9063E82B279A320063DD2A /* JelUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JelUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||||
|
3D91FDC82B28C62800919017 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
|
||||||
|
3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInToServerView.swift; sourceTree = "<group>"; };
|
||||||
|
3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinDateFormatter.swift; sourceTree = "<group>"; };
|
||||||
3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinClientController.swift; sourceTree = "<group>"; };
|
3DF1ED3D2B282836000AD8EA /* JellyfinClientController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinClientController.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@ -102,7 +108,7 @@
|
|||||||
3D1015D72B27F54A00F5C29A /* Views */ = {
|
3D1015D72B27F54A00F5C29A /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3D1015D82B27F57400F5C29A /* AddServerView.swift */,
|
3D91FDC52B28C28900919017 /* SignIn */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -120,6 +126,7 @@
|
|||||||
3D1015E02B27FE5700F5C29A /* Models */ = {
|
3D1015E02B27FE5700F5C29A /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -187,6 +194,16 @@
|
|||||||
path = JelUITests;
|
path = JelUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
3D91FDC52B28C28900919017 /* SignIn */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3D91FDC82B28C62800919017 /* SignInView.swift */,
|
||||||
|
3D1015D82B27F57400F5C29A /* AddServerView.swift */,
|
||||||
|
3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */,
|
||||||
|
);
|
||||||
|
path = SignIn;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -328,7 +345,10 @@
|
|||||||
3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */,
|
3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */,
|
||||||
3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */,
|
3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */,
|
||||||
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */,
|
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */,
|
||||||
|
3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */,
|
||||||
3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */,
|
3D1015DC2B27F5D300F5C29A /* Model.xcdatamodeld in Sources */,
|
||||||
|
3D91FDC92B28C62800919017 /* SignInView.swift in Sources */,
|
||||||
|
3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */,
|
||||||
3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */,
|
3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -9,11 +9,10 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var authState: AuthStateController
|
@ObservedObject var authState: AuthStateController
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
if !authState.loggedIn {
|
if !authState.loggedIn {
|
||||||
AddServerView(authState: authState)
|
SignInView(authState: authState)
|
||||||
} else {
|
} else {
|
||||||
Text("Logged in")
|
Text("Logged in")
|
||||||
Button("Log out") {
|
Button("Log out") {
|
||||||
|
@ -14,6 +14,8 @@ class AuthStateController: ObservableObject {
|
|||||||
|
|
||||||
private let defaults = UserDefaults.standard
|
private let defaults = UserDefaults.standard
|
||||||
|
|
||||||
|
static let shared = AuthStateController()
|
||||||
|
|
||||||
init(loggedIn: Bool = false, serverUrl: URL? = nil, authToken: String? = nil) {
|
init(loggedIn: Bool = false, serverUrl: URL? = nil, authToken: String? = nil) {
|
||||||
self.loggedIn = loggedIn
|
self.loggedIn = loggedIn
|
||||||
self.serverUrl = serverUrl
|
self.serverUrl = serverUrl
|
||||||
|
@ -9,22 +9,74 @@ import Foundation
|
|||||||
import Get
|
import Get
|
||||||
import JellyfinKit
|
import JellyfinKit
|
||||||
|
|
||||||
class JellyfinClientController {
|
struct AuthHeaders: Codable {
|
||||||
let api: APIClient
|
var Client: String
|
||||||
|
var Device: String
|
||||||
|
var DeviceId: String
|
||||||
|
var Version: String
|
||||||
|
var Token: String
|
||||||
|
}
|
||||||
|
|
||||||
init(serverUrl: URL) {
|
enum JellyfinClientError: Error {
|
||||||
self.api = APIClient(
|
case badResponseCode
|
||||||
baseURL: serverUrl
|
}
|
||||||
)
|
|
||||||
|
extension AuthHeaders {
|
||||||
|
func format() -> String {
|
||||||
|
return "MediaBrowser Client=\(self.Client), Device=\(self.Device), DeviceId=\(self.DeviceId), Version=\(self.Version), Token=\(self.Token)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JellyfinClientController: ObservableObject {
|
||||||
|
private var api: APIClient
|
||||||
|
|
||||||
|
private var authHeaders: AuthHeaders
|
||||||
|
private var authState: AuthStateController
|
||||||
|
|
||||||
|
init(authHeaders: AuthHeaders, serverUrl: URL? = nil, authState: AuthStateController = AuthStateController.shared) {
|
||||||
|
self.authHeaders = authHeaders
|
||||||
|
self.authState = authState
|
||||||
|
|
||||||
|
self.api = APIClient(baseURL: serverUrl)
|
||||||
|
self.setUrl(url: serverUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setToken(token: String) {
|
||||||
|
self.authHeaders.Token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUrl(url: URL?) {
|
||||||
|
if url == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.api = APIClient(baseURL: url, {
|
||||||
|
$0.sessionConfiguration.httpAdditionalHeaders = ["Authorization": self.authHeaders.format()]
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
|
||||||
|
$0.decoder = decoder
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func isJellyfinServer() async -> Bool {
|
func isJellyfinServer() async -> Bool {
|
||||||
let request = Paths.getPublicUsers
|
let request = Paths.getPublicUsers
|
||||||
do {
|
do {
|
||||||
try await api.send(request)
|
let res = try await api.send(request)
|
||||||
|
if res.statusCode != 200 {
|
||||||
|
throw JellyfinClientError.badResponseCode
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signIn(username: String, pw: String) async throws {
|
||||||
|
let request = Paths.authenticateUserByName(AuthenticateUserByName(pw: pw, username: username))
|
||||||
|
let res = try await self.api.send(request)
|
||||||
|
self.authState.loggedIn = true
|
||||||
|
self.authState.authToken = res.value.accessToken
|
||||||
|
self.authState.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,26 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct JelApp: App {
|
struct JelApp: App {
|
||||||
let datamodelController = DatamodelController.shared
|
let datamodelController = DatamodelController.shared
|
||||||
let authStateController = AuthStateController()
|
let authStateController = AuthStateController.shared
|
||||||
|
|
||||||
|
let jellyfinClientController = JellyfinClientController(authHeaders: AuthHeaders(
|
||||||
|
Client: "Jel",
|
||||||
|
Device: UIDevice.current.systemName,
|
||||||
|
DeviceId: UIDevice.current.identifierForVendor!.uuidString,
|
||||||
|
Version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0",
|
||||||
|
Token: ""))
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView(authState: authStateController)
|
ContentView(authState: authStateController)
|
||||||
.environment(\.managedObjectContext,
|
.environment(\.managedObjectContext,
|
||||||
datamodelController.container.viewContext)
|
datamodelController.container.viewContext)
|
||||||
|
.environmentObject(jellyfinClientController)
|
||||||
.task {
|
.task {
|
||||||
authStateController.load()
|
authStateController.load()
|
||||||
|
if authStateController.serverUrl != nil {
|
||||||
|
jellyfinClientController.setUrl(url: authStateController.serverUrl!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
Jel/Models/JellyfinDateFormatter.swift
Normal file
33
Jel/Models/JellyfinDateFormatter.swift
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// JellyfinDateFormatter.swift
|
||||||
|
// Jel
|
||||||
|
//
|
||||||
|
// Created by zerocool on 12/12/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// from: https://stackoverflow.com/a/46458771
|
||||||
|
extension Formatter {
|
||||||
|
static let iso8601withFractionalSeconds: ISO8601DateFormatter = {
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
static let iso8601: ISO8601DateFormatter = {
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.formatOptions = [.withInternetDateTime]
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSONDecoder.DateDecodingStrategy {
|
||||||
|
static let iso8601withFractionalSeconds = custom {
|
||||||
|
let container = try $0.singleValueContainer()
|
||||||
|
let string = try container.decode(String.self)
|
||||||
|
if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,14 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AddServerView: View {
|
struct AddServerView: View {
|
||||||
|
@EnvironmentObject var jellyfinClient: JellyfinClientController
|
||||||
@ObservedObject var authState: AuthStateController
|
@ObservedObject var authState: AuthStateController
|
||||||
|
@Binding var serverUrlIsValid: Bool
|
||||||
|
|
||||||
@State var serverUrlString: String = ""
|
@State var serverUrlString: String = "http://"
|
||||||
@State var urlHasError: Bool = false
|
@State var urlHasError: Bool = false
|
||||||
@State var currentErrorMessage: String = ""
|
@State var currentErrorMessage: String = ""
|
||||||
@State var loading: Bool = false
|
@State var isLoading: Bool = false
|
||||||
|
|
||||||
@FocusState var serverUrlIsFocused: Bool
|
@FocusState var serverUrlIsFocused: Bool
|
||||||
|
|
||||||
@ -22,10 +24,8 @@ struct AddServerView: View {
|
|||||||
Text("Connect to a server")
|
Text("Connect to a server")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
TextField(text: $serverUrlString) {
|
TextField(text: $serverUrlString) {
|
||||||
Text("http://jellyfin.example.com")
|
Text("http://jellyfin.example.com")
|
||||||
.foregroundStyle(.placeholder)
|
|
||||||
}
|
}
|
||||||
.keyboardType(.URL)
|
.keyboardType(.URL)
|
||||||
.textContentType(.URL)
|
.textContentType(.URL)
|
||||||
@ -33,11 +33,6 @@ struct AddServerView: View {
|
|||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.focused($serverUrlIsFocused)
|
.focused($serverUrlIsFocused)
|
||||||
.onChange(of: serverUrlIsFocused) {
|
|
||||||
if serverUrlIsFocused {
|
|
||||||
urlHasError = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
Task {
|
Task {
|
||||||
await checkServerUrl()
|
await checkServerUrl()
|
||||||
@ -45,7 +40,7 @@ struct AddServerView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if !loading {
|
if !isLoading {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
Task {
|
Task {
|
||||||
await checkServerUrl()
|
await checkServerUrl()
|
||||||
@ -55,14 +50,14 @@ struct AddServerView: View {
|
|||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
.disabled(urlHasError)
|
|
||||||
} else {
|
} else {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
.padding()
|
.padding([.leading, .trailing])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.disabled(isLoading)
|
||||||
|
|
||||||
if urlHasError {
|
if urlHasError {
|
||||||
Text(currentErrorMessage)
|
Text(currentErrorMessage)
|
||||||
@ -73,13 +68,16 @@ struct AddServerView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkServerUrl() async {
|
func checkServerUrl() async {
|
||||||
loading = true
|
isLoading = true
|
||||||
serverUrlIsFocused = false
|
serverUrlIsFocused = false
|
||||||
if isValidUrl(data: serverUrlString) {
|
if isValidUrl(data: serverUrlString) {
|
||||||
let url = URL(string: serverUrlString)!
|
let url = URL(string: serverUrlString)!
|
||||||
if await JellyfinClientController(serverUrl: url).isJellyfinServer() {
|
jellyfinClient.setUrl(url: url)
|
||||||
|
if await jellyfinClient.isJellyfinServer() {
|
||||||
authState.serverUrl = url
|
authState.serverUrl = url
|
||||||
|
authState.save()
|
||||||
urlHasError = false
|
urlHasError = false
|
||||||
|
serverUrlIsValid = true
|
||||||
} else {
|
} else {
|
||||||
urlHasError = true
|
urlHasError = true
|
||||||
currentErrorMessage = "Server not responding"
|
currentErrorMessage = "Server not responding"
|
||||||
@ -90,7 +88,7 @@ struct AddServerView: View {
|
|||||||
currentErrorMessage = "Invalid url"
|
currentErrorMessage = "Invalid url"
|
||||||
}
|
}
|
||||||
|
|
||||||
loading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidUrl(data: String) -> Bool {
|
func isValidUrl(data: String) -> Bool {
|
||||||
@ -105,5 +103,6 @@ struct AddServerView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AddServerView(authState: AuthStateController())
|
AddServerView(authState: AuthStateController(), serverUrlIsValid: .constant(false))
|
||||||
|
|
||||||
}
|
}
|
79
Jel/Views/SignIn/SignInToServerView.swift
Normal file
79
Jel/Views/SignIn/SignInToServerView.swift
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// SignInToServerView.swift
|
||||||
|
// Jel
|
||||||
|
//
|
||||||
|
// Created by zerocool on 12/12/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SignInToServerView: View {
|
||||||
|
@EnvironmentObject var jellyfinClient: JellyfinClientController
|
||||||
|
@ObservedObject var authState: AuthStateController
|
||||||
|
|
||||||
|
@State var username: String = ""
|
||||||
|
@State var password: String = ""
|
||||||
|
|
||||||
|
@State var isLoading: Bool = false
|
||||||
|
@State var hasError: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("Sign in")
|
||||||
|
.font(.title)
|
||||||
|
TextField(text: $username) {
|
||||||
|
Text("Username")
|
||||||
|
}
|
||||||
|
.textContentType(.username)
|
||||||
|
|
||||||
|
SecureField(text: $password) {
|
||||||
|
Text("Password")
|
||||||
|
}
|
||||||
|
.textContentType(.password)
|
||||||
|
.onSubmit {
|
||||||
|
Task {
|
||||||
|
await logInToServer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isLoading {
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await logInToServer()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Sign in")
|
||||||
|
}
|
||||||
|
.disabled(username.isEmpty || password.isEmpty)
|
||||||
|
} else {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasError {
|
||||||
|
Text("Unable to sign in")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.textInputAutocapitalization(.never)
|
||||||
|
.disabled(isLoading)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logInToServer() async {
|
||||||
|
isLoading = true
|
||||||
|
hasError = false
|
||||||
|
do {
|
||||||
|
try await jellyfinClient.signIn(username: username, pw: password)
|
||||||
|
} catch {
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SignInToServerView(authState: AuthStateController())
|
||||||
|
}
|
42
Jel/Views/SignIn/SignInView.swift
Normal file
42
Jel/Views/SignIn/SignInView.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// SignInView.swift
|
||||||
|
// Jel
|
||||||
|
//
|
||||||
|
// Created by zerocool on 12/12/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SignInView: View {
|
||||||
|
@EnvironmentObject var jellyfinClient: JellyfinClientController
|
||||||
|
@ObservedObject var authState: AuthStateController
|
||||||
|
@State var serverUrlIsValid: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
AddServerView(authState: authState, serverUrlIsValid: $serverUrlIsValid)
|
||||||
|
.navigationDestination(isPresented: $serverUrlIsValid) {
|
||||||
|
SignInToServerView(authState: authState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
Task {
|
||||||
|
await checkLoadedServerUrl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLoadedServerUrl() async {
|
||||||
|
if authState.serverUrl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if await jellyfinClient.isJellyfinServer() {
|
||||||
|
serverUrlIsValid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SignInView(authState: AuthStateController())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user