WIP - Complete App refactoring
This commit is contained in:
@@ -8,9 +8,10 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/*
|
||||
struct RecipeCardView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State var recipe: Recipe
|
||||
@State var recipe: CookbookApiRecipeV1
|
||||
@State var recipeThumb: UIImage?
|
||||
@State var isDownloaded: Bool? = nil
|
||||
|
||||
@@ -69,3 +70,63 @@ struct RecipeCardView: View {
|
||||
.frame(height: 80)
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
struct RecipeCardView: View {
|
||||
@State var state: AccountState
|
||||
@State var recipe: RecipeStub
|
||||
@State var recipeThumb: UIImage?
|
||||
@State var isDownloaded: Bool? = nil
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if let recipeThumb = recipeThumb {
|
||||
Image(uiImage: recipeThumb)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 17))
|
||||
} else {
|
||||
Image(systemName: "square.text.square")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundStyle(Color.white)
|
||||
.padding(10)
|
||||
.background(Color("ncblue"))
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 17))
|
||||
}
|
||||
Text(recipe.name)
|
||||
.font(.headline)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Spacer()
|
||||
if let isDownloaded = isDownloaded {
|
||||
VStack {
|
||||
Image(systemName: isDownloaded ? "checkmark.circle" : "icloud.and.arrow.down")
|
||||
.foregroundColor(.secondary)
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.backgroundHighlight)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 17))
|
||||
.task {
|
||||
recipeThumb = await state.getImage(
|
||||
id: recipe.id,
|
||||
size: .THUMB
|
||||
)
|
||||
|
||||
isDownloaded = recipe.storedLocally
|
||||
}
|
||||
.refreshable {
|
||||
recipeThumb = await state.getImage(
|
||||
id: recipe.id,
|
||||
size: .THUMB
|
||||
)
|
||||
}
|
||||
.frame(height: 80)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -9,14 +9,14 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
|
||||
/*
|
||||
struct RecipeListView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@EnvironmentObject var groceryList: GroceryList
|
||||
@State var categoryName: String
|
||||
@State var searchText: String = ""
|
||||
@Binding var showEditView: Bool
|
||||
@State var selectedRecipe: Recipe? = nil
|
||||
@State var selectedRecipe: CookbookApiRecipeV1? = nil
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -56,7 +56,7 @@ struct RecipeListView: View {
|
||||
.searchable(text: $searchText, prompt: "Search recipes/keywords")
|
||||
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
|
||||
|
||||
.navigationDestination(for: Recipe.self) { recipe in
|
||||
.navigationDestination(for: CookbookApiRecipeV1.self) { recipe in
|
||||
RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe))
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
@@ -85,7 +85,7 @@ struct RecipeListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func recipesFiltered() -> [Recipe] {
|
||||
func recipesFiltered() -> [CookbookApiRecipeV1] {
|
||||
guard let recipes = appState.recipes[categoryName] else { return [] }
|
||||
guard searchText != "" else { return recipes }
|
||||
return recipes.filter { recipe in
|
||||
@@ -94,3 +94,86 @@ struct RecipeListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
|
||||
struct RecipeListView: View {
|
||||
@Bindable var cookbookState: CookbookState
|
||||
@State var selectedCategory: String
|
||||
@State var searchText: String = ""
|
||||
@Binding var showEditView: Bool
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
let recipes = recipesFiltered()
|
||||
if !recipes.isEmpty {
|
||||
|
||||
List(recipesFiltered(), selection: $cookbookState.selectedAccountState.selectedRecipe) { recipe in
|
||||
RecipeCardView(state: cookbookState.selectedAccountState, recipe: recipe)
|
||||
.shadow(radius: 2)
|
||||
.background(
|
||||
NavigationLink {
|
||||
RecipeView(viewModel: RecipeView.ViewModel(recipeStub: recipe))
|
||||
.environment(cookbookState)
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(0)
|
||||
)
|
||||
.frame(height: 85)
|
||||
.listRowInsets(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
|
||||
.listRowSeparatorTint(.clear)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
|
||||
} else {
|
||||
VStack {
|
||||
Text("There are no recipes in this cookbook!")
|
||||
Button {
|
||||
Task {
|
||||
let _ = await cookbookState.selectedAccountState.getCategories()
|
||||
let _ = await cookbookState.selectedAccountState.getRecipeStubsForCategory(named: selectedCategory)
|
||||
}
|
||||
} label: {
|
||||
Text("Refresh")
|
||||
.bold()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchText, prompt: "Search recipes/keywords")
|
||||
.navigationTitle(selectedCategory == "*" ? String(localized: "Other") : selectedCategory)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
print("Add new recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
let _ = await cookbookState.selectedAccountState.getRecipeStubsForCategory(
|
||||
named: selectedCategory
|
||||
)
|
||||
}
|
||||
.refreshable {
|
||||
let _ = await cookbookState.selectedAccountState.getRecipeStubsForCategory(
|
||||
named: selectedCategory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func recipesFiltered() -> [RecipeStub] {
|
||||
guard let recipes = cookbookState.selectedAccountState.recipeStubs[selectedCategory] else { return [] }
|
||||
guard searchText != "" else { return recipes }
|
||||
return recipes.filter { recipe in
|
||||
recipe.name.lowercased().contains(searchText.lowercased()) || // check name for occurence of search term
|
||||
(recipe.keywords != nil && recipe.keywords!.lowercased().contains(searchText.lowercased())) // check keywords for search term
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
/*
|
||||
struct RecipeView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@@ -164,7 +164,7 @@ struct RecipeView: View {
|
||||
let recipeDetail = await appState.getRecipe(
|
||||
id: viewModel.recipe.recipe_id,
|
||||
fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer
|
||||
) ?? RecipeDetail.error
|
||||
) ?? CookbookApiRecipeDetailV1.error
|
||||
viewModel.setupView(recipeDetail: recipeDetail)
|
||||
|
||||
// Show download badge
|
||||
@@ -182,7 +182,7 @@ struct RecipeView: View {
|
||||
|
||||
} else {
|
||||
// Prepare view for a new recipe
|
||||
viewModel.setupView(recipeDetail: RecipeDetail())
|
||||
viewModel.setupView(recipeDetail: CookbookApiRecipeDetailV1())
|
||||
viewModel.editMode = true
|
||||
viewModel.isDownloaded = false
|
||||
}
|
||||
@@ -231,8 +231,8 @@ struct RecipeView: View {
|
||||
// MARK: - RecipeView ViewModel
|
||||
|
||||
class ViewModel: ObservableObject {
|
||||
@Published var observableRecipeDetail: ObservableRecipeDetail = ObservableRecipeDetail()
|
||||
@Published var recipeDetail: RecipeDetail = RecipeDetail.error
|
||||
@Published var observableRecipeDetail: Recipe = Recipe()
|
||||
@Published var recipeDetail: CookbookApiRecipeDetailV1 = CookbookApiRecipeDetailV1.error
|
||||
@Published var recipeImage: UIImage? = nil
|
||||
@Published var editMode: Bool = false
|
||||
@Published var showTitle: Bool = false
|
||||
@@ -244,7 +244,7 @@ struct RecipeView: View {
|
||||
@Published var presentIngredientEditView: Bool = false
|
||||
@Published var presentToolEditView: Bool = false
|
||||
|
||||
var recipe: Recipe
|
||||
var recipe: CookbookApiRecipeV1
|
||||
var sharedURL: URL? = nil
|
||||
var newRecipe: Bool = false
|
||||
|
||||
@@ -254,13 +254,13 @@ struct RecipeView: View {
|
||||
var alertAction: () async -> () = { }
|
||||
|
||||
// Initializers
|
||||
init(recipe: Recipe) {
|
||||
init(recipe: CookbookApiRecipeV1) {
|
||||
self.recipe = recipe
|
||||
}
|
||||
|
||||
init() {
|
||||
self.newRecipe = true
|
||||
self.recipe = Recipe(
|
||||
self.recipe = CookbookApiRecipeV1(
|
||||
name: String(localized: "New Recipe"),
|
||||
keywords: "",
|
||||
dateCreated: "",
|
||||
@@ -271,9 +271,9 @@ struct RecipeView: View {
|
||||
}
|
||||
|
||||
// View setup
|
||||
func setupView(recipeDetail: RecipeDetail) {
|
||||
func setupView(recipeDetail: CookbookApiRecipeDetailV1) {
|
||||
self.recipeDetail = recipeDetail
|
||||
self.observableRecipeDetail = ObservableRecipeDetail(recipeDetail)
|
||||
self.observableRecipeDetail = Recipe(recipeDetail)
|
||||
}
|
||||
|
||||
func presentAlert(_ type: UserAlert, action: @escaping () async -> () = {}) {
|
||||
@@ -458,4 +458,434 @@ struct RecipeViewToolBar: ToolbarContent {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
struct RecipeView: View {
|
||||
@Environment(CookbookState.self) var cookbookState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var viewModel: ViewModel
|
||||
@GestureState private var dragOffset = CGSize.zero
|
||||
|
||||
var imageHeight: CGFloat {
|
||||
if let image = viewModel.recipeImage {
|
||||
return image.size.height < 350 ? image.size.height : 350
|
||||
}
|
||||
return 200
|
||||
}
|
||||
|
||||
private enum CoordinateSpaces {
|
||||
case scrollView
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
ParallaxHeader(
|
||||
coordinateSpace: CoordinateSpaces.scrollView,
|
||||
defaultHeight: imageHeight
|
||||
) {
|
||||
if let recipeImage = viewModel.recipeImage {
|
||||
Image(uiImage: recipeImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(maxHeight: imageHeight + 200)
|
||||
.clipped()
|
||||
} else {
|
||||
Rectangle()
|
||||
.frame(height: 400)
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
if viewModel.editMode {
|
||||
RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe)
|
||||
}
|
||||
|
||||
if viewModel.editMode {
|
||||
RecipeMetadataSection(viewModel: viewModel)
|
||||
}
|
||||
|
||||
HStack {
|
||||
EditableText(text: $viewModel.recipe.name, editMode: $viewModel.editMode, titleKey: "Recipe Name")
|
||||
.font(.title)
|
||||
.bold()
|
||||
|
||||
Spacer()
|
||||
|
||||
if let isDownloaded = viewModel.isDownloaded {
|
||||
Image(systemName: isDownloaded ? "checkmark.circle" : "icloud.and.arrow.down")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}.padding([.top, .horizontal])
|
||||
|
||||
if viewModel.recipe.description != "" || viewModel.editMode {
|
||||
EditableText(text: $viewModel.recipe.description, editMode: $viewModel.editMode, titleKey: "Description", lineLimit: 0...5, axis: .vertical)
|
||||
.fontWeight(.medium)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
|
||||
// Recipe Body Section
|
||||
RecipeDurationSection(viewModel: viewModel)
|
||||
|
||||
Divider()
|
||||
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
||||
if(!viewModel.recipe.recipeIngredient.isEmpty || viewModel.editMode) {
|
||||
RecipeIngredientSection(viewModel: viewModel)
|
||||
}
|
||||
if(!viewModel.recipe.recipeInstructions.isEmpty || viewModel.editMode) {
|
||||
RecipeInstructionSection(viewModel: viewModel)
|
||||
}
|
||||
if(!viewModel.recipe.tool.isEmpty || viewModel.editMode) {
|
||||
RecipeToolSection(viewModel: viewModel)
|
||||
}
|
||||
RecipeNutritionSection(viewModel: viewModel)
|
||||
}
|
||||
|
||||
if !viewModel.editMode {
|
||||
Divider()
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
||||
RecipeKeywordSection(viewModel: viewModel)
|
||||
MoreInformationSection(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 5)
|
||||
.background(Rectangle().foregroundStyle(.background).shadow(radius: 5).mask(Rectangle().padding(.top, -20)))
|
||||
}
|
||||
}
|
||||
.coordinateSpace(name: CoordinateSpaces.scrollView)
|
||||
.ignoresSafeArea(.container, edges: .top)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
//.toolbarTitleDisplayMode(.inline)
|
||||
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
|
||||
|
||||
.toolbar {
|
||||
RecipeViewToolBar(viewModel: viewModel)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.presentShareSheet) {
|
||||
ShareView(recipeDetail: viewModel.recipe.toRecipeDetail(),
|
||||
recipeImage: viewModel.recipeImage,
|
||||
presentShareSheet: $viewModel.presentShareSheet)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.presentInstructionEditView) {
|
||||
EditableListView(
|
||||
isPresented: $viewModel.presentInstructionEditView,
|
||||
items: $viewModel.recipe.recipeInstructions,
|
||||
title: "Instructions",
|
||||
emptyListText: "Add cooking steps for fellow chefs to follow.",
|
||||
titleKey: "Instruction",
|
||||
lineLimit: 0...10,
|
||||
axis: .vertical)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.presentIngredientEditView) {
|
||||
EditableListView(
|
||||
isPresented: $viewModel.presentIngredientEditView,
|
||||
items: $viewModel.recipe.recipeIngredient,
|
||||
title: "Ingredients",
|
||||
emptyListText: "Start by adding your first ingredient! 🥬",
|
||||
titleKey: "Ingredient",
|
||||
lineLimit: 0...1,
|
||||
axis: .horizontal)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.presentToolEditView) {
|
||||
EditableListView(
|
||||
isPresented: $viewModel.presentToolEditView,
|
||||
items: $viewModel.recipe.tool,
|
||||
title: "Tools",
|
||||
emptyListText: "List your tools here. 🍴",
|
||||
titleKey: "Tool",
|
||||
lineLimit: 0...1,
|
||||
axis: .horizontal)
|
||||
}
|
||||
|
||||
.task {
|
||||
// Load recipe detail
|
||||
if let recipeStub = viewModel.recipeStub {
|
||||
// For existing recipes, load the recipeDetail and image
|
||||
let recipe = await cookbookState.selectedAccountState.getRecipe(
|
||||
id: recipeStub.id
|
||||
) ?? Recipe()
|
||||
viewModel.recipe = recipe
|
||||
|
||||
// Show download badge
|
||||
/*if viewModel.recipeStub!.storedLocally == nil {
|
||||
viewModel.recipeStub?.storedLocally = cookbookState.selectedAccountState.recipeDetailExists(
|
||||
recipeId: viewModel.recipe.recipe_id
|
||||
)
|
||||
}
|
||||
viewModel.isDownloaded = viewModel.recipeStub!.storedLocally
|
||||
*/
|
||||
// Load recipe image
|
||||
viewModel.recipeImage = await cookbookState.selectedAccountState.getImage(
|
||||
id: recipeStub.id,
|
||||
size: .FULL
|
||||
)
|
||||
|
||||
} else {
|
||||
// Prepare view for a new recipe
|
||||
viewModel.editMode = true
|
||||
viewModel.isDownloaded = false
|
||||
viewModel.recipe = Recipe()
|
||||
}
|
||||
}
|
||||
.alert(viewModel.alertType.localizedTitle, isPresented: $viewModel.presentAlert) {
|
||||
ForEach(viewModel.alertType.alertButtons) { buttonType in
|
||||
if buttonType == .OK {
|
||||
Button(AlertButton.OK.rawValue, role: .cancel) {
|
||||
Task {
|
||||
await viewModel.alertAction()
|
||||
}
|
||||
}
|
||||
} else if buttonType == .CANCEL {
|
||||
Button(AlertButton.CANCEL.rawValue, role: .cancel) { }
|
||||
} else if buttonType == .DELETE {
|
||||
Button(AlertButton.DELETE.rawValue, role: .destructive) {
|
||||
Task {
|
||||
await viewModel.alertAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(viewModel.alertType.localizedDescription)
|
||||
}
|
||||
.onAppear {
|
||||
if UserSettings.shared.keepScreenAwake {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
}
|
||||
.onChange(of: viewModel.editMode) { newValue in
|
||||
if newValue && cookbookState.selectedAccountState.keywords.isEmpty {
|
||||
Task {
|
||||
if let keywords = await cookbookState.selectedAccountState.getTags()?.sorted(by: { a, b in
|
||||
a.recipe_count > b.recipe_count
|
||||
}) {
|
||||
cookbookState.selectedAccountState.keywords = keywords
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RecipeView ViewModel
|
||||
|
||||
@Observable class ViewModel {
|
||||
var recipeImage: UIImage? = nil
|
||||
var editMode: Bool = false
|
||||
var showTitle: Bool = false
|
||||
var isDownloaded: Bool? = nil
|
||||
var importUrl: String = ""
|
||||
|
||||
var presentShareSheet: Bool = false
|
||||
var presentInstructionEditView: Bool = false
|
||||
var presentIngredientEditView: Bool = false
|
||||
var presentToolEditView: Bool = false
|
||||
|
||||
var recipeStub: RecipeStub? = nil
|
||||
var recipe: Recipe = Recipe()
|
||||
var sharedURL: URL? = nil
|
||||
var newRecipe: Bool = false
|
||||
|
||||
// Alerts
|
||||
var presentAlert = false
|
||||
var alertType: UserAlert = RecipeAlert.GENERIC
|
||||
var alertAction: () async -> () = { }
|
||||
|
||||
// Initializers
|
||||
init(recipeStub: RecipeStub) {
|
||||
self.recipeStub = recipeStub
|
||||
}
|
||||
|
||||
init() {
|
||||
self.newRecipe = true
|
||||
}
|
||||
|
||||
func presentAlert(_ type: UserAlert, action: @escaping () async -> () = {}) {
|
||||
alertType = type
|
||||
alertAction = action
|
||||
presentAlert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension RecipeView {
|
||||
func importRecipe(from url: String) async -> UserAlert? {
|
||||
/*let (scrapedRecipe, error) = await appState.importRecipe(url: url)
|
||||
if let scrapedRecipe = scrapedRecipe {
|
||||
viewModel.setupView(recipeDetail: scrapedRecipe)
|
||||
return nil
|
||||
}*/
|
||||
|
||||
do {
|
||||
let (scrapedRecipe, error) = try await RecipeScraper().scrape(url: url)
|
||||
if let scrapedRecipe = scrapedRecipe {
|
||||
viewModel.recipe = scrapedRecipe.toRecipe()
|
||||
}
|
||||
if let error = error {
|
||||
return error
|
||||
}
|
||||
} catch {
|
||||
print("Error")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Tool Bar
|
||||
|
||||
|
||||
struct RecipeViewToolBar: ToolbarContent {
|
||||
@Environment(CookbookState.self) var cookbookState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
|
||||
var body: some ToolbarContent {
|
||||
if viewModel.editMode {
|
||||
ToolbarItemGroup(placement: .topBarLeading) {
|
||||
Button("Cancel") {
|
||||
viewModel.editMode = false
|
||||
if viewModel.newRecipe {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if !viewModel.newRecipe {
|
||||
Menu {
|
||||
Button(role: .destructive) {
|
||||
viewModel.presentAlert(
|
||||
RecipeAlert.CONFIRM_DELETE,
|
||||
action: {
|
||||
await handleDelete()
|
||||
}
|
||||
)
|
||||
} label: {
|
||||
Label("Delete Recipe", systemImage: "trash")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
Task {
|
||||
await handleUpload()
|
||||
}
|
||||
} label: {
|
||||
if viewModel.newRecipe {
|
||||
Text("Upload Recipe")
|
||||
} else {
|
||||
Text("Upload Changes")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Menu {
|
||||
Button {
|
||||
viewModel.editMode = true
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
|
||||
Button {
|
||||
print("Sharing recipe ...")
|
||||
viewModel.presentShareSheet = true
|
||||
} label: {
|
||||
Label("Share Recipe", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleUpload() async {
|
||||
if viewModel.newRecipe {
|
||||
print("Uploading new recipe.")
|
||||
if let recipeValidationError = recipeValid() {
|
||||
viewModel.presentAlert(recipeValidationError)
|
||||
return
|
||||
}
|
||||
|
||||
if let alert = await cookbookState.selectedAccountState.postRecipe(recipe: viewModel.recipe) {
|
||||
viewModel.presentAlert(alert)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
print("Uploading changed recipe.")
|
||||
|
||||
if let alert = await cookbookState.selectedAccountState.updateRecipe(recipe: viewModel.recipe) {
|
||||
viewModel.presentAlert(alert)
|
||||
return
|
||||
}
|
||||
}
|
||||
let _ = await cookbookState.selectedAccountState.getCategories()
|
||||
let _ = await cookbookState.selectedAccountState.getRecipeStubsForCategory(named: viewModel.recipe.recipeCategory)
|
||||
let _ = await cookbookState.selectedAccountState.getRecipe(id: viewModel.recipe.id)
|
||||
viewModel.editMode = false
|
||||
viewModel.presentAlert(RecipeAlert.UPLOAD_SUCCESS)
|
||||
}
|
||||
|
||||
func handleDelete() async {
|
||||
let category = viewModel.recipe.recipeCategory
|
||||
if let alert = await cookbookState.selectedAccountState.deleteRecipe(id: viewModel.recipe.id) {
|
||||
viewModel.presentAlert(alert)
|
||||
return
|
||||
}
|
||||
let _ = await cookbookState.selectedAccountState.getCategories()
|
||||
let _ = await cookbookState.selectedAccountState.getRecipeStubsForCategory(named: category)
|
||||
viewModel.presentAlert(RecipeAlert.DELETE_SUCCESS)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
func recipeValid() -> RecipeAlert? {
|
||||
// Check if the recipe has a name
|
||||
if viewModel.recipe.name.replacingOccurrences(of: " ", with: "") == "" {
|
||||
return RecipeAlert.NO_TITLE
|
||||
}
|
||||
|
||||
// Check if the recipe has a unique name
|
||||
for recipeList in cookbookState.selectedAccountState.recipeStubs.values {
|
||||
for r in recipeList {
|
||||
if r.name
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.lowercased() ==
|
||||
viewModel.recipe.name
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.lowercased()
|
||||
{
|
||||
return RecipeAlert.DUPLICATE
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -9,17 +9,17 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Duration Section
|
||||
|
||||
/*
|
||||
struct RecipeDurationSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
@State var presentPopover: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) {
|
||||
DurationView(time: viewModel.observableRecipeDetail.prepTime, title: LocalizedStringKey("Preparation"))
|
||||
DurationView(time: viewModel.observableRecipeDetail.cookTime, title: LocalizedStringKey("Cooking"))
|
||||
DurationView(time: viewModel.observableRecipeDetail.totalTime, title: LocalizedStringKey("Total time"))
|
||||
DurationView(time: viewModel.recipe.prepTime, title: LocalizedStringKey("Preparation"))
|
||||
DurationView(time: viewModel.recipe.cookTime, title: LocalizedStringKey("Cooking"))
|
||||
DurationView(time: viewModel.recipe.totalTime, title: LocalizedStringKey("Total time"))
|
||||
}
|
||||
if viewModel.editMode {
|
||||
Button {
|
||||
@@ -34,9 +34,9 @@ struct RecipeDurationSection: View {
|
||||
.padding()
|
||||
.popover(isPresented: $presentPopover) {
|
||||
EditableDurationView(
|
||||
prepTime: viewModel.observableRecipeDetail.prepTime,
|
||||
cookTime: viewModel.observableRecipeDetail.cookTime,
|
||||
totalTime: viewModel.observableRecipeDetail.totalTime
|
||||
prepTime: viewModel.recipe.prepTime,
|
||||
cookTime: viewModel.recipe.cookTime,
|
||||
totalTime: viewModel.recipe.totalTime
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ fileprivate struct EditableDurationView: View {
|
||||
TimePickerView(selectedHour: $totalTime.hourComponent, selectedMinute: $totalTime.minuteComponent)
|
||||
}
|
||||
.padding()
|
||||
.onChange(of: prepTime.hourComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: prepTime.minuteComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: cookTime.hourComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: cookTime.minuteComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: prepTime.hourComponent) { updateTotalTime() }
|
||||
.onChange(of: prepTime.minuteComponent) { updateTotalTime() }
|
||||
.onChange(of: cookTime.hourComponent) { updateTotalTime() }
|
||||
.onChange(of: cookTime.minuteComponent) { updateTotalTime() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,3 +142,5 @@ fileprivate struct TimePickerView: View {
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -10,9 +10,9 @@ import SwiftUI
|
||||
|
||||
|
||||
// MARK: - RecipeView Import Section
|
||||
|
||||
/*
|
||||
struct RecipeImportSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
var importRecipe: (String) async -> UserAlert?
|
||||
|
||||
var body: some View {
|
||||
@@ -49,4 +49,4 @@ struct RecipeImportSection: View {
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,23 +9,23 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Ingredients Section
|
||||
|
||||
/*
|
||||
struct RecipeIngredientSection: View {
|
||||
@EnvironmentObject var groceryList: GroceryList
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@Environment(CookbookState.self) var cookbookState
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Button {
|
||||
withAnimation {
|
||||
if groceryList.containsRecipe(viewModel.observableRecipeDetail.id) {
|
||||
groceryList.deleteGroceryRecipe(viewModel.observableRecipeDetail.id)
|
||||
if cookbookState.groceryList.containsRecipe(viewModel.recipe.id) {
|
||||
cookbookState.groceryList.deleteGroceryRecipe(viewModel.recipe.id)
|
||||
} else {
|
||||
groceryList.addItems(
|
||||
viewModel.observableRecipeDetail.recipeIngredient,
|
||||
toRecipe: viewModel.observableRecipeDetail.id,
|
||||
recipeName: viewModel.observableRecipeDetail.name
|
||||
cookbookState.groceryList.addItems(
|
||||
viewModel.recipe.recipeIngredient,
|
||||
toRecipe: viewModel.recipe.id,
|
||||
recipeName: viewModel.recipe.name
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -45,26 +45,26 @@ struct RecipeIngredientSection: View {
|
||||
.foregroundStyle(.secondary)
|
||||
.bold()
|
||||
|
||||
ServingPickerView(selectedServingSize: $viewModel.observableRecipeDetail.ingredientMultiplier)
|
||||
ServingPickerView(selectedServingSize: $viewModel.recipe.ingredientMultiplier)
|
||||
}
|
||||
|
||||
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
||||
ForEach(0..<viewModel.recipe.recipeIngredient.count, id: \.self) { ix in
|
||||
IngredientListItem(
|
||||
ingredient: $viewModel.observableRecipeDetail.recipeIngredient[ix],
|
||||
servings: $viewModel.observableRecipeDetail.ingredientMultiplier,
|
||||
recipeYield: Double(viewModel.observableRecipeDetail.recipeYield),
|
||||
recipeId: viewModel.observableRecipeDetail.id
|
||||
ingredient: $viewModel.recipe.recipeIngredient[ix],
|
||||
servings: $viewModel.recipe.ingredientMultiplier,
|
||||
recipeYield: Double(viewModel.recipe.recipeYield),
|
||||
recipeId: viewModel.recipe.id
|
||||
) {
|
||||
groceryList.addItem(
|
||||
viewModel.observableRecipeDetail.recipeIngredient[ix],
|
||||
toRecipe: viewModel.observableRecipeDetail.id,
|
||||
recipeName: viewModel.observableRecipeDetail.name
|
||||
cookbookState.groceryList.addItem(
|
||||
viewModel.recipe.recipeIngredient[ix],
|
||||
toRecipe: viewModel.recipe.id,
|
||||
recipeName: viewModel.recipe.name
|
||||
)
|
||||
}
|
||||
.padding(4)
|
||||
}
|
||||
|
||||
if viewModel.observableRecipeDetail.ingredientMultiplier != Double(viewModel.observableRecipeDetail.recipeYield) {
|
||||
if viewModel.recipe.ingredientMultiplier != Double(viewModel.recipe.recipeYield) {
|
||||
HStack() {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -83,14 +83,14 @@ struct RecipeIngredientSection: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.animation(.easeInOut, value: viewModel.observableRecipeDetail.ingredientMultiplier)
|
||||
.animation(.easeInOut, value: viewModel.recipe.ingredientMultiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RecipeIngredientSection List Item
|
||||
|
||||
fileprivate struct IngredientListItem: View {
|
||||
@EnvironmentObject var groceryList: GroceryList
|
||||
@Environment(CookbookState.self) var cookbookState
|
||||
@Binding var ingredient: String
|
||||
@Binding var servings: Double
|
||||
@State var recipeYield: Double
|
||||
@@ -110,7 +110,7 @@ fileprivate struct IngredientListItem: View {
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
if groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||
if cookbookState.groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||
if #available(iOS 17.0, *) {
|
||||
Image(systemName: "storefront")
|
||||
.foregroundStyle(Color.green)
|
||||
@@ -140,11 +140,11 @@ fileprivate struct IngredientListItem: View {
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.onChange(of: servings) { newServings in
|
||||
.onChange(of: servings) { _, newServings in
|
||||
if recipeYield == 0 {
|
||||
modifiedIngredient = ObservableRecipeDetail.adjustIngredient(ingredient, by: newServings)
|
||||
modifiedIngredient = Recipe.adjustIngredient(ingredient, by: newServings)
|
||||
} else {
|
||||
modifiedIngredient = ObservableRecipeDetail.adjustIngredient(ingredient, by: newServings/recipeYield)
|
||||
modifiedIngredient = Recipe.adjustIngredient(ingredient, by: newServings/recipeYield)
|
||||
}
|
||||
}
|
||||
.foregroundStyle(isSelected ? Color.secondary : Color.primary)
|
||||
@@ -168,8 +168,8 @@ fileprivate struct IngredientListItem: View {
|
||||
.onEnded { gesture in
|
||||
withAnimation {
|
||||
if dragOffset > maxDragDistance * 0.3 { // Swipe threshold
|
||||
if groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||
groceryList.deleteItem(ingredient, fromRecipe: recipeId)
|
||||
if cookbookState.groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||
cookbookState.groceryList.deleteItem(ingredient, fromRecipe: recipeId)
|
||||
} else {
|
||||
addToGroceryListAction()
|
||||
}
|
||||
@@ -209,9 +209,12 @@ struct ServingPickerView: View {
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedServingSize) { newValue in
|
||||
.onChange(of: selectedServingSize) { _, newValue in
|
||||
if newValue < 0 { selectedServingSize = 0 }
|
||||
else if newValue > 100 { selectedServingSize = 100 }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,9 +9,9 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Instructions Section
|
||||
|
||||
/*
|
||||
struct RecipeInstructionSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
@@ -19,8 +19,8 @@ struct RecipeInstructionSection: View {
|
||||
SecondaryLabel(text: LocalizedStringKey("Instructions"))
|
||||
Spacer()
|
||||
}
|
||||
ForEach(viewModel.observableRecipeDetail.recipeInstructions.indices, id: \.self) { ix in
|
||||
RecipeInstructionListItem(instruction: $viewModel.observableRecipeDetail.recipeInstructions[ix], index: ix+1)
|
||||
ForEach(viewModel.recipe.recipeInstructions.indices, id: \.self) { ix in
|
||||
RecipeInstructionListItem(instruction: $viewModel.recipe.recipeInstructions[ix], index: ix+1)
|
||||
}
|
||||
if viewModel.editMode {
|
||||
Button {
|
||||
@@ -56,4 +56,4 @@ fileprivate struct RecipeInstructionListItem: View {
|
||||
.animation(.easeInOut, value: isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,16 +9,16 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Keyword Section
|
||||
|
||||
/*
|
||||
struct RecipeKeywordSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
let columns: [GridItem] = [ GridItem(.flexible(minimum: 50, maximum: 200), spacing: 5) ]
|
||||
|
||||
var body: some View {
|
||||
CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandKeywordSection) {
|
||||
Group {
|
||||
if !viewModel.observableRecipeDetail.keywords.isEmpty && !viewModel.editMode {
|
||||
RecipeListSection(list: $viewModel.observableRecipeDetail.keywords)
|
||||
if !viewModel.recipe.keywords.isEmpty && !viewModel.editMode {
|
||||
RecipeListSection(list: $viewModel.recipe.keywords)
|
||||
} else {
|
||||
Text(LocalizedStringKey("No keywords."))
|
||||
}
|
||||
@@ -189,3 +189,4 @@ struct KeywordPickerView_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,14 +9,14 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Recipe Metadata Section
|
||||
|
||||
/*
|
||||
struct RecipeMetadataSection: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@Environment(CookbookState.self) var cookbookState
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
@State var keywords: [RecipeKeyword] = []
|
||||
var categories: [String] {
|
||||
appState.categories.map({ category in category.name })
|
||||
cookbookState.selectedAccountState.categories.map({ category in category.name })
|
||||
}
|
||||
|
||||
@State var presentKeywordSheet: Bool = false
|
||||
@@ -28,11 +28,11 @@ struct RecipeMetadataSection: View {
|
||||
// Category
|
||||
SecondaryLabel(text: "Category")
|
||||
HStack {
|
||||
TextField("Category", text: $viewModel.observableRecipeDetail.recipeCategory)
|
||||
TextField("Category", text: $viewModel.recipe.recipeCategory)
|
||||
.lineLimit(1)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
||||
Picker("Choose", selection: $viewModel.observableRecipeDetail.recipeCategory) {
|
||||
Picker("Choose", selection: $viewModel.recipe.recipeCategory) {
|
||||
Text("").tag("")
|
||||
ForEach(categories, id: \.self) { item in
|
||||
Text(item)
|
||||
@@ -45,10 +45,10 @@ struct RecipeMetadataSection: View {
|
||||
// Keywords
|
||||
SecondaryLabel(text: "Keywords")
|
||||
|
||||
if !viewModel.observableRecipeDetail.keywords.isEmpty {
|
||||
if !viewModel.recipe.keywords.isEmpty {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
ForEach(viewModel.observableRecipeDetail.keywords, id: \.self) { keyword in
|
||||
ForEach(viewModel.recipe.keywords, id: \.self) { keyword in
|
||||
Text(keyword)
|
||||
.padding(5)
|
||||
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.primary.opacity(0.1)))
|
||||
@@ -70,11 +70,11 @@ struct RecipeMetadataSection: View {
|
||||
Button {
|
||||
presentServingsPopover.toggle()
|
||||
} label: {
|
||||
Text("\(viewModel.observableRecipeDetail.recipeYield) Serving(s)")
|
||||
Text("\(viewModel.recipe.recipeYield) Serving(s)")
|
||||
.lineLimit(1)
|
||||
}
|
||||
.popover(isPresented: $presentServingsPopover) {
|
||||
PickerPopoverView(isPresented: $presentServingsPopover, value: $viewModel.observableRecipeDetail.recipeYield, items: 1..<99, title: "Servings", titleKey: "Servings")
|
||||
PickerPopoverView(isPresented: $presentServingsPopover, value: $viewModel.recipe.recipeYield, items: 1..<99, title: "Servings", titleKey: "Servings")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ struct RecipeMetadataSection: View {
|
||||
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.primary.opacity(0.1)))
|
||||
.padding([.horizontal, .bottom], 5)
|
||||
.sheet(isPresented: $presentKeywordSheet) {
|
||||
KeywordPickerView(title: "Keywords", searchSuggestions: appState.allKeywords, selection: $viewModel.observableRecipeDetail.keywords)
|
||||
KeywordPickerView(title: "Keywords", searchSuggestions: cookbookState.selectedAccountState.keywords, selection: $viewModel.recipe.keywords)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,22 +126,22 @@ fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, C
|
||||
// MARK: - RecipeView More Information Section
|
||||
|
||||
struct MoreInformationSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandInfoSection) {
|
||||
VStack(alignment: .leading) {
|
||||
if let dateCreated = viewModel.recipeDetail.dateCreated {
|
||||
if let dateCreated = viewModel.recipe.dateCreated {
|
||||
Text("Created: \(Date.convertISOStringToLocalString(isoDateString: dateCreated) ?? "")")
|
||||
}
|
||||
if let dateModified = viewModel.recipeDetail.dateModified {
|
||||
if let dateModified = viewModel.recipe.dateModified {
|
||||
Text("Last modified: \(Date.convertISOStringToLocalString(isoDateString: dateModified) ?? "")")
|
||||
}
|
||||
if viewModel.observableRecipeDetail.url != "", let url = URL(string: viewModel.observableRecipeDetail.url) {
|
||||
if viewModel.recipe.url != "", let url = URL(string: viewModel.recipe.url ?? "") {
|
||||
HStack(alignment: .top) {
|
||||
Text("URL:")
|
||||
Link(destination: url) {
|
||||
Text(viewModel.observableRecipeDetail.url)
|
||||
Text(viewModel.recipe.url ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,3 +157,5 @@ struct MoreInformationSection: View {
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,9 +9,9 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Nutrition Section
|
||||
|
||||
/*
|
||||
struct RecipeNutritionSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandNutritionSection) {
|
||||
@@ -28,7 +28,7 @@ struct RecipeNutritionSection: View {
|
||||
} else if !nutritionEmpty() {
|
||||
VStack(alignment: .leading) {
|
||||
ForEach(Nutrition.allCases, id: \.self) { nutrition in
|
||||
if let value = viewModel.observableRecipeDetail.nutrition[nutrition.dictKey], nutrition.dictKey != Nutrition.servingSize.dictKey {
|
||||
if let value = viewModel.recipe.nutrition[nutrition.dictKey], nutrition.dictKey != Nutrition.servingSize.dictKey {
|
||||
HStack(alignment: .top) {
|
||||
Text("\(nutrition.localizedDescription): \(value)")
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -43,7 +43,7 @@ struct RecipeNutritionSection: View {
|
||||
}
|
||||
} title: {
|
||||
HStack {
|
||||
if let servingSize = viewModel.observableRecipeDetail.nutrition["servingSize"] {
|
||||
if let servingSize = viewModel.recipe.nutrition["servingSize"] {
|
||||
SecondaryLabel(text: "Nutrition (\(servingSize))")
|
||||
} else {
|
||||
SecondaryLabel(text: LocalizedStringKey("Nutrition"))
|
||||
@@ -56,17 +56,19 @@ struct RecipeNutritionSection: View {
|
||||
|
||||
func binding(for key: String) -> Binding<String> {
|
||||
Binding(
|
||||
get: { viewModel.observableRecipeDetail.nutrition[key, default: ""] },
|
||||
set: { viewModel.observableRecipeDetail.nutrition[key] = $0 }
|
||||
get: { viewModel.recipe.nutrition[key, default: ""] },
|
||||
set: { viewModel.recipe.nutrition[key] = $0 }
|
||||
)
|
||||
}
|
||||
|
||||
func nutritionEmpty() -> Bool {
|
||||
for nutrition in Nutrition.allCases {
|
||||
if let value = viewModel.observableRecipeDetail.nutrition[nutrition.dictKey] {
|
||||
if let value = viewModel.recipe.nutrition[nutrition.dictKey] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -9,9 +9,9 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - RecipeView Tool Section
|
||||
|
||||
/*
|
||||
struct RecipeToolSection: View {
|
||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||
@State var viewModel: RecipeView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
@@ -20,7 +20,7 @@ struct RecipeToolSection: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
RecipeListSection(list: $viewModel.observableRecipeDetail.tool)
|
||||
RecipeListSection(list: $viewModel.recipe.tool)
|
||||
|
||||
if viewModel.editMode {
|
||||
Button {
|
||||
@@ -35,3 +35,5 @@ struct RecipeToolSection: View {
|
||||
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
|
||||
|
||||
struct ShareView: View {
|
||||
@State var recipeDetail: RecipeDetail
|
||||
@State var recipeDetail: CookbookApiRecipeDetailV1
|
||||
@State var recipeImage: UIImage?
|
||||
@Binding var presentShareSheet: Bool
|
||||
|
||||
|
||||
Reference in New Issue
Block a user