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>
This commit is contained in:
2026-02-15 00:47:28 +01:00
parent 527acd2967
commit 7c824b492e
31 changed files with 534 additions and 1103 deletions

View File

@@ -6,6 +6,7 @@
//
import Foundation
import OSLog
import SwiftUI
struct OnboardingView: View {
@@ -203,7 +204,7 @@ struct ServerAddressField: View {
.tint(.white)
.font(.headline)
.onChange(of: serverProtocol) { value in
print(value)
Logger.view.debug("\(value.rawValue)")
userSettings.serverProtocol = value.rawValue
}

View File

@@ -6,6 +6,7 @@
//
import Foundation
import OSLog
import SwiftUI
@@ -72,7 +73,7 @@ struct TokenLoginView: View {
case .username:
focusedField = .token
default:
print("Attempting to log in ...")
Logger.view.debug("Attempting to log in ...")
}
}
}
@@ -87,21 +88,16 @@ struct TokenLoginView: View {
showAlert = true
return false
}
UserSettings.shared.setAuthString()
let (data, error) = await cookbookApi.getCategories(auth: UserSettings.shared.authString)
if let error = error {
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
}
guard let data = data else {
alertMessage = "Login failed. Please check your inputs."
showAlert = true
return false
}
return true
}
}

View File

@@ -6,6 +6,7 @@
//
import Foundation
import OSLog
import SwiftUI
import WebKit
@@ -82,7 +83,7 @@ struct V2LoginView: View {
Task {
let error = await sendLoginV2Request()
if let error = error {
alertMessage = "A network error occured (\(error.rawValue))."
alertMessage = "A network error occured (\(error.localizedDescription))."
showAlert = true
}
if let loginRequest = loginRequest {
@@ -151,13 +152,13 @@ struct V2LoginView: View {
}
func fetchLoginV2Response() async -> (LoginV2Response?, NetworkError?) {
guard let loginRequest = loginRequest else { return (nil, .parametersNil) }
guard let loginRequest = loginRequest else { return (nil, .invalidRequest) }
return await NextcloudApi.loginV2Response(req: loginRequest)
}
func checkLogin(response: LoginV2Response?, error: NetworkError?) {
if let error = error {
alertMessage = "Login failed. Please login via the browser and try again. (\(error.rawValue))"
alertMessage = "Login failed. Please login via the browser and try again. (\(error.localizedDescription))"
showAlert = true
return
}
@@ -166,7 +167,7 @@ struct V2LoginView: View {
showAlert = true
return
}
print("Login successful for user \(response.loginName)!")
Logger.network.debug("Login successful for user \(response.loginName)!")
UserSettings.shared.username = response.loginName
UserSettings.shared.token = response.appPassword
let loginString = "\(UserSettings.shared.username):\(UserSettings.shared.token)"