Files
Nextcloud-Cookbook-iOS/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookModelsV1.swift
2025-05-26 15:52:24 +02:00

306 lines
10 KiB
Swift

//
// RecipeModels.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 17.02.24.
//
import Foundation
import SwiftUI
struct Category: Codable, Identifiable, Hashable {
var id: String { name }
let name: String
let recipe_count: Int
private enum CodingKeys: String, CodingKey {
case name, recipe_count
}
}
struct CookbookApiRecipeV1: CookbookApiRecipe, Codable, Identifiable, Hashable {
var id: String { name + String(recipe_id) }
let name: String
let keywords: String?
let dateCreated: String?
let dateModified: String?
let imageUrl: String?
let imagePlaceholderUrl: String?
let recipe_id: Int
// Properties excluded from Codable
var storedLocally: Bool? = nil
private enum CodingKeys: String, CodingKey {
case name, keywords, dateCreated, dateModified, imageUrl, imagePlaceholderUrl, recipe_id
}
func toRecipeStub() -> RecipeStub {
return RecipeStub(
id: String(recipe_id),
name: name,
keywords: keywords,
dateCreated: dateCreated,
dateModified: dateModified,
thumbnailPath: nil
)
}
}
struct CookbookApiRecipeDetailV1: CookbookApiRecipeDetail {
var name: String
var keywords: String
var dateCreated: String?
var dateModified: String?
var imageUrl: String?
var id: String
var prepTime: String?
var cookTime: String?
var totalTime: String?
var description: String
var url: String?
var recipeYield: Int
var recipeCategory: String
var tool: [String]
var recipeIngredient: [String]
var recipeInstructions: [String]
var nutrition: [String:String]
init(name: String, keywords: String, dateCreated: String?, dateModified: String?, imageUrl: String?, id: String, prepTime: String? = nil, cookTime: String? = nil, totalTime: String? = nil, description: String, url: String?, recipeYield: Int, recipeCategory: String, tool: [String], recipeIngredient: [String], recipeInstructions: [String], nutrition: [String:String]) {
self.name = name
self.keywords = keywords
self.dateCreated = dateCreated
self.dateModified = dateModified
self.imageUrl = imageUrl
self.id = id
self.prepTime = prepTime
self.cookTime = cookTime
self.totalTime = totalTime
self.description = description
self.url = url
self.recipeYield = recipeYield
self.recipeCategory = recipeCategory
self.tool = tool
self.recipeIngredient = recipeIngredient
self.recipeInstructions = recipeInstructions
self.nutrition = nutrition
}
init() {
name = ""
keywords = ""
dateCreated = ""
dateModified = ""
imageUrl = ""
id = ""
prepTime = ""
cookTime = ""
totalTime = ""
description = ""
url = ""
recipeYield = 0
recipeCategory = ""
tool = []
recipeIngredient = []
recipeInstructions = []
nutrition = [:]
}
// Custom decoder to handle value type ambiguity
private enum CodingKeys: String, CodingKey {
case name, keywords, dateCreated, dateModified, imageUrl, id, prepTime, cookTime, totalTime, description, url, recipeYield, recipeCategory, tool, recipeIngredient, recipeInstructions, nutrition
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
keywords = try container.decode(String.self, forKey: .keywords)
dateCreated = try container.decodeIfPresent(String.self, forKey: .dateCreated)
dateModified = try container.decodeIfPresent(String.self, forKey: .dateModified)
imageUrl = try container.decodeIfPresent(String.self, forKey: .imageUrl)
id = try container.decode(String.self, forKey: .id)
prepTime = try container.decodeIfPresent(String.self, forKey: .prepTime)
cookTime = try container.decodeIfPresent(String.self, forKey: .cookTime)
totalTime = try container.decodeIfPresent(String.self, forKey: .totalTime)
description = try container.decode(String.self, forKey: .description)
url = try container.decode(String.self, forKey: .url)
recipeYield = try container.decode(Int.self, forKey: .recipeYield)
recipeCategory = try container.decode(String.self, forKey: .recipeCategory)
tool = try container.decode([String].self, forKey: .tool)
recipeIngredient = try container.decode([String].self, forKey: .recipeIngredient)
recipeInstructions = try container.decode([String].self, forKey: .recipeInstructions)
nutrition = try container.decode(Dictionary<String, JSONAny>.self, forKey: .nutrition).mapValues { String(describing: $0.value) }
}
func toRecipe() -> Recipe {
return Recipe(
id: self.id,
name: self.name,
keywords: keywords.components(separatedBy: ","),
dateCreated: self.dateCreated,
dateModified: self.dateModified,
prepTime: self.prepTime ?? "",
cookTime: self.cookTime ?? "",
totalTime: self.totalTime ?? "",
recipeDescription: self.description,
url: self.url,
yield: self.recipeYield,
category: self.recipeCategory,
tools: self.tool,
ingredients: self.recipeIngredient,
instructions: self.recipeInstructions,
nutrition: self.nutrition,
ingredientMultiplier: 1.0
)
}
static func fromRecipe(_ recipe: Recipe) -> any CookbookApiRecipeDetail {
return CookbookApiRecipeDetailV1(
name: recipe.name,
keywords: recipe.keywords.joined(separator: ","),
dateCreated: recipe.dateCreated,
dateModified: recipe.dateModified,
imageUrl: "",
id: recipe.id,
description: recipe.recipeDescription,
url: recipe.url,
recipeYield: recipe.yield,
recipeCategory: recipe.category,
tool: recipe.tools,
recipeIngredient: recipe.ingredients,
recipeInstructions: recipe.instructions,
nutrition: recipe.nutrition
)
}
}
extension CookbookApiRecipeDetailV1 {
static var error: CookbookApiRecipeDetailV1 {
return CookbookApiRecipeDetailV1(
name: "Error: Unable to load recipe.",
keywords: "",
dateCreated: "",
dateModified: "",
imageUrl: "",
id: "",
prepTime: "",
cookTime: "",
totalTime: "",
description: "",
url: "",
recipeYield: 0,
recipeCategory: "",
tool: [],
recipeIngredient: [],
recipeInstructions: [],
nutrition: [:]
)
}
func getKeywordsArray() -> [String] {
if keywords == "" { return [] }
return keywords.components(separatedBy: ",")
}
mutating func setKeywordsFromArray(_ keywordsArray: [String]) {
if !keywordsArray.isEmpty {
self.keywords = keywordsArray.joined(separator: ",")
}
}
}
/*
struct RecipeImage {
enum RecipeImageSize: String {
case THUMB="thumb", FULL="full"
}
var imageExists: Bool = true
var thumb: UIImage?
var full: UIImage?
}
*/
struct RecipeKeyword: Codable {
let name: String
let recipe_count: Int
}
enum Nutrition: CaseIterable {
case servingSize,
calories,
carbohydrateContent,
cholesterolContent,
fatContent,
saturatedFatContent,
unsaturatedFatContent,
transFatContent,
fiberContent,
proteinContent,
sodiumContent,
sugarContent
var localizedDescription: String {
switch self {
case .servingSize:
return NSLocalizedString("Serving size", comment: "Serving size")
case .calories:
return NSLocalizedString("Calories", comment: "Calories")
case .carbohydrateContent:
return NSLocalizedString("Carbohydrate content", comment: "Carbohydrate content")
case .cholesterolContent:
return NSLocalizedString("Cholesterol content", comment: "Cholesterol content")
case .fatContent:
return NSLocalizedString("Fat content", comment: "Fat content")
case .saturatedFatContent:
return NSLocalizedString("Saturated fat content", comment: "Saturated fat content")
case .unsaturatedFatContent:
return NSLocalizedString("Unsaturated fat content", comment: "Unsaturated fat content")
case .transFatContent:
return NSLocalizedString("Trans fat content", comment: "Trans fat content")
case .fiberContent:
return NSLocalizedString("Fiber content", comment: "Fiber content")
case .proteinContent:
return NSLocalizedString("Protein content", comment: "Protein content")
case .sodiumContent:
return NSLocalizedString("Sodium content", comment: "Sodium content")
case .sugarContent:
return NSLocalizedString("Sugar content", comment: "Sugar content")
}
}
var dictKey: String {
switch self {
case .servingSize:
"servingSize"
case .calories:
"calories"
case .carbohydrateContent:
"carbohydrateContent"
case .cholesterolContent:
"cholesterolContent"
case .fatContent:
"fatContent"
case .saturatedFatContent:
"saturatedFatContent"
case .unsaturatedFatContent:
"unsaturatedFatContent"
case .transFatContent:
"transFatContent"
case .fiberContent:
"fiberContent"
case .proteinContent:
"proteinContent"
case .sodiumContent:
"sodiumContent"
case .sugarContent:
"sugarContent"
}
}
}