Adopt modern SwiftUI patterns now that the minimum target is iOS 18: NavigationStack, .toolbar, .tint, new Tab API with sidebarAdaptable style, and remove iOS 17 availability checks. Add Liquid Glass effect support for iOS 26 in TimerView and fix an optional interpolation warning in AppState. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
158 lines
4.2 KiB
Swift
158 lines
4.2 KiB
Swift
//
|
|
// RecipeSectionStructureViews.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
// Created by Vincent Meilinger on 01.03.24.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
// MARK: - RecipeView Generic Editable View Elements
|
|
|
|
|
|
struct RecipeListSection: View {
|
|
@Binding var list: [String]
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
ForEach(list, id: \.self) { item in
|
|
HStack(alignment: .top) {
|
|
Text("\u{2022}")
|
|
Text("\(item)")
|
|
.multilineTextAlignment(.leading)
|
|
}
|
|
.padding(4)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct SecondaryLabel: View {
|
|
let text: LocalizedStringKey
|
|
var body: some View {
|
|
Text(text)
|
|
.foregroundColor(.secondary)
|
|
.font(.headline)
|
|
.padding(.vertical, 5)
|
|
}
|
|
}
|
|
|
|
|
|
struct EditableText: View {
|
|
@Binding var text: String
|
|
@Binding var editMode: Bool
|
|
@State var titleKey: LocalizedStringKey = ""
|
|
@State var lineLimit: ClosedRange<Int> = 0...1
|
|
@State var axis: Axis = .horizontal
|
|
|
|
var body: some View {
|
|
if editMode {
|
|
TextField(titleKey, text: $text, axis: axis)
|
|
.textFieldStyle(.roundedBorder)
|
|
.lineLimit(lineLimit)
|
|
} else {
|
|
Text(text)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct EditableListView: View {
|
|
@Binding var isPresented: Bool
|
|
@Binding var items: [String]
|
|
@State var title: LocalizedStringKey
|
|
@State var emptyListText: LocalizedStringKey
|
|
@State var titleKey: LocalizedStringKey = ""
|
|
@State var lineLimit: ClosedRange<Int> = 0...50
|
|
@State var axis: Axis = .vertical
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ZStack {
|
|
List {
|
|
if items.isEmpty {
|
|
Text(emptyListText)
|
|
} else {
|
|
ForEach(items.indices, id: \.self) { ix in
|
|
TextField(titleKey, text: $items[ix], axis: axis)
|
|
.lineLimit(lineLimit)
|
|
.padding(5)
|
|
}
|
|
.onDelete(perform: deleteItem)
|
|
.onMove(perform: moveItem)
|
|
.scrollDismissesKeyboard(.immediately)
|
|
|
|
}
|
|
}
|
|
VStack {
|
|
Spacer()
|
|
|
|
Button {
|
|
addItem()
|
|
} label: {
|
|
Image(systemName: "plus")
|
|
.foregroundStyle(.white)
|
|
.bold()
|
|
.padding()
|
|
.background(Circle().fill(Color.nextcloudBlue))
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
.navigationTitle(title)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button(action: { isPresented = false }) {
|
|
Text("Done")
|
|
}
|
|
}
|
|
}
|
|
.environment(\.editMode, .constant(.active))
|
|
}
|
|
}
|
|
|
|
private func addItem() {
|
|
withAnimation {
|
|
items.append("")
|
|
}
|
|
}
|
|
|
|
private func deleteItem(at offsets: IndexSet) {
|
|
withAnimation {
|
|
items.remove(atOffsets: offsets)
|
|
}
|
|
}
|
|
|
|
private func moveItem(from source: IndexSet, to destination: Int) {
|
|
withAnimation {
|
|
items.move(fromOffsets: source, toOffset: destination)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Previews
|
|
|
|
struct EditableListView_Previews: PreviewProvider {
|
|
// Sample keywords for preview
|
|
@State static var sampleList: [String] = [
|
|
/*"3 Eggs",
|
|
"1 kg Potatos",
|
|
"3 g Sugar",
|
|
"1 ml Milk",
|
|
"Salt, Pepper"*/
|
|
]
|
|
|
|
static var previews: some View {
|
|
Color.white
|
|
.sheet(isPresented: .constant(true), content: {
|
|
EditableListView(isPresented: .constant(true), items: $sampleList, title: "Ingredient", emptyListText: "Add cooking steps for fellow chefs to follow.")
|
|
})
|
|
|
|
}
|
|
}
|