Files
Nextcloud-Cookbook-iOS/Nextcloud Cookbook iOS Client/Data/ObservableRecipeDetail.swift
2024-03-11 20:07:50 +01:00

185 lines
7.9 KiB
Swift

//
// ObservableRecipeDetail.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 01.03.24.
//
import Foundation
import SwiftUI
class ObservableRecipeDetail: ObservableObject {
var id: String
@Published var name: String
@Published var keywords: [String]
@Published var imageUrl: String
@Published var prepTime: DurationComponents
@Published var cookTime: DurationComponents
@Published var totalTime: DurationComponents
@Published var description: String
@Published var url: String
@Published var recipeYield: Int
@Published var recipeCategory: String
@Published var tool: [String]
@Published var recipeIngredient: [String]
@Published var recipeInstructions: [String]
@Published var nutrition: [String:String]
init() {
id = ""
name = String(localized: "New Recipe")
keywords = []
imageUrl = ""
prepTime = DurationComponents()
cookTime = DurationComponents()
totalTime = DurationComponents()
description = ""
url = ""
recipeYield = 0
recipeCategory = ""
tool = []
recipeIngredient = []
recipeInstructions = []
nutrition = [:]
}
init(_ recipeDetail: RecipeDetail) {
id = recipeDetail.id
name = recipeDetail.name
keywords = recipeDetail.keywords.isEmpty ? [] : recipeDetail.keywords.components(separatedBy: ",")
imageUrl = recipeDetail.imageUrl
prepTime = DurationComponents.fromPTString(recipeDetail.prepTime ?? "")
cookTime = DurationComponents.fromPTString(recipeDetail.cookTime ?? "")
totalTime = DurationComponents.fromPTString(recipeDetail.totalTime ?? "")
description = recipeDetail.description
url = recipeDetail.url
recipeYield = recipeDetail.recipeYield
recipeCategory = recipeDetail.recipeCategory
tool = recipeDetail.tool
recipeIngredient = recipeDetail.recipeIngredient
recipeInstructions = recipeDetail.recipeInstructions
nutrition = recipeDetail.nutrition
}
func toRecipeDetail() -> RecipeDetail {
return RecipeDetail(
name: self.name,
keywords: self.keywords.joined(separator: ","),
dateCreated: "",
dateModified: "",
imageUrl: self.imageUrl,
id: self.id,
prepTime: self.prepTime.toPTString(),
cookTime: self.cookTime.toPTString(),
totalTime: self.totalTime.toPTString(),
description: self.description,
url: self.url,
recipeYield: self.recipeYield,
recipeCategory: self.recipeCategory,
tool: self.tool,
recipeIngredient: self.recipeIngredient,
recipeInstructions: self.recipeInstructions,
nutrition: self.nutrition
)
}
/*static func modifyIngredientAmounts(in ingredient: String, withFactor factor: Double) -> String {
// Regular expression to match numbers, including integers and decimals
// Patterns:
// "\\b\\d+(\\.\\d+)?\\b" works only if there is a space following
let regex = try! NSRegularExpression(pattern: "\\b\\d+(\\.\\d+)?\\b", options: [])
let matches = regex.matches(in: ingredient, options: [], range: NSRange(ingredient.startIndex..., in: ingredient))
// Reverse the matches to replace from the end to avoid affecting indices of unprocessed matches
let reversedMatches = matches.reversed()
var modifiedIngredient = ingredient
for match in reversedMatches {
guard let range = Range(match.range, in: modifiedIngredient) else { continue }
let originalNumberString = String(modifiedIngredient[range])
if let originalNumber = Double(originalNumberString) {
let modifiedNumber = originalNumber * factor
// Format the number to remove trailing zeros if it's an integer after multiplication
let formattedNumber = formatNumber(modifiedNumber)
modifiedIngredient.replaceSubrange(range, with: formattedNumber)
}
}
return modifiedIngredient
}*/
static func modifyIngredientAmounts(in ingredient: String, withFactor factor: Double) -> String {
// Regular expression to match numbers (including integers and decimals) and fractions
let regexPattern = "\\b(\\d+(\\.\\d+)?)\\b|\\b(\\d+/\\d+)\\b"
let regex = try! NSRegularExpression(pattern: regexPattern, options: [])
let matches = regex.matches(in: ingredient, options: [], range: NSRange(ingredient.startIndex..., in: ingredient))
var modifiedIngredient = ingredient
// Reverse the matches to replace from the end to avoid affecting indices of unprocessed matches
let reversedMatches = matches.reversed()
for match in reversedMatches {
let fullMatchRange = match.range(at: 0)
// Check for a fractional match
if match.range(at: 3).location != NSNotFound, let fractionRange = Range(match.range(at: 3), in: modifiedIngredient) {
let fractionString = String(modifiedIngredient[fractionRange])
let fractionParts = fractionString.split(separator: "/").compactMap { Double($0) }
if fractionParts.count == 2, let numerator = fractionParts.first, let denominator = fractionParts.last, denominator != 0 {
let fractionValue = numerator / denominator
let modifiedNumber = fractionValue * factor
let formattedNumber = formatNumber(modifiedNumber)
modifiedIngredient.replaceSubrange(fractionRange, with: formattedNumber)
}
}
// Check for an integer or decimal match
else if let numberRange = Range(fullMatchRange, in: modifiedIngredient) {
let numberString = String(modifiedIngredient[numberRange])
if let number = Double(numberString) {
let modifiedNumber = number * factor
let formattedNumber = formatNumber(modifiedNumber)
modifiedIngredient.replaceSubrange(numberRange, with: formattedNumber)
}
}
}
return modifiedIngredient
}
static func formatNumber(_ value: Double) -> String {
let integerPart = value >= 1 ? Int(value) : 0
let decimalPart = value - Double(integerPart)
if integerPart >= 1 && decimalPart < 0.0001 {
return String(format: "%.0f", value)
}
// Define known fractions and their decimal equivalents
let knownFractions: [(fraction: String, value: Double)] = [
("1/8", 0.125), ("1/6", 0.167), ("1/4", 0.25), ("1/3", 0.33), ("1/2", 0.5), ("2/3", 0.66), ("3/4", 0.75)
]
// Find the known fraction closest to the given value
let closest = knownFractions.min(by: { abs($0.value - decimalPart) < abs($1.value - decimalPart) })!
// Check if the value is close enough to a known fraction to be considered a match
let threshold = 0.05
if abs(closest.value - decimalPart) <= threshold && integerPart == 0 {
return closest.fraction
} else if abs(closest.value - decimalPart) <= threshold && integerPart > 0 {
return "\(String(integerPart)) \(closest.fraction)"
} else {
// If no close match is found, return the original value as a string
return String(format: "%.2f", value)
}
}
func ingredientUnitsToMetric() {
// TODO: Convert imperial units in recipes to metric units
}
}