// // RecipeIngredientSection.swift // Nextcloud Cookbook iOS Client // // Created by Vincent Meilinger on 01.03.24. // import Foundation import SwiftUI // MARK: - RecipeView Ingredients Section struct RecipeIngredientSection: View { @EnvironmentObject var groceryList: GroceryListManager @ObservedObject var viewModel: RecipeView.ViewModel var body: some View { VStack(alignment: .leading) { HStack { SecondaryLabel(text: LocalizedStringKey("Ingredients")) Spacer() Image(systemName: "person.2") .foregroundStyle(.secondary) .bold() ServingPickerView(selectedServingSize: $viewModel.observableRecipeDetail.ingredientMultiplier) } ForEach(0.. 0 { Image(systemName: isInGroceryList ? "cart.badge.minus" : "cart.badge.plus") .font(.caption) .bold() .foregroundStyle(.white) .frame(width: dragOffset, alignment: .center) .frame(maxHeight: .infinity) .background( RoundedRectangle(cornerRadius: 10) .fill(isInGroceryList ? Color.red : Color.green) ) } // Ingredient row HStack(alignment: .center) { Text("•") .foregroundStyle(.secondary) if !unmodified && String(modifiedIngredient.characters) == ingredient { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.red) } if unmodified { Text(ingredient) .multilineTextAlignment(.leading) .lineLimit(5) } else { Text(modifiedIngredient) .multilineTextAlignment(.leading) .lineLimit(5) } if isInGroceryList { Image(systemName: "cart") .font(.caption2) .foregroundStyle(.green) } Spacer() } .background(Color(.systemBackground)) .offset(x: dragOffset) } .clipped() .onChange(of: servings) { newServings in if recipeYield == 0 { modifiedIngredient = ObservableRecipeDetail.adjustIngredient(ingredient, by: newServings) } else { modifiedIngredient = ObservableRecipeDetail.adjustIngredient(ingredient, by: newServings/recipeYield) } } .gesture( DragGesture() .onChanged { gesture in if animationStartOffset == 0 { animationStartOffset = gesture.translation.width } let dragAmount = gesture.translation.width let offset = min(dragAmount, maxDragDistance + pow(max(0, dragAmount - maxDragDistance), 0.7)) - animationStartOffset self.dragOffset = max(0, offset) } .onEnded { _ in withAnimation { if dragOffset > maxDragDistance * swipeThreshold { if isInGroceryList { groceryList.deleteItem(ingredient, fromRecipe: recipeId) } else { groceryList.addItem( ingredient, toRecipe: recipeId, recipeName: recipeName ) } } self.dragOffset = 0 self.animationStartOffset = 0 } } ) } } struct ServingPickerView: View { @Binding var selectedServingSize: Double // Computed property to handle the text field input and update the selectedServingSize var body: some View { HStack { Button { selectedServingSize -= 1 } label: { Image(systemName: "minus.square.fill") .bold() } TextField("", value: $selectedServingSize, formatter: numberFormatter) .keyboardType(.numbersAndPunctuation) .lineLimit(1) .multilineTextAlignment(.center) .frame(width: 40) Button { selectedServingSize += 1 } label: { Image(systemName: "plus.square.fill") .bold() } } .onChange(of: selectedServingSize) { newValue in if newValue < 0 { selectedServingSize = 0 } else if newValue > 100 { selectedServingSize = 100 } } } }