Files
Nextcloud-Cookbook-iOS/Nextcloud Cookbook iOS Client/Views/Onboarding/TokenLoginView.swift
Hendrik Hogertz 7c824b492e Modernize networking layer and fix category navigation and recipe list bugs
Network layer:
- Replace static CookbookApi protocol with instance-based CookbookApiProtocol
  using async/throws instead of tuple returns
- Refactor ApiRequest to use URLComponents for proper URL encoding, replace
  print statements with OSLog, and return typed NetworkError cases
- Add structured NetworkError variants (httpError, connectionError, etc.)
- Remove global cookbookApi constant in favor of injected dependency on AppState
- Delete unused RecipeEditViewModel, RecipeScraper, and Scraper playground

Data & model fixes:
- Add custom Decodable for RecipeDetail with safe fallbacks for malformed JSON
- Make Category Hashable/Equatable use only `name` so NavigationSplitView
  selection survives category refreshes with updated recipe_count
- Return server-assigned ID from uploadRecipe so new recipes get their ID
  before the post-upload refresh block executes

View updates:
- Refresh both old and new category recipe lists after upload when category
  changes, mapping empty recipeCategory to "*" for uncategorized recipes
- Raise deployment target to iOS 18, adopt new SwiftUI API conventions
- Clean up alerts, onboarding views, and settings

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:47:28 +01:00

104 lines
3.1 KiB
Swift

//
// TokenLoginView.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 21.11.23.
//
import Foundation
import OSLog
import SwiftUI
struct TokenLoginView: View {
@Binding var showAlert: Bool
@Binding var alertMessage: String
@FocusState private var focusedField: Field?
@State var userSettings = UserSettings.shared
// TextField handling
enum Field {
case server
case username
case token
}
var body: some View {
VStack(alignment: .leading) {
ServerAddressField()
.padding(.bottom)
LoginLabel(text: "User name")
BorderedLoginTextField(example: "username", text: $userSettings.username)
.focused($focusedField, equals: .username)
.textContentType(.username)
.submitLabel(.next)
.padding(.bottom)
LoginLabel(text: "App Token")
BorderedLoginTextField(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()
}
}
.onSubmit {
switch focusedField {
case .server:
focusedField = .username
case .username:
focusedField = .token
default:
Logger.view.debug("Attempting to log in ...")
}
}
}
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
}
UserSettings.shared.setAuthString()
let client = CookbookApiFactory.makeClient()
do {
let _ = try await client.getCategories()
return true
} catch {
alertMessage = "Login failed. Please check your inputs and internet connection."
showAlert = true
return false
}
}
}