Redesign recipe creation and edit view with Form-based layout and URL import
Replace the single "+" button with a 2-option menu (Create New Recipe / Import from URL) across RecipeTabView, RecipeListView, and AllRecipesListView. Add ImportURLSheet for server-side recipe import with loading and error states. Completely redesign edit mode to use a native Form layout with inline editing for all sections (metadata, duration, ingredients, instructions, tools, nutrition) instead of the previous sheet-based EditableListView approach. Move delete action from edit toolbar to view mode context menu. Add recipe image display to the edit form. Refactor RecipeListView and AllRecipesListView to use closure-based callbacks instead of Binding<Bool> for the create/import actions. Add preloadedRecipeDetail support to RecipeView.ViewModel for imported recipes. Add DE/ES/FR translations for all new UI strings. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,13 +18,6 @@ struct RecipeTabView: View {
|
||||
|
||||
private let gridColumns = [GridItem(.adaptive(minimum: 160), spacing: 12)]
|
||||
|
||||
private var showEditViewBinding: Binding<Bool> {
|
||||
Binding(
|
||||
get: { false },
|
||||
set: { if $0 { viewModel.navigateToNewRecipe() } }
|
||||
)
|
||||
}
|
||||
|
||||
private var nonEmptyCategories: [Category] {
|
||||
appState.categories.filter { $0.recipe_count > 0 }
|
||||
}
|
||||
@@ -112,21 +105,34 @@ struct RecipeTabView: View {
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
case .newRecipe:
|
||||
RecipeView(viewModel: RecipeView.ViewModel())
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
RecipeView(viewModel: {
|
||||
let vm = RecipeView.ViewModel()
|
||||
if let imported = viewModel.importedRecipeDetail {
|
||||
vm.preloadedRecipeDetail = imported
|
||||
}
|
||||
return vm
|
||||
}())
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
.onAppear {
|
||||
viewModel.importedRecipeDetail = nil
|
||||
}
|
||||
case .category(let category):
|
||||
RecipeListView(
|
||||
categoryName: category.name,
|
||||
showEditView: showEditViewBinding
|
||||
onCreateNew: { viewModel.navigateToNewRecipe() },
|
||||
onImportFromURL: { viewModel.showImportURLSheet = true }
|
||||
)
|
||||
.id(category.id)
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
case .allRecipes:
|
||||
AllRecipesListView(showEditView: showEditViewBinding)
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
AllRecipesListView(
|
||||
onCreateNew: { viewModel.navigateToNewRecipe() },
|
||||
onImportFromURL: { viewModel.showImportURLSheet = true }
|
||||
)
|
||||
.environmentObject(appState)
|
||||
.environmentObject(groceryList)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Recipe.self) { recipe in
|
||||
@@ -138,17 +144,27 @@ struct RecipeTabView: View {
|
||||
} detail: {
|
||||
NavigationStack {
|
||||
if viewModel.showAllRecipesInDetail {
|
||||
AllRecipesListView(showEditView: showEditViewBinding)
|
||||
AllRecipesListView(
|
||||
onCreateNew: { viewModel.navigateToNewRecipe() },
|
||||
onImportFromURL: { viewModel.showImportURLSheet = true }
|
||||
)
|
||||
} else if let category = viewModel.selectedCategory {
|
||||
RecipeListView(
|
||||
categoryName: category.name,
|
||||
showEditView: showEditViewBinding
|
||||
onCreateNew: { viewModel.navigateToNewRecipe() },
|
||||
onImportFromURL: { viewModel.showImportURLSheet = true }
|
||||
)
|
||||
.id(category.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.sheet(isPresented: $viewModel.showImportURLSheet) {
|
||||
ImportURLSheet { recipeDetail in
|
||||
viewModel.navigateToImportedRecipe(recipeDetail: recipeDetail)
|
||||
}
|
||||
.environmentObject(appState)
|
||||
}
|
||||
.task {
|
||||
let connection = await appState.checkServerConnection()
|
||||
DispatchQueue.main.async {
|
||||
@@ -185,6 +201,9 @@ struct RecipeTabView: View {
|
||||
@Published var selectedCategory: Category? = nil
|
||||
@Published var showAllRecipesInDetail: Bool = false
|
||||
|
||||
@Published var showImportURLSheet: Bool = false
|
||||
@Published var importedRecipeDetail: RecipeDetail? = nil
|
||||
|
||||
func navigateToSettings() {
|
||||
sidebarPath.append(SidebarDestination.settings)
|
||||
}
|
||||
@@ -193,6 +212,11 @@ struct RecipeTabView: View {
|
||||
sidebarPath.append(SidebarDestination.newRecipe)
|
||||
}
|
||||
|
||||
func navigateToImportedRecipe(recipeDetail: RecipeDetail) {
|
||||
importedRecipeDetail = recipeDetail
|
||||
sidebarPath.append(SidebarDestination.newRecipe)
|
||||
}
|
||||
|
||||
func navigateToCategory(_ category: Category) {
|
||||
selectedCategory = category
|
||||
showAllRecipesInDetail = false
|
||||
@@ -273,9 +297,18 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent {
|
||||
|
||||
// Create new recipes
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
Logger.view.debug("Add new recipe")
|
||||
viewModel.navigateToNewRecipe()
|
||||
Menu {
|
||||
Button {
|
||||
Logger.view.debug("Add new recipe")
|
||||
viewModel.navigateToNewRecipe()
|
||||
} label: {
|
||||
Label("Create New Recipe", systemImage: "square.and.pencil")
|
||||
}
|
||||
Button {
|
||||
viewModel.showImportURLSheet = true
|
||||
} label: {
|
||||
Label("Import from URL", systemImage: "link")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user