306 lines
10 KiB
Swift
306 lines
10 KiB
Swift
//
|
|
// RecipeModels.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
// Created by Vincent Meilinger on 17.02.24.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
struct CookbookApiCategory: Codable, Identifiable, Hashable {
|
|
var id: String { name }
|
|
var name: String
|
|
var 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"
|
|
}
|
|
}
|
|
}
|
|
|