Basic ingredient amount adjustment
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D792B25C66600A3469E /* OnboardingView.swift */; };
|
||||
A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */; };
|
||||
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */; };
|
||||
A90C45F42B9F4DB6005D62B6 /* Units.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90C45F32B9F4DB6005D62B6 /* Units.swift */; };
|
||||
A97506132B920D9F00E86029 /* RecipeDurationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506122B920D9F00E86029 /* RecipeDurationSection.swift */; };
|
||||
A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506142B920DF200E86029 /* RecipeGenericViews.swift */; };
|
||||
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506182B920EC200E86029 /* RecipeIngredientSection.swift */; };
|
||||
@@ -125,6 +126,7 @@
|
||||
A7FB0D792B25C66600A3469E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenLoginView.swift; sourceTree = "<group>"; };
|
||||
A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2LoginView.swift; sourceTree = "<group>"; };
|
||||
A90C45F32B9F4DB6005D62B6 /* Units.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Units.swift; sourceTree = "<group>"; };
|
||||
A97506122B920D9F00E86029 /* RecipeDurationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDurationSection.swift; sourceTree = "<group>"; };
|
||||
A97506142B920DF200E86029 /* RecipeGenericViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeGenericViews.swift; sourceTree = "<group>"; };
|
||||
A97506182B920EC200E86029 /* RecipeIngredientSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeIngredientSection.swift; sourceTree = "<group>"; };
|
||||
@@ -285,6 +287,7 @@
|
||||
A70171C52AB4C43A00064C43 /* DataModels.swift */,
|
||||
A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */,
|
||||
A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */,
|
||||
A90C45F32B9F4DB6005D62B6 /* Units.swift */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
@@ -593,6 +596,7 @@
|
||||
A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */,
|
||||
A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */,
|
||||
A70171C02AB498A900064C43 /* RecipeView.swift in Sources */,
|
||||
A90C45F42B9F4DB6005D62B6 /* Units.swift in Sources */,
|
||||
A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */,
|
||||
A975061B2B920F9F00E86029 /* RecipeNutritionSection.swift in Sources */,
|
||||
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */,
|
||||
@@ -769,7 +773,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_Client.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
||||
@@ -793,7 +797,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
MARKETING_VERSION = 1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
@@ -813,7 +817,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_Client.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
||||
@@ -837,7 +841,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
MARKETING_VERSION = 1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
||||
Binary file not shown.
@@ -83,11 +83,100 @@ class ObservableRecipeDetail: ObservableObject {
|
||||
)
|
||||
}
|
||||
|
||||
func ingredients(for servings: Int) -> [String] {
|
||||
for ingredient in recipeIngredient {
|
||||
// TODO: Parse ingredient strings, adjust them for yield
|
||||
/*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 []
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
212
Nextcloud Cookbook iOS Client/Data/Units.swift
Normal file
212
Nextcloud Cookbook iOS Client/Data/Units.swift
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// Units.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 11.03.24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
// MARK: - Ingredient Units
|
||||
|
||||
enum MeasurementUnit {
|
||||
// Volume Metric
|
||||
case milliLiter, centiLiter, deciLiter, liter
|
||||
|
||||
// Volume Imperial
|
||||
case teaspoon, tablespoon, cup, pint, quart, gallon, gill, fluidOunce // Please just use metric
|
||||
|
||||
// Weight Metric
|
||||
case milliGram, gram, kilogram
|
||||
|
||||
// Weight Imperial
|
||||
case ounce, pound
|
||||
|
||||
// Other
|
||||
case pinch, dash, smidgen
|
||||
|
||||
|
||||
var localizedDescription: [LocalizedStringKey] {
|
||||
switch self {
|
||||
case .milliLiter:
|
||||
return ["milliliter", "millilitre", "ml", "cc"]
|
||||
case .centiLiter:
|
||||
return ["centiliter", "centilitre", "cl"]
|
||||
case .deciLiter:
|
||||
return ["deciliter", "decilitre", "dl"]
|
||||
case .liter:
|
||||
return ["liter", "litre", "l"]
|
||||
case .teaspoon:
|
||||
return ["teaspoon", "tsp"]
|
||||
case .tablespoon:
|
||||
return ["tablespoon", "tbsp"]
|
||||
case .cup:
|
||||
return ["cup", "c"]
|
||||
case .pint:
|
||||
return ["pint", "pt"]
|
||||
case .quart:
|
||||
return ["quart", "qt"]
|
||||
case .gallon:
|
||||
return ["gallon", "gal"]
|
||||
case .gill:
|
||||
return ["gill", "gi"]
|
||||
case .fluidOunce:
|
||||
return ["fluid ounce", "fl oz"]
|
||||
case .milliGram:
|
||||
return ["milligram", "mg"]
|
||||
case .gram:
|
||||
return ["gram", "g"]
|
||||
case .kilogram:
|
||||
return ["kilogram", "kg"]
|
||||
case .ounce:
|
||||
return ["ounce", "oz"]
|
||||
case .pound:
|
||||
return ["pound", "lb"]
|
||||
case .pinch:
|
||||
return ["pinch"]
|
||||
case .dash:
|
||||
return ["dash"]
|
||||
case .smidgen:
|
||||
return ["smidgen"]
|
||||
}
|
||||
}
|
||||
|
||||
static func convert(value: Double, from fromUnit: MeasurementUnit, to toUnit: MeasurementUnit) -> Double? {
|
||||
let (baseValue, _) = MeasurementUnit.toBaseUnit(value: value, unit: fromUnit)
|
||||
return MeasurementUnit.fromBaseUnit(value: baseValue, targetUnit: toUnit)
|
||||
}
|
||||
|
||||
private static func baseUnit(of unit: MeasurementUnit) -> MeasurementUnit {
|
||||
switch unit {
|
||||
// Volume Metric (all converted to liters)
|
||||
case .milliLiter, .centiLiter, .deciLiter, .liter, .teaspoon, .tablespoon, .cup, .pint, .quart, .gallon, .gill, .fluidOunce, .dash:
|
||||
return .liter
|
||||
|
||||
// Weight (all converted to grams)
|
||||
case .milliGram, .gram, .kilogram, .ounce, .pound, .pinch, .smidgen:
|
||||
return .gram
|
||||
}
|
||||
}
|
||||
|
||||
private static func toBaseUnit(value: Double, unit: MeasurementUnit) -> (Double, MeasurementUnit) {
|
||||
guard abs(value) >= Double(1e-10) else {
|
||||
return (0, unit)
|
||||
}
|
||||
switch unit {
|
||||
case .milliLiter:
|
||||
return (value/1000, .liter)
|
||||
case .centiLiter:
|
||||
return (value/100, .liter)
|
||||
case .deciLiter:
|
||||
return (value/10, .liter)
|
||||
case .liter:
|
||||
return (value, .liter)
|
||||
case .teaspoon:
|
||||
return (value * 0.005, .liter)
|
||||
case .tablespoon:
|
||||
return (value * 0.015, .liter)
|
||||
case .cup:
|
||||
return (value * 0.25, .liter)
|
||||
case .pint:
|
||||
return (value * 0.5, .liter)
|
||||
case .quart:
|
||||
return (value * 0.946, .liter)
|
||||
case .gallon:
|
||||
return (value * 3.8, .liter)
|
||||
case .gill:
|
||||
return (value * 0.17, .liter)
|
||||
case .fluidOunce:
|
||||
return (value * 0.03, .liter)
|
||||
case .milliGram:
|
||||
return (value * 0.001, .gram)
|
||||
case .gram:
|
||||
return (value, .gram)
|
||||
case .kilogram:
|
||||
return (value * 1000, .gram)
|
||||
case .ounce:
|
||||
return (value * 30, .gram)
|
||||
case .pound:
|
||||
return (value * 450, .gram)
|
||||
case .pinch:
|
||||
return (value * 0.3, .gram)
|
||||
case .dash:
|
||||
return (value * 0.000625, .liter)
|
||||
case .smidgen:
|
||||
return (value * 0.15, .gram)
|
||||
}
|
||||
}
|
||||
|
||||
static private func fromBaseUnit(value: Double, targetUnit: MeasurementUnit) -> Double {
|
||||
guard abs(value) >= Double(1e-10) else {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch targetUnit {
|
||||
case .milliLiter:
|
||||
return value * 1000
|
||||
case .centiLiter:
|
||||
return value * 100
|
||||
case .deciLiter:
|
||||
return value * 10
|
||||
case .liter:
|
||||
return value
|
||||
case .teaspoon:
|
||||
return value / 0.005
|
||||
case .tablespoon:
|
||||
return value / 0.015
|
||||
case .cup:
|
||||
return value / 0.25
|
||||
case .pint:
|
||||
return value / 0.5
|
||||
case .quart:
|
||||
return value / 0.946
|
||||
case .gallon:
|
||||
return value / 3.8
|
||||
case .gill:
|
||||
return value / 0.17
|
||||
case .fluidOunce:
|
||||
return value / 0.03
|
||||
case .milliGram:
|
||||
return value * 1000
|
||||
case .gram:
|
||||
return value
|
||||
case .kilogram:
|
||||
return value / 1000
|
||||
case .ounce:
|
||||
return value / 30
|
||||
case .pound:
|
||||
return value / 450
|
||||
case .pinch:
|
||||
return value / 0.3
|
||||
case .dash:
|
||||
return value / 0.000625
|
||||
case .smidgen:
|
||||
return value / 0.15
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum TemperatureUnit {
|
||||
case fahrenheit, celsius
|
||||
|
||||
var localizedDescription: [LocalizedStringKey] {
|
||||
switch self {
|
||||
case .fahrenheit:
|
||||
["fahrenheit", "f"]
|
||||
case .celsius:
|
||||
["celsius", "c"]
|
||||
}
|
||||
}
|
||||
|
||||
static func celsiusToFahrenheit(_ celsius: Double) -> Double {
|
||||
return celsius * 9.0 / 5.0 + 32.0
|
||||
}
|
||||
|
||||
static func fahrenheitToCelsius(_ fahrenheit: Double) -> Double {
|
||||
return (fahrenheit - 32.0) * 5.0 / 9.0
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%.2f" : {
|
||||
|
||||
},
|
||||
"%@" : {
|
||||
"localizations" : {
|
||||
@@ -661,6 +664,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"c" : {
|
||||
|
||||
},
|
||||
"Calories" : {
|
||||
"comment" : "Calories",
|
||||
@@ -774,6 +780,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cc" : {
|
||||
|
||||
},
|
||||
"celsius" : {
|
||||
|
||||
},
|
||||
"centiliter" : {
|
||||
|
||||
},
|
||||
"centilitre" : {
|
||||
|
||||
},
|
||||
"Cholesterol content" : {
|
||||
"comment" : "Cholesterol content",
|
||||
@@ -819,6 +837,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cl" : {
|
||||
|
||||
},
|
||||
"Configure what is stored on your device." : {
|
||||
"localizations" : {
|
||||
@@ -1061,6 +1082,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cup" : {
|
||||
|
||||
},
|
||||
"dash" : {
|
||||
|
||||
},
|
||||
"deciliter" : {
|
||||
|
||||
},
|
||||
"decilitre" : {
|
||||
|
||||
},
|
||||
"Delete" : {
|
||||
"localizations" : {
|
||||
@@ -1238,6 +1271,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dl" : {
|
||||
|
||||
},
|
||||
"Done" : {
|
||||
"localizations" : {
|
||||
@@ -1481,6 +1517,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"f" : {
|
||||
|
||||
},
|
||||
"fahrenheit" : {
|
||||
|
||||
},
|
||||
"Fat content" : {
|
||||
"comment" : "Fat content",
|
||||
@@ -1527,6 +1569,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fl oz" : {
|
||||
|
||||
},
|
||||
"fluid ounce" : {
|
||||
|
||||
},
|
||||
"g" : {
|
||||
|
||||
},
|
||||
"gal" : {
|
||||
|
||||
},
|
||||
"gallon" : {
|
||||
|
||||
},
|
||||
"General" : {
|
||||
"localizations" : {
|
||||
@@ -1571,6 +1628,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"gi" : {
|
||||
|
||||
},
|
||||
"gill" : {
|
||||
|
||||
},
|
||||
"gram" : {
|
||||
|
||||
},
|
||||
"Grocery List" : {
|
||||
"localizations" : {
|
||||
@@ -1923,6 +1989,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kg" : {
|
||||
|
||||
},
|
||||
"kilogram" : {
|
||||
|
||||
},
|
||||
"l" : {
|
||||
|
||||
},
|
||||
"Language" : {
|
||||
"localizations" : {
|
||||
@@ -1989,6 +2064,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lb" : {
|
||||
|
||||
},
|
||||
"List your tools here. 🍴" : {
|
||||
"localizations" : {
|
||||
@@ -2011,6 +2089,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"liter" : {
|
||||
|
||||
},
|
||||
"litre" : {
|
||||
|
||||
},
|
||||
"Log out" : {
|
||||
"localizations" : {
|
||||
@@ -2121,6 +2205,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mg" : {
|
||||
|
||||
},
|
||||
"milligram" : {
|
||||
|
||||
},
|
||||
"milliliter" : {
|
||||
|
||||
},
|
||||
"millilitre" : {
|
||||
|
||||
},
|
||||
"Minutes" : {
|
||||
"localizations" : {
|
||||
@@ -2211,6 +2307,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ml" : {
|
||||
|
||||
},
|
||||
"More information" : {
|
||||
"localizations" : {
|
||||
@@ -2498,6 +2597,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ounce" : {
|
||||
|
||||
},
|
||||
"oz" : {
|
||||
|
||||
},
|
||||
"Parsing error" : {
|
||||
"localizations" : {
|
||||
@@ -2564,6 +2669,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinch" : {
|
||||
|
||||
},
|
||||
"pint" : {
|
||||
|
||||
},
|
||||
"Please check the entered URL." : {
|
||||
"localizations" : {
|
||||
@@ -2630,6 +2741,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pound" : {
|
||||
|
||||
},
|
||||
"Preparation" : {
|
||||
"localizations" : {
|
||||
@@ -2698,6 +2812,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
|
||||
},
|
||||
"qt" : {
|
||||
|
||||
},
|
||||
"quart" : {
|
||||
|
||||
},
|
||||
"Recipe" : {
|
||||
"localizations" : {
|
||||
@@ -3008,6 +3131,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Serving Size" : {
|
||||
|
||||
},
|
||||
"Servings" : {
|
||||
"localizations" : {
|
||||
@@ -3186,6 +3312,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"smidgen" : {
|
||||
|
||||
},
|
||||
"Sodium content" : {
|
||||
"comment" : "Sodium content",
|
||||
@@ -3386,6 +3515,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tablespoon" : {
|
||||
|
||||
},
|
||||
"tbsp" : {
|
||||
|
||||
},
|
||||
"teaspoon" : {
|
||||
|
||||
},
|
||||
"Thank you for downloading" : {
|
||||
"localizations" : {
|
||||
@@ -3721,6 +3859,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsp" : {
|
||||
|
||||
},
|
||||
"Unable to adjust the highlighted ingredient amount!" : {
|
||||
|
||||
},
|
||||
"Unable to complete action." : {
|
||||
"localizations" : {
|
||||
|
||||
@@ -13,6 +13,7 @@ import SwiftUI
|
||||
struct RecipeIngredientSection: View {
|
||||
@EnvironmentObject var groceryList: GroceryList
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var servingsMultiplier: Double = 1
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
@@ -25,6 +26,7 @@ struct RecipeIngredientSection: View {
|
||||
SecondaryLabel(text: LocalizedStringKey("Ingredients for \(viewModel.observableRecipeDetail.recipeYield) servings"))
|
||||
}
|
||||
Spacer()
|
||||
ServingPickerView(selectedServingSize: $servingsMultiplier)
|
||||
Button {
|
||||
withAnimation {
|
||||
if groceryList.containsRecipe(viewModel.observableRecipeDetail.id) {
|
||||
@@ -45,9 +47,19 @@ struct RecipeIngredientSection: View {
|
||||
}
|
||||
}.disabled(viewModel.editMode)
|
||||
}
|
||||
|
||||
if servingsMultiplier != 1 {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.red)
|
||||
Text("Unable to adjust the highlighted ingredient amount!")
|
||||
}
|
||||
}
|
||||
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
||||
IngredientListItem(ingredient: $viewModel.observableRecipeDetail.recipeIngredient[ix], recipeId: viewModel.observableRecipeDetail.id) {
|
||||
IngredientListItem(
|
||||
ingredient: $viewModel.observableRecipeDetail.recipeIngredient[ix],
|
||||
servings: $servingsMultiplier,
|
||||
recipeId: viewModel.observableRecipeDetail.id
|
||||
) {
|
||||
groceryList.addItem(
|
||||
viewModel.observableRecipeDetail.recipeIngredient[ix],
|
||||
toRecipe: viewModel.observableRecipeDetail.id,
|
||||
@@ -73,6 +85,7 @@ struct RecipeIngredientSection: View {
|
||||
fileprivate struct IngredientListItem: View {
|
||||
@EnvironmentObject var groceryList: GroceryList
|
||||
@Binding var ingredient: String
|
||||
@Binding var servings: Double
|
||||
@State var recipeId: String
|
||||
let addToGroceryListAction: () -> Void
|
||||
@State var isSelected: Bool = false
|
||||
@@ -98,10 +111,17 @@ fileprivate struct IngredientListItem: View {
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
}
|
||||
|
||||
Text("\(ingredient)")
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(5)
|
||||
if servings == 1 {
|
||||
Text(ingredient)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(5)
|
||||
} else {
|
||||
let modifiedIngredient = ObservableRecipeDetail.modifyIngredientAmounts(in: ingredient, withFactor: servings)
|
||||
Text(modifiedIngredient)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(5)
|
||||
.foregroundStyle(modifiedIngredient == ingredient ? .red : .primary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.foregroundStyle(isSelected ? Color.secondary : Color.primary)
|
||||
@@ -139,3 +159,32 @@ fileprivate struct IngredientListItem: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct ServingPickerView: View {
|
||||
@Binding var selectedServingSize: Double
|
||||
var servingSizes: [Double] {
|
||||
var servingSizes: [Double] = [0.125, 0.25, 0.33, 0.5, 0.66, 0.75, 1]
|
||||
for i in 2...100 {
|
||||
servingSizes.append(Double(i))
|
||||
}
|
||||
return servingSizes
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Picker("Serving Size", selection: Binding(
|
||||
get: {
|
||||
self.selectedServingSize
|
||||
},
|
||||
set: { newValue in
|
||||
self.selectedServingSize = newValue
|
||||
}
|
||||
)) {
|
||||
ForEach(servingSizes, id: \.self) { size in
|
||||
Text(ObservableRecipeDetail.formatNumber(size)).tag(size)
|
||||
}
|
||||
}
|
||||
.pickerStyle(MenuPickerStyle())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user