diff --git a/.DS_Store b/.DS_Store index ab6c29c..0183d00 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/SwiftFood.xcodeproj/project.xcworkspace/xcuserdata/matt.xcuserdatad/UserInterfaceState.xcuserstate b/SwiftFood.xcodeproj/project.xcworkspace/xcuserdata/matt.xcuserdatad/UserInterfaceState.xcuserstate index 324fdb7..41ffa07 100644 Binary files a/SwiftFood.xcodeproj/project.xcworkspace/xcuserdata/matt.xcuserdatad/UserInterfaceState.xcuserstate and b/SwiftFood.xcodeproj/project.xcworkspace/xcuserdata/matt.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SwiftFood/.DS_Store b/SwiftFood/.DS_Store new file mode 100644 index 0000000..4303823 Binary files /dev/null and b/SwiftFood/.DS_Store differ diff --git a/SwiftFood/Assets.xcassets/.DS_Store b/SwiftFood/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..767a4dd Binary files /dev/null and b/SwiftFood/Assets.xcassets/.DS_Store differ diff --git a/SwiftFood/Schemas/Recipe.swift b/SwiftFood/Schemas/Recipe.swift index 705bd18..c04049c 100644 --- a/SwiftFood/Schemas/Recipe.swift +++ b/SwiftFood/Schemas/Recipe.swift @@ -6,3 +6,23 @@ // import Foundation +import SwiftData + +@Model +class Recipe: Identifiable { + + var id: String + var title: String + var time_required: Float + var servings_amount: Int + var instructions: String + + init(title: String, time_required: Float, servings_amount: Int, instructions: String) { + + self.id = UUID().uuidString + self.title = title + self.time_required = time_required + self.servings_amount = servings_amount + self.instructions = instructions + } +} diff --git a/SwiftFood/SwiftFoodApp.swift b/SwiftFood/SwiftFoodApp.swift index c2ff9bb..e018062 100644 --- a/SwiftFood/SwiftFoodApp.swift +++ b/SwiftFood/SwiftFoodApp.swift @@ -13,7 +13,8 @@ struct SwiftFoodApp: App { var body: some Scene { WindowGroup { ContentView() - .modelContainer(for: [MyIngredient.self]) // Attach ModelContainer + // attach the model container, enables the db + .modelContainer(for: [MyIngredient.self, Recipe.self]) } } } diff --git a/SwiftFood/Views/ContentView.swift b/SwiftFood/Views/ContentView.swift index e3a24dc..280ffbb 100644 --- a/SwiftFood/Views/ContentView.swift +++ b/SwiftFood/Views/ContentView.swift @@ -25,6 +25,12 @@ struct ContentView: View { .accessibilityIdentifier("ToIngredients") .buttonStyle(.borderedProminent) .padding(.bottom) + NavigationLink("View your recipes!") { + RecipeList() + } + .accessibilityIdentifier("ToRecipes") + .buttonStyle(.borderedProminent) + .padding(.bottom) } .padding() } diff --git a/SwiftFood/Views/MyIngredientList.swift b/SwiftFood/Views/MyIngredientList.swift index 129311e..dda819a 100644 --- a/SwiftFood/Views/MyIngredientList.swift +++ b/SwiftFood/Views/MyIngredientList.swift @@ -5,8 +5,6 @@ // Created by Matthew Caballero on 12/7/24. // -// Test change - import SwiftUI import SwiftData @@ -59,6 +57,7 @@ struct MyIngredientList: View { List { // For each ingredient in myingredient context + // Gets each ingredient of ingredient entity from the database ForEach(myIngredients) { ingredient in HStack { VStack(alignment: .leading) { @@ -79,16 +78,17 @@ struct MyIngredientList: View { Image(systemName: "trash") .foregroundColor(.red) } - .buttonStyle(.borderless) // To avoid affecting list row interaction + .buttonStyle(.borderless) } } } + .cornerRadius(20) } .padding() } func addIngredient() { - guard !title.isEmpty, !unit.isEmpty else { return } // Prevent empty input + guard !title.isEmpty, !unit.isEmpty else { return } // No empty fields let ingredient = MyIngredient( title: title, amount: amount, @@ -96,7 +96,7 @@ struct MyIngredientList: View { ) context.insert(ingredient) - // Resetting input fields + // Reset input fields when done title = "" amount = 0 unit = "" diff --git a/SwiftFood/Views/RecipeCreate.swift b/SwiftFood/Views/RecipeCreate.swift new file mode 100644 index 0000000..445545c --- /dev/null +++ b/SwiftFood/Views/RecipeCreate.swift @@ -0,0 +1,105 @@ +// +// RecipeCreate.swift +// SwiftFood +// +// Created by Matthew Caballero on 12/11/24. +// + +import SwiftUI +import SwiftData + +struct RecipeCreate: View { + @Environment(\.modelContext) private var context + @Environment(\.dismiss) private var dismiss + + @Query private var myRecipes: [Recipe] + + @State private var title: String = "" + @State private var time_required: Float = 0.0 + @State private var servings_amount: Int = 0 + @State private var instructions: String = "" + + // Used below for the number field, to show decimal correctly + private var decimalFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumFractionDigits = 0 + formatter.maximumFractionDigits = 2 + return formatter + } + + var body: some View { + VStack { + Text("My Ingredients") + .font(.headline) + + // Text input fields + // Uses accessibility identifiers, easy to access during testing + VStack(alignment: .leading, spacing: 10) { + HStack { + Text("Title: ") + TextField("Recipe Title", text: $title) + .accessibilityIdentifier("CreateRecipeTitle") + .textFieldStyle(.roundedBorder) + .frame(width: 110) + } + HStack { + Text("Time Required:") + TextField("Time Required", value: $time_required, formatter: decimalFormatter) + .accessibilityIdentifier("CreateRecipeTimeRequired") + .textFieldStyle(.roundedBorder) + .keyboardType(.decimalPad) + .frame(width: 40) + Text("hours") + } + HStack { + Text("Number of Servings: ") + TextField("Number of Servings", value: $servings_amount, formatter: NumberFormatter()) + .accessibilityIdentifier("CreateRecipeServingsAmount") + .textFieldStyle(.roundedBorder) + .keyboardType(.numberPad) + .frame(width: 40) + Text("servings") + } + Text("Instructions: ") + ScrollView { + TextEditor(text: $instructions) + .accessibilityIdentifier("CreateRecipeInstructions") + .frame(height: 150) // Fixed height + .cornerRadius(8) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray)) + } + } + // Add button + Button("Add Recipe") { + // Trigger add recipe function + addRecipe() + // Close current view / go back in the nav stack + dismiss() + } + .accessibilityIdentifier("AddRecipeButton") + .buttonStyle(.borderedProminent) + } + } + + func addRecipe() { + guard !title.isEmpty, time_required > 0, servings_amount > 0, !instructions.isEmpty else { return } // No empty inputs + let recipe = Recipe( + title: title, + time_required: time_required, + servings_amount: servings_amount, + instructions: instructions + ) + context.insert(recipe) + + // Resetting input fields + title = "" + time_required = 0.0 + servings_amount = 0 + instructions = "" + } +} + +#Preview { + ContentView() +} diff --git a/SwiftFood/Views/RecipeList.swift b/SwiftFood/Views/RecipeList.swift new file mode 100644 index 0000000..3c75b0b --- /dev/null +++ b/SwiftFood/Views/RecipeList.swift @@ -0,0 +1,62 @@ +// +// RecipeList.swift +// SwiftFood +// +// Created by Matthew Caballero on 12/11/24. +// + +import SwiftUI +import SwiftData + +struct RecipeList: View { + @Environment(\.modelContext) private var context + @Query private var myRecipes: [Recipe] + + var body: some View { + VStack { + Text("My Recipes") + .font(.headline) + + List { + // For each recipe in recipes context + ForEach(myRecipes) { recipe in + HStack { + VStack(alignment: .leading) { + Text(recipe.title) + .font(.headline) + // Formatting to show the decimal correctly + Text("\(String(format: "%.1f", recipe.time_required)) hours") + Text("\(recipe.servings_amount) servings") + } + Spacer() + + // Delete Button + Button(action: { + deleteItem(recipe) + }) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + } + .cornerRadius(20) + NavigationLink("Create new recipe!") { + RecipeCreate() + } + .accessibilityIdentifier("ToRecipeCreate") + .buttonStyle(.borderedProminent) + .padding(.bottom) + } + .padding() + } + + func deleteItem(_ recipe: Recipe) { + context.delete(recipe) + } +} + +#Preview { + ContentView() +}