diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate index 1f00a6c..cbd1a85 100644 Binary files a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate and b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Nextcloud Cookbook iOS Client/Data/UserSettings.swift b/Nextcloud Cookbook iOS Client/Data/UserSettings.swift index 7aac9cd..e5b9245 100644 --- a/Nextcloud Cookbook iOS Client/Data/UserSettings.swift +++ b/Nextcloud Cookbook iOS Client/Data/UserSettings.swift @@ -37,6 +37,12 @@ class UserSettings: ObservableObject { } } + @Published var serverProtocol: String { + didSet { + UserDefaults.standard.set(serverProtocol, forKey: "serverProtocol") + } + } + @Published var onboarding: Bool { didSet { UserDefaults.standard.set(onboarding, forKey: "onboarding") @@ -102,6 +108,7 @@ class UserSettings: ObservableObject { self.token = UserDefaults.standard.object(forKey: "token") as? String ?? "" self.authString = UserDefaults.standard.object(forKey: "authString") as? String ?? "" self.serverAddress = UserDefaults.standard.object(forKey: "serverAddress") as? String ?? "" + self.serverProtocol = UserDefaults.standard.object(forKey: "serverProtocol") as? String ?? "https://" self.onboarding = UserDefaults.standard.object(forKey: "onboarding") as? Bool ?? true self.defaultCategory = UserDefaults.standard.object(forKey: "defaultCategory") as? String ?? "" self.language = UserDefaults.standard.object(forKey: "language") as? String ?? SupportedLanguage.DEVICE.rawValue diff --git a/Nextcloud Cookbook iOS Client/Localizable.xcstrings b/Nextcloud Cookbook iOS Client/Localizable.xcstrings index 8a68a9a..3e2e5fb 100644 --- a/Nextcloud Cookbook iOS Client/Localizable.xcstrings +++ b/Nextcloud Cookbook iOS Client/Localizable.xcstrings @@ -710,6 +710,9 @@ } } } + }, + "Copy Link" : { + }, "Could not establish a connection to the server. The action will be retried upon reconnection." : { "localizations" : { @@ -952,6 +955,9 @@ } } } + }, + "e.g.: example.com" : { + }, "Edit" : { "localizations" : { @@ -1152,6 +1158,7 @@ } }, "If the login button does not open your browser, copy the following link and paste it in your browser manually:" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1172,6 +1179,9 @@ } } } + }, + "If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually." : { + }, "If you are interested in contributing to this project or simply wish to review its source code, we encourage you to visit the GitHub repository for this application." : { "localizations" : { @@ -1574,8 +1584,12 @@ } } } + }, + "Make sure to enter the server address in the form 'example.com', or \n':'\n when a non-standard port is used." : { + }, "Make sure to enter the server address in the form 'example.com'. Currently, only servers using the 'https' protocol are supported." : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2828,9 +2842,6 @@ } } } - }, - "Use a non-standard port" : { - }, "Validate" : { "localizations" : { diff --git a/Nextcloud Cookbook iOS Client/Network/CookbookApi/ApiRequest.swift b/Nextcloud Cookbook iOS Client/Network/CookbookApi/ApiRequest.swift index 67498f5..8056637 100644 --- a/Nextcloud Cookbook iOS Client/Network/CookbookApi/ApiRequest.swift +++ b/Nextcloud Cookbook iOS Client/Network/CookbookApi/ApiRequest.swift @@ -9,8 +9,6 @@ import Foundation import OSLog struct ApiRequest { - /// The server address, e.g. https://example.com - let serverAddress: String let path: String let method: RequestMethod let authString: String? @@ -21,14 +19,12 @@ struct ApiRequest { let cookbookPath = "/index.php/apps/cookbook" init( - serverAdress: String, path: String, method: RequestMethod, authString: String? = nil, headerFields: [HeaderField] = [], body: Data? = nil ) { - self.serverAddress = serverAdress self.method = method self.path = path self.headerFields = headerFields @@ -40,7 +36,7 @@ struct ApiRequest { Logger.network.debug("\(method.rawValue) \(path) sending ...") // Prepare URL - let urlString = "https://" + serverAddress + cookbookPath + path + let urlString = UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + cookbookPath + path print("Full path: \(urlString)") //Logger.network.debug("Full path: \(urlString)") guard let urlStringSanitized = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return (nil, .unknownError) } diff --git a/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApi.swift b/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApi.swift index deb6c99..7baed46 100644 --- a/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApi.swift +++ b/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApi.swift @@ -13,20 +13,17 @@ import UIKit protocol CookbookApi { /// Not implemented yet. static func importRecipe( - from serverAdress: String, auth: String, data: Data ) async -> (RecipeDetail?, NetworkError?) /// Get either the full image or a thumbnail sized version. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - id: The according recipe id. /// - size: The size of the image. /// - Returns: The image of the recipe with the specified id. A NetworkError if the request fails, otherwise nil. static func getImage( - from serverAdress: String, auth: String, id: Int, size: RecipeImage.RecipeImageSize @@ -34,91 +31,75 @@ protocol CookbookApi { /// Get all recipes. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - Returns: A list of all recipes. static func getRecipes( - from serverAdress: String, auth: String ) async -> ([Recipe]?, NetworkError?) /// Create a new recipe. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - Returns: A NetworkError if the request fails. Nil otherwise. static func createRecipe( - from serverAdress: String, auth: String, recipe: RecipeDetail ) async -> (NetworkError?) /// Get the recipe with the specified id. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - id: The recipe id. /// - Returns: The recipe if it exists. A NetworkError if the request fails. static func getRecipe( - from serverAdress: String, auth: String, id: Int ) async -> (RecipeDetail?, NetworkError?) /// Update an existing recipe with new entries. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - id: The recipe id. /// - Returns: A NetworkError if the request fails. Nil otherwise. static func updateRecipe( - from serverAdress: String, - auth: String, + auth: String, recipe: RecipeDetail ) async -> (NetworkError?) /// Delete the recipe with the specified id. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - id: The recipe id. /// - Returns: A NetworkError if the request fails. Nil otherwise. static func deleteRecipe( - from serverAdress: String, auth: String, id: Int ) async -> (NetworkError?) /// Get all categories. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - Returns: A list of categories. A NetworkError if the request fails. static func getCategories( - from serverAdress: String, auth: String ) async -> ([Category]?, NetworkError?) /// Get all recipes of a specified category. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - categoryName: The category name. /// - Returns: A list of recipes. A NetworkError if the request fails. static func getCategory( - from serverAdress: String, auth: String, named categoryName: String ) async -> ([Recipe]?, NetworkError?) /// Rename an existing category. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - categoryName: The name of the category to be renamed. /// - newName: The new category name. /// - Returns: A NetworkError if the request fails. static func renameCategory( - from serverAdress: String, auth: String, named categoryName: String, newName: String @@ -126,63 +107,51 @@ protocol CookbookApi { /// Get all keywords/tags. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - Returns: A list of tag strings. A NetworkError if the request fails. static func getTags( - from serverAdress: String, auth: String ) async -> ([RecipeKeyword]?, NetworkError?) /// Get all recipes tagged with the specified keyword. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - keyword: The keyword. /// - Returns: A list of recipes tagged with the specified keyword. A NetworkError if the request fails. static func getRecipesTagged( - from serverAdress: String, auth: String, keyword: String ) async -> ([Recipe]?, NetworkError?) /// Get the servers api version. /// - Parameters: - /// - serverAdress: Server address in the format https://example.com. /// - auth: Server authentication string. /// - Returns: A NetworkError if the request fails. static func getApiVersion( - from serverAdress: String, auth: String ) async -> (NetworkError?) /// Trigger a reindexing action on the server. /// - Parameters: - /// - serverAdress: Server address in the format. https://example.com /// - auth: Server authentication string /// - Returns: A NetworkError if the request fails. static func postReindex( - from serverAdress: String, auth: String ) async -> (NetworkError?) /// Get the current configuration of the Cookbook server application. /// - Parameters: - /// - serverAdress: Server address in the format. https://example.com /// - auth: Server authentication string /// - Returns: A NetworkError if the request fails. static func getConfig( - from serverAdress: String, auth: String ) async -> (NetworkError?) /// Set the current configuration of the Cookbook server application. /// - Parameters: - /// - serverAdress: Server address in the format. https://example.com /// - auth: Server authentication string /// - Returns: A NetworkError if the request fails. static func postConfig( - from serverAdress: String, auth: String ) async -> (NetworkError?) } diff --git a/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApiV1.swift b/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApiV1.swift index 2f8c7ec..e4f4a0e 100644 --- a/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApiV1.swift +++ b/Nextcloud Cookbook iOS Client/Network/CookbookApi/CookbookApiV1.swift @@ -10,9 +10,8 @@ import UIKit class CookbookApiV1: CookbookApi { - static func importRecipe(from serverAdress: String, auth: String, data: Data) async -> (RecipeDetail?, NetworkError?) { + static func importRecipe(auth: String, data: Data) async -> (RecipeDetail?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/import", method: .POST, authString: auth, @@ -24,10 +23,9 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func getImage(from serverAdress: String, auth: String, id: Int, size: RecipeImage.RecipeImageSize) async -> (UIImage?, NetworkError?) { + static func getImage(auth: String, id: Int, size: RecipeImage.RecipeImageSize) async -> (UIImage?, NetworkError?) { let imageSize = (size == .FULL ? "full" : "thumb") let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes/\(id)/image?size=\(imageSize)", method: .GET, authString: auth, @@ -39,9 +37,8 @@ class CookbookApiV1: CookbookApi { return (UIImage(data: data), error) } - static func getRecipes(from serverAdress: String, auth: String) async -> ([Recipe]?, NetworkError?) { + static func getRecipes(auth: String) async -> ([Recipe]?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes", method: .GET, authString: auth, @@ -53,13 +50,12 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func createRecipe(from serverAdress: String, auth: String, recipe: RecipeDetail) async -> (NetworkError?) { + static func createRecipe(auth: String, recipe: RecipeDetail) async -> (NetworkError?) { guard let recipeData = JSONEncoder.safeEncode(recipe) else { return .dataError } let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes", method: .POST, authString: auth, @@ -82,9 +78,8 @@ class CookbookApiV1: CookbookApi { return nil } - static func getRecipe(from serverAdress: String, auth: String, id: Int) async -> (RecipeDetail?, NetworkError?) { + static func getRecipe(auth: String, id: Int) async -> (RecipeDetail?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes/\(id)", method: .GET, authString: auth, @@ -96,12 +91,11 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func updateRecipe(from serverAdress: String, auth: String, recipe: RecipeDetail) async -> (NetworkError?) { + static func updateRecipe(auth: String, recipe: RecipeDetail) async -> (NetworkError?) { guard let recipeData = JSONEncoder.safeEncode(recipe) else { return .dataError } let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes/\(recipe.id)", method: .PUT, authString: auth, @@ -124,9 +118,8 @@ class CookbookApiV1: CookbookApi { return nil } - static func deleteRecipe(from serverAdress: String, auth: String, id: Int) async -> (NetworkError?) { + static func deleteRecipe(auth: String, id: Int) async -> (NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/recipes/\(id)", method: .DELETE, authString: auth, @@ -138,9 +131,8 @@ class CookbookApiV1: CookbookApi { return nil } - static func getCategories(from serverAdress: String, auth: String) async -> ([Category]?, NetworkError?) { + static func getCategories(auth: String) async -> ([Category]?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/categories", method: .GET, authString: auth, @@ -152,9 +144,8 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func getCategory(from serverAdress: String, auth: String, named categoryName: String) async -> ([Recipe]?, NetworkError?) { + static func getCategory(auth: String, named categoryName: String) async -> ([Recipe]?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/category/\(categoryName)", method: .GET, authString: auth, @@ -166,9 +157,8 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func renameCategory(from serverAdress: String, auth: String, named categoryName: String, newName: String) async -> (NetworkError?) { + static func renameCategory(auth: String, named categoryName: String, newName: String) async -> (NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/category/\(categoryName)", method: .PUT, authString: auth, @@ -180,9 +170,8 @@ class CookbookApiV1: CookbookApi { return nil } - static func getTags(from serverAdress: String, auth: String) async -> ([RecipeKeyword]?, NetworkError?) { + static func getTags(auth: String) async -> ([RecipeKeyword]?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/keywords", method: .GET, authString: auth, @@ -194,9 +183,8 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func getRecipesTagged(from serverAdress: String, auth: String, keyword: String) async -> ([Recipe]?, NetworkError?) { + static func getRecipesTagged(auth: String, keyword: String) async -> ([Recipe]?, NetworkError?) { let request = ApiRequest( - serverAdress: serverAdress, path: "/api/v1/tags/\(keyword)", method: .GET, authString: auth, @@ -208,19 +196,19 @@ class CookbookApiV1: CookbookApi { return (JSONDecoder.safeDecode(data), nil) } - static func getApiVersion(from serverAdress: String, auth: String) async -> (NetworkError?) { + static func getApiVersion(auth: String) async -> (NetworkError?) { return .none } - static func postReindex(from serverAdress: String, auth: String) async -> (NetworkError?) { + static func postReindex(auth: String) async -> (NetworkError?) { return .none } - static func getConfig(from serverAdress: String, auth: String) async -> (NetworkError?) { + static func getConfig(auth: String) async -> (NetworkError?) { return .none } - static func postConfig(from serverAdress: String, auth: String) async -> (NetworkError?) { + static func postConfig(auth: String) async -> (NetworkError?) { return .none } } diff --git a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift index da72632..87b5555 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift @@ -49,7 +49,6 @@ import UIKit */ func getCategories() async { let (categories, _) = await api.getCategories( - from: userSettings.serverAddress, auth: userSettings.authString ) if let categories = categories { @@ -93,7 +92,6 @@ import UIKit func getServer(store: Bool = false) async -> Bool { let (recipes, _) = await api.getCategory( - from: userSettings.serverAddress, auth: userSettings.authString, named: categoryString ) @@ -156,7 +154,6 @@ import UIKit */ func getRecipes() async -> [Recipe] { let (recipes, error) = await api.getRecipes( - from: userSettings.serverAddress, auth: userSettings.authString ) if let recipes = recipes { @@ -197,7 +194,6 @@ import UIKit func getServer() async -> RecipeDetail? { let (recipe, error) = await api.getRecipe( - from: userSettings.serverAddress, auth: userSettings.authString, id: id ) @@ -291,7 +287,6 @@ import UIKit func getServer() async -> UIImage? { let (image, _) = await api.getImage( - from: userSettings.serverAddress, auth: userSettings.authString, id: id, size: size @@ -368,7 +363,6 @@ import UIKit func getServer() async -> [RecipeKeyword]? { let (tags, _) = await api.getTags( - from: userSettings.serverAddress, auth: userSettings.authString ) return tags @@ -423,7 +417,6 @@ import UIKit */ func deleteRecipe(withId id: Int, categoryName: String) async -> RequestAlert? { let (error) = await api.deleteRecipe( - from: userSettings.serverAddress, auth: userSettings.authString, id: id ) @@ -455,7 +448,6 @@ import UIKit */ func checkServerConnection() async -> Bool { let (categories, _) = await api.getCategories( - from: userSettings.serverAddress, auth: userSettings.authString ) if let categories = categories { @@ -485,13 +477,11 @@ import UIKit var error: NetworkError? = nil if createNew { error = await api.createRecipe( - from: userSettings.serverAddress, auth: userSettings.authString, recipe: recipeDetail ) } else { error = await api.updateRecipe( - from: userSettings.serverAddress, auth: userSettings.authString, recipe: recipeDetail ) @@ -505,7 +495,6 @@ import UIKit func importRecipe(url: String) async -> (RecipeDetail?, RequestAlert?) { guard let data = JSONEncoder.safeEncode(RecipeImportRequest(url: url)) else { return (nil, .REQUEST_DROPPED) } let (recipeDetail, error) = await api.importRecipe( - from: userSettings.serverAddress, auth: userSettings.authString, data: data ) diff --git a/Nextcloud Cookbook iOS Client/Views/Onboarding/OnboardingView.swift b/Nextcloud Cookbook iOS Client/Views/Onboarding/OnboardingView.swift index 02aff48..98a73e1 100644 --- a/Nextcloud Cookbook iOS Client/Views/Onboarding/OnboardingView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Onboarding/OnboardingView.swift @@ -181,11 +181,8 @@ struct LoginTextField: View { struct ServerAddressField: View { - @Binding var addressString: String - @State var serverAddress: String = "" + @ObservedObject var userSettings = UserSettings.shared @State var serverProtocol: ServerProtocol = .https - @State var serverPort: String = "" - @State var useNonStandardPort: Bool = false enum ServerProtocol: String { case https="https://", http="http://" @@ -205,22 +202,26 @@ struct ServerAddressField: View { }.pickerStyle(.menu) .tint(.white) .font(.headline) + .onChange(of: serverProtocol) { color in + userSettings.serverProtocol = color.rawValue + } + + TextField("e.g.: example.com", text: $userSettings.serverAddress) + .textFieldStyle(.plain) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + .foregroundStyle(.white) + .padding() + .background( + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.white.opacity(0.2)) + ) - LoginTextField(example: "e.g.: example.com", text: $serverAddress) - } - - Toggle("Use a non-standard port", isOn: $useNonStandardPort) - .tint(.white.opacity(0.2)) - .foregroundStyle(.white) - .font(.headline) - .padding(.top) - if useNonStandardPort { - LoginTextField(example: "e.g.: 80", text: $serverPort) } LoginLabel(text: "Full server address") .padding(.top) - Text(createServerAddressString()) + Text(userSettings.serverProtocol + userSettings.serverAddress) .foregroundColor(.white) .padding(.vertical, 5) } @@ -230,26 +231,13 @@ struct ServerAddressField: View { .stroke(.white, lineWidth: 2) .foregroundColor(.clear) ) - }.animation(.easeInOut, value: useNonStandardPort) - } - - func createServerAddressString() -> String { - if useNonStandardPort && serverPort != "" { - addressString = serverProtocol.rawValue + serverAddress + ":" + serverPort - } else { - addressString = serverProtocol.rawValue + serverAddress } - return addressString } } struct ServerAddressField_Preview: PreviewProvider { static var previews: some View { - ServerAddressField(addressString: .constant(""), - serverAddress: "example.com", - serverProtocol: .https, - serverPort: "80", - useNonStandardPort: true) + ServerAddressField() .previewLayout(.sizeThatFits) .padding() .background(Color.nextcloudBlue) diff --git a/Nextcloud Cookbook iOS Client/Views/Onboarding/TokenLoginView.swift b/Nextcloud Cookbook iOS Client/Views/Onboarding/TokenLoginView.swift index d112349..89efc58 100644 --- a/Nextcloud Cookbook iOS Client/Views/Onboarding/TokenLoginView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Onboarding/TokenLoginView.swift @@ -26,14 +26,9 @@ struct TokenLoginView: View { var body: some View { VStack(alignment: .leading) { - /*LoginLabel(text: "Server address") - LoginTextField(example: "e.g.: example.com", text: $userSettings.serverAddress) - .focused($focusedField, equals: .server) - .textContentType(.URL) - .submitLabel(.next) + ServerAddressField() .padding(.bottom) - */ - ServerAddressField(addressString: $userSettings.serverAddress) + LoginLabel(text: "User name") BorderedLoginTextField(example: "username", text: $userSettings.username) .focused($focusedField, equals: .username) diff --git a/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift b/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift index f974a7d..e3133d3 100644 --- a/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift @@ -9,11 +9,10 @@ import Foundation import SwiftUI enum V2LoginStage: LoginStage { - case serverAddress, login, validate + case login, validate func next() -> V2LoginStage { switch self { - case .serverAddress: return .login case .login: return .validate case .validate: return .validate } @@ -21,8 +20,7 @@ enum V2LoginStage: LoginStage { func previous() -> V2LoginStage { switch self { - case .serverAddress: return .serverAddress - case .login: return .serverAddress + case .login: return .login case .validate: return .login } } @@ -34,9 +32,8 @@ struct V2LoginView: View { @Binding var showAlert: Bool @Binding var alertMessage: String - @State var loginStage: V2LoginStage = .serverAddress + @State var loginStage: V2LoginStage = .login @State var loginRequest: LoginV2Request? = nil - @FocusState private var focusedField: Field? @State var userSettings = UserSettings.shared @@ -50,27 +47,14 @@ struct V2LoginView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - /*LoginLabel(text: "Server address") - .padding() - LoginTextField(example: "e.g.: example.com", text: $userSettings.serverAddress, color: loginStage == .serverAddress ? .white : .secondary) - .focused($focusedField, equals: .server) - .textContentType(.URL) - .submitLabel(.done) - .padding([.bottom, .horizontal]) - .onSubmit { - withAnimation(.easeInOut) { - loginStage = loginStage.next() - } - } - */ - ServerAddressField(addressString: $userSettings.serverAddress) + ServerAddressField() CollapsibleView { VStack(alignment: .leading) { - Text("Make sure to enter the server address in the form 'example.com'. Currently, only servers using the 'https' protocol are supported.") - if let loginRequest = loginRequest { - Text("If the login button does not open your browser, copy the following link and paste it in your browser manually:") - Text(loginRequest.login) - } + Text("Make sure to enter the server address in the form 'example.com', or \n':'\n when a non-standard port is used.") + .padding(.bottom) + Text("The 'Login' button will open a web browser. Please follow the login instructions provided there.\nAfter a successful login, return to this application and press 'Validate'.") + .padding(.bottom) + Text("If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually.") } } title: { Text("Show help") @@ -78,43 +62,45 @@ struct V2LoginView: View { .font(.headline) }.padding() - if loginStage == .login || loginStage == .validate { - Text("The 'Login' button will open a web browser. Please follow the login instructions provided there.\nAfter a successful login, return to this application and press 'Validate'.") - .font(.subheadline) - .foregroundStyle(.white) - .padding() - } - HStack { - if loginStage == .login || loginStage == .validate { - Button { - if userSettings.serverAddress == "" { - alertMessage = "Please enter a valid server address." - showAlert = true - return - } - - Task { - await sendLoginV2Request() - if let loginRequest = loginRequest { - await UIApplication.shared.open(URL(string: loginRequest.login)!) - } else { - alertMessage = "Unable to reach server. Please check your server address and internet connection." - showAlert = true - } - } - loginStage = loginStage.next() - } label: { - Text("Login") - .foregroundColor(.white) - .font(.headline) - .padding() - .background( - RoundedRectangle(cornerRadius: 10) - .stroke(Color.white, lineWidth: 2) - .foregroundColor(.clear) - ) - }.padding() + if loginRequest != nil { + Button("Copy Link") { + UIPasteboard.general.string = loginRequest!.login } + .font(.headline) + .foregroundStyle(.white) + .padding() + } + + HStack { + Button { + if userSettings.serverAddress == "" { + alertMessage = "Please enter a valid server address." + showAlert = true + return + } + + Task { + await sendLoginV2Request() + if let loginRequest = loginRequest { + await UIApplication.shared.open(URL(string: loginRequest.login)!) + } else { + alertMessage = "Unable to reach server. Please check your server address and internet connection." + showAlert = true + } + } + loginStage = loginStage.next() + } label: { + Text("Login") + .foregroundColor(.white) + .font(.headline) + .padding() + .background( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.white, lineWidth: 2) + .foregroundColor(.clear) + ) + }.padding() + if loginStage == .validate { Spacer()