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:
@@ -21,23 +21,70 @@ struct RecipeDurationSection: View {
|
||||
DurationView(time: viewModel.observableRecipeDetail.cookTime, title: LocalizedStringKey("Cooking"))
|
||||
DurationView(time: viewModel.observableRecipeDetail.totalTime, title: LocalizedStringKey("Total time"))
|
||||
}
|
||||
if viewModel.editMode {
|
||||
Button {
|
||||
presentPopover.toggle()
|
||||
} label: {
|
||||
Text("Edit")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.popover(isPresented: $presentPopover) {
|
||||
EditableDurationView(
|
||||
prepTime: viewModel.observableRecipeDetail.prepTime,
|
||||
cookTime: viewModel.observableRecipeDetail.cookTime,
|
||||
totalTime: viewModel.observableRecipeDetail.totalTime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Recipe Edit Duration Section (Form-based)
|
||||
|
||||
struct RecipeEditDurationSection: View {
|
||||
@ObservedObject var prepTime: DurationComponents
|
||||
@ObservedObject var cookTime: DurationComponents
|
||||
@ObservedObject var totalTime: DurationComponents
|
||||
|
||||
var body: some View {
|
||||
Section("Duration") {
|
||||
DurationPickerRow(label: "Preparation", time: prepTime)
|
||||
DurationPickerRow(label: "Cooking", time: cookTime)
|
||||
DurationPickerRow(label: "Total time", time: totalTime)
|
||||
}
|
||||
.onChange(of: prepTime.hourComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: prepTime.minuteComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: cookTime.hourComponent) { _ in updateTotalTime() }
|
||||
.onChange(of: cookTime.minuteComponent) { _ in updateTotalTime() }
|
||||
}
|
||||
|
||||
private func updateTotalTime() {
|
||||
var hourComponent = prepTime.hourComponent + cookTime.hourComponent
|
||||
var minuteComponent = prepTime.minuteComponent + cookTime.minuteComponent
|
||||
if minuteComponent >= 60 {
|
||||
hourComponent += minuteComponent / 60
|
||||
minuteComponent %= 60
|
||||
}
|
||||
totalTime.hourComponent = hourComponent
|
||||
totalTime.minuteComponent = minuteComponent
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct DurationPickerRow: View {
|
||||
let label: LocalizedStringKey
|
||||
@ObservedObject var time: DurationComponents
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
Spacer()
|
||||
Menu {
|
||||
ForEach(0..<25, id: \.self) { hour in
|
||||
Button("\(hour) h") {
|
||||
time.hourComponent = hour
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("\(time.hourComponent) h")
|
||||
.monospacedDigit()
|
||||
}
|
||||
Menu {
|
||||
ForEach(0..<60, id: \.self) { minute in
|
||||
Button("\(minute) min") {
|
||||
time.minuteComponent = minute
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("\(time.minuteComponent) min")
|
||||
.monospacedDigit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user