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>
90 lines
2.5 KiB
Swift
90 lines
2.5 KiB
Swift
//
|
|
// RecipeInstructionSection.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
// Created by Vincent Meilinger on 01.03.24.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
// MARK: - RecipeView Instructions Section
|
|
|
|
struct RecipeInstructionSection: View {
|
|
@ObservedObject var viewModel: RecipeView.ViewModel
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
HStack {
|
|
SecondaryLabel(text: LocalizedStringKey("Instructions"))
|
|
Spacer()
|
|
}
|
|
ForEach(viewModel.observableRecipeDetail.recipeInstructions.indices, id: \.self) { ix in
|
|
RecipeInstructionListItem(instruction: $viewModel.observableRecipeDetail.recipeInstructions[ix], index: ix+1)
|
|
}
|
|
}
|
|
.padding()
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - Recipe Edit Instruction Section (Form-based)
|
|
|
|
struct RecipeEditInstructionSection: View {
|
|
@Binding var instructions: [String]
|
|
|
|
var body: some View {
|
|
Section {
|
|
ForEach(instructions.indices, id: \.self) { index in
|
|
HStack(alignment: .top) {
|
|
Text("\(index + 1).")
|
|
.foregroundStyle(.secondary)
|
|
.monospacedDigit()
|
|
TextField("Step \(index + 1)", text: $instructions[index], axis: .vertical)
|
|
.lineLimit(1...10)
|
|
Image(systemName: "line.3.horizontal")
|
|
.foregroundStyle(.tertiary)
|
|
.padding(.top, 4)
|
|
}
|
|
}
|
|
.onDelete { indexSet in
|
|
instructions.remove(atOffsets: indexSet)
|
|
}
|
|
.onMove { from, to in
|
|
instructions.move(fromOffsets: from, toOffset: to)
|
|
}
|
|
|
|
Button {
|
|
instructions.append("")
|
|
} label: {
|
|
Label("Add Step", systemImage: "plus.circle.fill")
|
|
}
|
|
} header: {
|
|
Text("Instructions")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fileprivate struct RecipeInstructionListItem: View {
|
|
@Binding var instruction: String
|
|
@State var index: Int
|
|
@State var isSelected: Bool = false
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top) {
|
|
Text("\(index)")
|
|
.monospaced()
|
|
Text(instruction)
|
|
}.padding(4)
|
|
.foregroundStyle(isSelected ? Color.secondary : Color.primary)
|
|
.onTapGesture {
|
|
isSelected.toggle()
|
|
}
|
|
.animation(.easeInOut, value: isSelected)
|
|
}
|
|
}
|
|
|