Revert "Merge updated onboarding view"
This commit is contained in:
@@ -9,8 +9,8 @@ import SwiftUI
|
||||
|
||||
|
||||
struct MainView: View {
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@StateObject var userSettings: UserSettings = UserSettings()
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
@StateObject var viewModel = MainViewModel()
|
||||
|
||||
@State private var selectedCategory: Category? = nil
|
||||
@State private var showEditView: Bool = false
|
||||
@@ -37,10 +37,6 @@ struct MainView: View {
|
||||
.padding(7)
|
||||
}
|
||||
|
||||
if viewModel.categories.isEmpty {
|
||||
Text("Pull to refresh.")
|
||||
}
|
||||
|
||||
// Categories
|
||||
ForEach(viewModel.categories) { category in
|
||||
if category.recipe_count != 0 {
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
//
|
||||
// OnboardingView.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 15.09.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct OnboardingView: View {
|
||||
@State var selectedTab: Int = 0
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
WelcomeTab().tag(0)
|
||||
LoginTab().tag(1)
|
||||
}
|
||||
.tabViewStyle(.page)
|
||||
.background(
|
||||
selectedTab == 1 ? Color.nextcloudBlue.ignoresSafeArea() : Color(uiColor: .systemBackground).ignoresSafeArea()
|
||||
)
|
||||
.animation(.easeInOut, value: selectedTab)
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeTab: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image("cookbook-icon")
|
||||
.resizable()
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
Text("Thank you for downloading")
|
||||
.font(.headline)
|
||||
Text("Cookbook Client")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Spacer()
|
||||
Text("This application is an open source effort. If you're interested in suggesting or contributing new features, or you encounter any problems, please use the support link or visit the GitHub repository in the app settings.")
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.fontDesign(.rounded)
|
||||
}
|
||||
}
|
||||
|
||||
protocol LoginStage {
|
||||
func next() -> Self
|
||||
func previous() -> Self
|
||||
}
|
||||
|
||||
enum LoginMethod {
|
||||
case v2, token
|
||||
}
|
||||
|
||||
enum TokenLoginStage: LoginStage {
|
||||
case serverAddress, userName, appToken, validate
|
||||
|
||||
func next() -> TokenLoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .userName
|
||||
case .userName: return .appToken
|
||||
case .appToken: return .validate
|
||||
case .validate: return .validate
|
||||
}
|
||||
}
|
||||
|
||||
func previous() -> TokenLoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .serverAddress
|
||||
case .userName: return .serverAddress
|
||||
case .appToken: return .userName
|
||||
case .validate: return .appToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct LoginTab: View {
|
||||
@State var loginMethod: LoginMethod = .v2
|
||||
|
||||
// Login error alert
|
||||
@State var showAlert: Bool = false
|
||||
@State var alertMessage: String = "Error: Could not connect to server."
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
Picker("Login Method", selection: $loginMethod) {
|
||||
Text("Nextcloud Login").tag(LoginMethod.v2)
|
||||
Text("App Token Login").tag(LoginMethod.token)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.foregroundColor(.white)
|
||||
.padding()
|
||||
if loginMethod == .token {
|
||||
TokenLoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
}
|
||||
else if loginMethod == .v2 {
|
||||
V2LoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.fontDesign(.rounded)
|
||||
.padding()
|
||||
.alert(alertMessage, isPresented: $showAlert) {
|
||||
Button("Ok", role: .cancel) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
struct LoginLabel: View {
|
||||
let text: String
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginTextField: View {
|
||||
var example: String
|
||||
@Binding var text: String
|
||||
@State var color: Color = .white
|
||||
|
||||
var body: some View {
|
||||
TextField(example, text: $text)
|
||||
.textFieldStyle(.plain)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.foregroundColor(color)
|
||||
.accentColor(color)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(color, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
//
|
||||
// TokenLoginView.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 21.11.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct TokenLoginView: View {
|
||||
@Binding var showAlert: Bool
|
||||
@Binding var alertMessage: String
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
@AppStorage("serverAddress") var serverAddress = ""
|
||||
@AppStorage("username") var userName = ""
|
||||
@AppStorage("token") var token = ""
|
||||
@AppStorage("onboarding") var onboarding = false
|
||||
|
||||
// TextField handling
|
||||
enum Field {
|
||||
case server
|
||||
case username
|
||||
case token
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
LoginLabel(text: "Server address")
|
||||
LoginTextField(example: "e.g.: example.com", text: $serverAddress)
|
||||
.focused($focusedField, equals: .server)
|
||||
.textContentType(.URL)
|
||||
.submitLabel(.next)
|
||||
.padding(.bottom)
|
||||
|
||||
LoginLabel(text: "User name")
|
||||
LoginTextField(example: "username", text: $userName)
|
||||
.focused($focusedField, equals: .username)
|
||||
.textContentType(.username)
|
||||
.submitLabel(.next)
|
||||
.padding(.bottom)
|
||||
|
||||
|
||||
LoginLabel(text: "App Token")
|
||||
LoginTextField(example: "can be generated in security settings of your nextcloud", text: $token)
|
||||
.focused($focusedField, equals: .token)
|
||||
.textContentType(.password)
|
||||
.submitLabel(.join)
|
||||
HStack{
|
||||
Spacer()
|
||||
Button {
|
||||
Task {
|
||||
if await loginCheck(nextcloudLogin: false) {
|
||||
onboarding = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Submit")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onSubmit {
|
||||
switch focusedField {
|
||||
case .server:
|
||||
focusedField = .username
|
||||
case .username:
|
||||
focusedField = .token
|
||||
default:
|
||||
print("Attempting to log in ...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loginCheck(nextcloudLogin: Bool) async -> Bool {
|
||||
if serverAddress == "" {
|
||||
alertMessage = "Please enter a server address!"
|
||||
showAlert = true
|
||||
return false
|
||||
} else if !nextcloudLogin && (userName == "" || token == "") {
|
||||
alertMessage = "Please enter a user name and app token!"
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
let headerFields = [
|
||||
HeaderField.ocsRequest(value: true),
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .GET,
|
||||
path: .CATEGORIES,
|
||||
headerFields: headerFields,
|
||||
authenticate: true
|
||||
)
|
||||
|
||||
var (data, error): (Data?, Error?) = (nil, nil)
|
||||
do {
|
||||
let loginString = "\(userName):\(token)"
|
||||
let loginData = loginString.data(using: String.Encoding.utf8)!
|
||||
let authString = loginData.base64EncodedString()
|
||||
(data, error) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: "https://\(serverAddress)/index.php/apps/cookbook/api/v1/",
|
||||
authString: authString
|
||||
)
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
}
|
||||
guard let data = data else {
|
||||
alertMessage = "Login failed. Please check your inputs."
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
if let testRequest: [Category] = JSONDecoder.safeDecode(data) {
|
||||
print("validationResponse: \(testRequest)")
|
||||
return true
|
||||
}
|
||||
alertMessage = "Login failed. Please check your inputs and internet connection."
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
//
|
||||
// V2LoginView.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 21.11.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum V2LoginStage: LoginStage {
|
||||
case serverAddress, login, validate
|
||||
|
||||
func next() -> V2LoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .login
|
||||
case .login: return .validate
|
||||
case .validate: return .validate
|
||||
}
|
||||
}
|
||||
|
||||
func previous() -> V2LoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .serverAddress
|
||||
case .login: return .serverAddress
|
||||
case .validate: return .login
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CollapsibleView<T: View>: View {
|
||||
@State var titleColor: Color = .white
|
||||
@State var content: () -> T
|
||||
@State var title: () -> Text
|
||||
|
||||
@State var isCollapsed: Bool = true
|
||||
@State var rotationAngle: Double = -90
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Button {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
isCollapsed.toggle()
|
||||
if isCollapsed {
|
||||
rotationAngle += 90
|
||||
} else {
|
||||
rotationAngle -= 90
|
||||
}
|
||||
}
|
||||
rotationAngle = isCollapsed ? -90 : 0
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "chevron.down")
|
||||
.bold()
|
||||
.rotationEffect(Angle(degrees: rotationAngle))
|
||||
title()
|
||||
}.foregroundStyle(titleColor)
|
||||
}
|
||||
|
||||
if !isCollapsed {
|
||||
content()
|
||||
.padding(.top, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct V2LoginView: View {
|
||||
@Binding var showAlert: Bool
|
||||
@Binding var alertMessage: String
|
||||
|
||||
@State var loginStage: V2LoginStage = .serverAddress
|
||||
@State var loginRequest: LoginV2Request? = nil
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
@AppStorage("serverAddress") var serverAddress = ""
|
||||
@AppStorage("username") var userName = ""
|
||||
@AppStorage("token") var token = ""
|
||||
@AppStorage("onboarding") var onboarding = true
|
||||
|
||||
// TextField handling
|
||||
enum Field {
|
||||
case server
|
||||
case username
|
||||
case token
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
LoginLabel(text: "Server address")
|
||||
.padding()
|
||||
LoginTextField(example: "e.g.: example.com", text: $serverAddress, color: loginStage == .serverAddress ? .white : .secondary)
|
||||
.focused($focusedField, equals: .server)
|
||||
.textContentType(.URL)
|
||||
.submitLabel(.done)
|
||||
.padding([.bottom, .horizontal])
|
||||
.onSubmit {
|
||||
withAnimation(.easeInOut) {
|
||||
loginStage = loginStage.next()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
} title: {
|
||||
Text("Show help")
|
||||
.foregroundColor(.white)
|
||||
.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 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()
|
||||
|
||||
Button {
|
||||
// fetch login v2 response
|
||||
Task {
|
||||
guard let res = await fetchLoginV2Response() else {
|
||||
alertMessage = "Login failed. Please login via the browser and try again."
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
print("Login successfull for user \(res.loginName)!")
|
||||
self.userName = res.loginName
|
||||
self.token = res.appPassword
|
||||
self.onboarding = false
|
||||
}
|
||||
} label: {
|
||||
Text("Validate")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.disabled(loginRequest == nil ? true : false)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendLoginV2Request() async {
|
||||
let hostPath = "https://\(serverAddress)"
|
||||
let headerFields: [HeaderField] = [
|
||||
//HeaderField.ocsRequest(value: true),
|
||||
//HeaderField.accept(value: .JSON)
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .POST,
|
||||
path: .LOGINV2REQ,
|
||||
headerFields: headerFields
|
||||
)
|
||||
do {
|
||||
let (data, _): (Data?, Error?) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: hostPath,
|
||||
authString: nil
|
||||
)
|
||||
|
||||
guard let data = data else { return }
|
||||
print("Data: \(data)")
|
||||
let loginReq: LoginV2Request? = JSONDecoder.safeDecode(data)
|
||||
self.loginRequest = loginReq
|
||||
} catch {
|
||||
print("Could not establish communication with the server.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fetchLoginV2Response() async -> LoginV2Response? {
|
||||
guard let loginRequest = loginRequest else { return nil }
|
||||
let headerFields = [
|
||||
HeaderField.ocsRequest(value: true),
|
||||
HeaderField.accept(value: .JSON),
|
||||
HeaderField.contentType(value: .FORM)
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .POST,
|
||||
path: .NONE,
|
||||
headerFields: headerFields,
|
||||
body: "token=\(loginRequest.poll.token)".data(using: .utf8),
|
||||
authenticate: false
|
||||
)
|
||||
|
||||
var (data, error): (Data?, Error?) = (nil, nil)
|
||||
do {
|
||||
(data, error) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: loginRequest.poll.endpoint,
|
||||
authString: nil
|
||||
)
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
}
|
||||
guard let data = data else { return nil }
|
||||
if let loginRes: LoginV2Response = JSONDecoder.safeDecode(data) {
|
||||
return loginRes
|
||||
}
|
||||
print("Could not decode.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
355
Nextcloud Cookbook iOS Client/Views/OnboardingView.swift
Normal file
355
Nextcloud Cookbook iOS Client/Views/OnboardingView.swift
Normal file
@@ -0,0 +1,355 @@
|
||||
//
|
||||
// OnboardingView.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 15.09.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct OnboardingView: View {
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
@State var selectedTab: Int = 0
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
WelcomeTab().tag(0)
|
||||
LoginTab(userSettings: userSettings).tag(1)
|
||||
}
|
||||
.tabViewStyle(.page)
|
||||
.background(
|
||||
selectedTab == 1 ? Color.nextcloudBlue.ignoresSafeArea() : Color(uiColor: .systemBackground).ignoresSafeArea()
|
||||
)
|
||||
.animation(.easeInOut, value: selectedTab)
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeTab: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image("cookbook-icon")
|
||||
.resizable()
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
Text("Thank you for downloading")
|
||||
.font(.headline)
|
||||
Text("Cookbook Client")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Spacer()
|
||||
Text("This application is an open source effort. If you're interested in suggesting or contributing new features, or you encounter any problems, please use the support link or visit the GitHub repository in the app settings.")
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.fontDesign(.rounded)
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginTab: View {
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
|
||||
// Login flow
|
||||
enum LoginMethod {
|
||||
case v2, token
|
||||
}
|
||||
@State var selectedLoginMethod: LoginMethod = .v2
|
||||
@State var loginRequest: LoginV2Request? = nil
|
||||
|
||||
// Login error alert
|
||||
@State var showAlert: Bool = false
|
||||
@State var alertMessage: String = "Error: Could not connect to server."
|
||||
|
||||
// TextField handling
|
||||
enum Field {
|
||||
case server
|
||||
case username
|
||||
case token
|
||||
}
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
Picker("Login Method", selection: $selectedLoginMethod) {
|
||||
Text("Nextcloud Login").tag(LoginMethod.v2)
|
||||
Text("App Token Login").tag(LoginMethod.token)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.foregroundColor(.white)
|
||||
if selectedLoginMethod == .token {
|
||||
LoginLabel(text: "Server address")
|
||||
LoginTextField(example: "e.g.: example.com", text: $userSettings.serverAddress)
|
||||
.focused($focusedField, equals: .server)
|
||||
.textContentType(.URL)
|
||||
.submitLabel(.next)
|
||||
.padding(.bottom)
|
||||
|
||||
LoginLabel(text: "User name")
|
||||
LoginTextField(example: "username", text: $userSettings.username)
|
||||
.focused($focusedField, equals: .username)
|
||||
.textContentType(.username)
|
||||
.submitLabel(.next)
|
||||
.padding(.bottom)
|
||||
|
||||
|
||||
LoginLabel(text: "App Token")
|
||||
LoginTextField(example: "can be generated in security settings of your nextcloud", text: $userSettings.token)
|
||||
.focused($focusedField, equals: .token)
|
||||
.textContentType(.password)
|
||||
.submitLabel(.join)
|
||||
HStack{
|
||||
Spacer()
|
||||
Button {
|
||||
Task {
|
||||
if await loginCheck(nextcloudLogin: false) {
|
||||
userSettings.onboarding = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Submit")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
else if selectedLoginMethod == .v2 {
|
||||
LoginLabel(text: "Server address")
|
||||
LoginTextField(example: "e.g.: example.com", text: $userSettings.serverAddress)
|
||||
.focused($focusedField, equals: .server)
|
||||
.textContentType(.URL)
|
||||
.submitLabel(.done)
|
||||
.padding(.bottom)
|
||||
.onSubmit {
|
||||
if userSettings.serverAddress == "" { 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("Entering the server address will open a web browser. Please follow the login instructions provided there. If the browser does not open, click the link 'Open in browser'\nAfter a successfull login, return to this application and press 'Validate'.")
|
||||
.font(.subheadline)
|
||||
.padding(.bottom)
|
||||
.foregroundStyle(.white)
|
||||
Button {
|
||||
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
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Open in browser")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
HStack{
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// fetch login v2 response
|
||||
Task {
|
||||
guard let res = await fetchLoginV2Response() else {
|
||||
alertMessage = "Login failed. Please login via the browser and try again."
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
print("Login successfull for user \(res.loginName)!")
|
||||
userSettings.username = res.loginName
|
||||
userSettings.token = res.appPassword
|
||||
userSettings.onboarding = false
|
||||
}
|
||||
} label: {
|
||||
Text("Validate")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.disabled(loginRequest == nil ? true : false)
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.onSubmit {
|
||||
switch focusedField {
|
||||
case .server:
|
||||
focusedField = .username
|
||||
case .username:
|
||||
focusedField = .token
|
||||
default:
|
||||
print("Attempting to log in ...")
|
||||
}
|
||||
}
|
||||
.fontDesign(.rounded)
|
||||
.padding()
|
||||
.alert(alertMessage, isPresented: $showAlert) {
|
||||
Button("Ok", role: .cancel) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendLoginV2Request() async {
|
||||
let hostPath = "https://\(userSettings.serverAddress)"
|
||||
let headerFields: [HeaderField] = [
|
||||
//HeaderField.ocsRequest(value: true),
|
||||
//HeaderField.accept(value: .JSON)
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .POST,
|
||||
path: .LOGINV2REQ,
|
||||
headerFields: headerFields
|
||||
)
|
||||
do {
|
||||
let (data, _): (Data?, Error?) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: hostPath,
|
||||
authString: nil
|
||||
)
|
||||
|
||||
guard let data = data else { return }
|
||||
print("Data: \(data)")
|
||||
let loginReq: LoginV2Request? = JSONDecoder.safeDecode(data)
|
||||
self.loginRequest = loginReq
|
||||
} catch {
|
||||
print("Could not establish communication with the server.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fetchLoginV2Response() async -> LoginV2Response? {
|
||||
guard let loginRequest = loginRequest else { return nil }
|
||||
let headerFields = [
|
||||
HeaderField.ocsRequest(value: true),
|
||||
HeaderField.accept(value: .JSON),
|
||||
HeaderField.contentType(value: .FORM)
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .POST,
|
||||
path: .NONE,
|
||||
headerFields: headerFields,
|
||||
body: "token=\(loginRequest.poll.token)".data(using: .utf8),
|
||||
authenticate: false
|
||||
)
|
||||
|
||||
var (data, error): (Data?, Error?) = (nil, nil)
|
||||
do {
|
||||
(data, error) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: loginRequest.poll.endpoint,
|
||||
authString: nil
|
||||
)
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
}
|
||||
guard let data = data else { return nil }
|
||||
if let loginRes: LoginV2Response = JSONDecoder.safeDecode(data) {
|
||||
return loginRes
|
||||
}
|
||||
print("Could not decode.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func loginCheck(nextcloudLogin: Bool) async -> Bool {
|
||||
if userSettings.serverAddress == "" {
|
||||
alertMessage = "Please enter a server address!"
|
||||
showAlert = true
|
||||
return false
|
||||
} else if !nextcloudLogin && (userSettings.username == "" || userSettings.token == "") {
|
||||
alertMessage = "Please enter a user name and app token!"
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
let headerFields = [
|
||||
HeaderField.ocsRequest(value: true),
|
||||
]
|
||||
let request = RequestWrapper.customRequest(
|
||||
method: .GET,
|
||||
path: .CATEGORIES,
|
||||
headerFields: headerFields,
|
||||
authenticate: true
|
||||
)
|
||||
|
||||
var (data, error): (Data?, Error?) = (nil, nil)
|
||||
do {
|
||||
let loginString = "\(userSettings.username):\(userSettings.token)"
|
||||
let loginData = loginString.data(using: String.Encoding.utf8)!
|
||||
let authString = loginData.base64EncodedString()
|
||||
(data, error) = try await NetworkHandler.sendHTTPRequest(
|
||||
request,
|
||||
hostPath: "https://\(userSettings.serverAddress)/index.php/apps/cookbook/api/v1/",
|
||||
authString: authString
|
||||
)
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
}
|
||||
guard let data = data else {
|
||||
alertMessage = "Login failed. Please check your inputs."
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
if let testRequest: [Category] = JSONDecoder.safeDecode(data) {
|
||||
print("validationResponse: \(testRequest)")
|
||||
return true
|
||||
}
|
||||
alertMessage = "Login failed. Please check your inputs and internet connection."
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginLabel: View {
|
||||
let text: String
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginTextField: View {
|
||||
var example: String
|
||||
@Binding var text: String
|
||||
|
||||
var body: some View {
|
||||
TextField(example, text: $text)
|
||||
.textFieldStyle(.plain)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.foregroundColor(.white)
|
||||
.accentColor(.white)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user