WIP - Complete App refactoring
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// CookbookApi.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 13.11.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
import UIKit
|
||||
|
||||
|
||||
/// The Cookbook API class used for requests to the Nextcloud Cookbook service.
|
||||
let cookbookApi: CookbookApi.Type = {
|
||||
switch UserSettings.shared.cookbookApiVersion {
|
||||
case .v1:
|
||||
return CookbookApiV1.self
|
||||
}
|
||||
}()
|
||||
|
||||
/// The Cookbook API version.
|
||||
enum CookbookApiVersion: String {
|
||||
case v1 = "v1"
|
||||
}
|
||||
|
||||
|
||||
/// A protocol defining common API endpoints that are likely to remain the same over future Cookbook API versions.
|
||||
protocol CookbookApi {
|
||||
static var basePath: String { get }
|
||||
|
||||
/// Not implemented yet.
|
||||
static func importRecipe(
|
||||
auth: String,
|
||||
data: Data
|
||||
) async -> (RecipeDetail?, NetworkError?)
|
||||
|
||||
/// Get either the full image or a thumbnail sized version.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - id: The according recipe id.
|
||||
/// - size: The size of the image.
|
||||
/// - Returns: The image of the recipe with the specified id. A NetworkError if the request fails, otherwise nil.
|
||||
static func getImage(
|
||||
auth: String,
|
||||
id: Int,
|
||||
size: RecipeImage.RecipeImageSize
|
||||
) async -> (UIImage?, NetworkError?)
|
||||
|
||||
/// Get all recipes.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - Returns: A list of all recipes.
|
||||
static func getRecipes(
|
||||
auth: String
|
||||
) async -> ([Recipe]?, NetworkError?)
|
||||
|
||||
/// Create a new recipe.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - Returns: A NetworkError if the request fails. Nil otherwise.
|
||||
static func createRecipe(
|
||||
auth: String,
|
||||
recipe: RecipeDetail
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Get the recipe with the specified id.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - id: The recipe id.
|
||||
/// - Returns: The recipe if it exists. A NetworkError if the request fails.
|
||||
static func getRecipe(
|
||||
auth: String, id: Int
|
||||
) async -> (RecipeDetail?, NetworkError?)
|
||||
|
||||
/// Update an existing recipe with new entries.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - id: The recipe id.
|
||||
/// - Returns: A NetworkError if the request fails. Nil otherwise.
|
||||
static func updateRecipe(
|
||||
auth: String,
|
||||
recipe: RecipeDetail
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Delete the recipe with the specified id.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - id: The recipe id.
|
||||
/// - Returns: A NetworkError if the request fails. Nil otherwise.
|
||||
static func deleteRecipe(
|
||||
auth: String,
|
||||
id: Int
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Get all categories.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - Returns: A list of categories. A NetworkError if the request fails.
|
||||
static func getCategories(
|
||||
auth: String
|
||||
) async -> ([Category]?, NetworkError?)
|
||||
|
||||
/// Get all recipes of a specified category.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - categoryName: The category name.
|
||||
/// - Returns: A list of recipes. A NetworkError if the request fails.
|
||||
static func getCategory(
|
||||
auth: String,
|
||||
named categoryName: String
|
||||
) async -> ([Recipe]?, NetworkError?)
|
||||
|
||||
/// Rename an existing category.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - categoryName: The name of the category to be renamed.
|
||||
/// - newName: The new category name.
|
||||
/// - Returns: A NetworkError if the request fails.
|
||||
static func renameCategory(
|
||||
auth: String,
|
||||
named categoryName: String,
|
||||
newName: String
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Get all keywords/tags.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - Returns: A list of tag strings. A NetworkError if the request fails.
|
||||
static func getTags(
|
||||
auth: String
|
||||
) async -> ([RecipeKeyword]?, NetworkError?)
|
||||
|
||||
/// Get all recipes tagged with the specified keyword.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - keyword: The keyword.
|
||||
/// - Returns: A list of recipes tagged with the specified keyword. A NetworkError if the request fails.
|
||||
static func getRecipesTagged(
|
||||
auth: String,
|
||||
keyword: String
|
||||
) async -> ([Recipe]?, NetworkError?)
|
||||
|
||||
/// Get the servers api version.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string.
|
||||
/// - Returns: A NetworkError if the request fails.
|
||||
static func getApiVersion(
|
||||
auth: String
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Trigger a reindexing action on the server.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string
|
||||
/// - Returns: A NetworkError if the request fails.
|
||||
static func postReindex(
|
||||
auth: String
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Get the current configuration of the Cookbook server application.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string
|
||||
/// - Returns: A NetworkError if the request fails.
|
||||
static func getConfig(
|
||||
auth: String
|
||||
) async -> (NetworkError?)
|
||||
|
||||
/// Set the current configuration of the Cookbook server application.
|
||||
/// - Parameters:
|
||||
/// - auth: Server authentication string
|
||||
/// - Returns: A NetworkError if the request fails.
|
||||
static func postConfig(
|
||||
auth: String
|
||||
) async -> (NetworkError?)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// CookbookApiV1.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 16.11.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
class CookbookApiV1: CookbookApi {
|
||||
static let basePath: String = "/index.php/apps/cookbook/api/v1"
|
||||
|
||||
static func importRecipe(auth: String, data: Data) async -> (RecipeDetail?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/import",
|
||||
method: .POST,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func getImage(auth: String, id: Int, size: RecipeImage.RecipeImageSize) async -> (UIImage?, NetworkError?) {
|
||||
let imageSize = (size == .FULL ? "full" : "thumb")
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes/\(id)/image?size=\(imageSize)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .IMAGE)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (UIImage(data: data), error)
|
||||
}
|
||||
|
||||
static func getRecipes(auth: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
print("\n\nRECIPE: ", String(data: data, encoding: .utf8))
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func createRecipe(auth: String, recipe: RecipeDetail) async -> (NetworkError?) {
|
||||
guard let recipeData = JSONEncoder.safeEncode(recipe) else {
|
||||
return .dataError
|
||||
}
|
||||
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes",
|
||||
method: .POST,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)],
|
||||
body: recipeData
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (error) }
|
||||
do {
|
||||
let json = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
|
||||
if let id = json as? Int {
|
||||
return nil
|
||||
} else if let dict = json as? [String: Any] {
|
||||
return .serverError
|
||||
}
|
||||
} catch {
|
||||
return .decodingFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func getRecipe(auth: String, id: Int) async -> (RecipeDetail?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes/\(id)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func updateRecipe(auth: String, recipe: RecipeDetail) async -> (NetworkError?) {
|
||||
guard let recipeData = JSONEncoder.safeEncode(recipe) else {
|
||||
return .dataError
|
||||
}
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes/\(recipe.id)",
|
||||
method: .PUT,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)],
|
||||
body: recipeData
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (error) }
|
||||
do {
|
||||
let json = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
|
||||
if let id = json as? Int {
|
||||
return nil
|
||||
} else if let dict = json as? [String: Any] {
|
||||
return .serverError
|
||||
}
|
||||
} catch {
|
||||
return .decodingFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func deleteRecipe(auth: String, id: Int) async -> (NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/recipes/\(id)",
|
||||
method: .DELETE,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (error) }
|
||||
return nil
|
||||
}
|
||||
|
||||
static func getCategories(auth: String) async -> ([Category]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/categories",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func getCategory(auth: String, named categoryName: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/category/\(categoryName)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func renameCategory(auth: String, named categoryName: String, newName: String) async -> (NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/category/\(categoryName)",
|
||||
method: .PUT,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (error) }
|
||||
return nil
|
||||
}
|
||||
|
||||
static func getTags(auth: String) async -> ([RecipeKeyword]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/keywords",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func getRecipesTagged(auth: String, keyword: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: basePath + "/tags/\(keyword)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
)
|
||||
|
||||
let (data, error) = await request.send()
|
||||
guard let data = data else { return (nil, error) }
|
||||
return (JSONDecoder.safeDecode(data), nil)
|
||||
}
|
||||
|
||||
static func getApiVersion(auth: String) async -> (NetworkError?) {
|
||||
return .none
|
||||
}
|
||||
|
||||
static func postReindex(auth: String) async -> (NetworkError?) {
|
||||
return .none
|
||||
}
|
||||
|
||||
static func getConfig(auth: String) async -> (NetworkError?) {
|
||||
return .none
|
||||
}
|
||||
|
||||
static func postConfig(auth: String) async -> (NetworkError?) {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Models.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 11.05.24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: - Login flow
|
||||
|
||||
struct LoginV2Request: Codable {
|
||||
let poll: LoginV2Poll
|
||||
let login: String
|
||||
}
|
||||
|
||||
struct LoginV2Poll: Codable {
|
||||
let token: String
|
||||
let endpoint: String
|
||||
}
|
||||
|
||||
struct LoginV2Response: Codable {
|
||||
let server: String
|
||||
let loginName: String
|
||||
let appPassword: String
|
||||
}
|
||||
|
||||
struct LoginValidation: Codable {
|
||||
let ocs: Ocs
|
||||
}
|
||||
|
||||
struct Ocs: Codable {
|
||||
let meta: MetaData
|
||||
}
|
||||
|
||||
struct MetaData: Codable {
|
||||
let status: String
|
||||
let statuscode: Int
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// RecipeModels.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 17.02.24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct CookbookApiRecipeV1: Codable {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension CookbookApiRecipeV1: Identifiable, Hashable {
|
||||
var id: String { name }
|
||||
}
|
||||
|
||||
|
||||
struct CookbookApiRecipeDetailV1: Codable {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//
|
||||
// CookbookProtocols.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 11.05.24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
Reference in New Issue
Block a user