Recipes are now searchable
@@ -1,109 +1,109 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cookbook-20@2x.png",
|
||||
"filename" : "cookbook-icon-20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-20@3x.png",
|
||||
"filename" : "cookbook-icon-20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-29@2x.png",
|
||||
"filename" : "cookbook-icon-29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-29@3x.png",
|
||||
"filename" : "cookbook-icon-29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-40@2x.png",
|
||||
"filename" : "cookbook-icon-40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-40@3x.png",
|
||||
"filename" : "cookbook-icon-40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-60@2x.png",
|
||||
"filename" : "cookbook-icon-60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-60@3x.png",
|
||||
"filename" : "cookbook-icon-60@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-20.png",
|
||||
"filename" : "cookbook-icon-20.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-20@2x.png",
|
||||
"filename" : "cookbook-icon-20@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-29.png",
|
||||
"filename" : "cookbook-icon-29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-29@2x.png",
|
||||
"filename" : "cookbook-icon-29@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-40.png",
|
||||
"filename" : "cookbook-icon-40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-40@2x.png",
|
||||
"filename" : "cookbook-icon-40@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-76.png",
|
||||
"filename" : "cookbook-icon-76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-76@2x.png",
|
||||
"filename" : "cookbook-icon-76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-83.5@2x.png",
|
||||
"filename" : "cookbook-icon-83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-1024.png",
|
||||
"filename" : "cookbook-icon-1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
21
Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-category.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cookbook-category.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-category.imageset/cookbook-category.png
vendored
Normal file
|
After Width: | Height: | Size: 86 KiB |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cookbook-icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
BIN
Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/cookbook-icon.png
vendored
Normal file
|
After Width: | Height: | Size: 58 KiB |
21
Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-recipe.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-recipe.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-recipe.imageset/cookbook-recipe.png
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
@@ -18,17 +18,24 @@ extension Formatter {
|
||||
func formatDate(duration: String) -> String {
|
||||
var duration = duration
|
||||
if duration.hasPrefix("PT") { duration.removeFirst(2) }
|
||||
let hour, minute, second: Double
|
||||
var hour: Int = 0, minute: Int = 0
|
||||
if let index = duration.firstIndex(of: "H") {
|
||||
hour = Double(duration[..<index]) ?? 0
|
||||
hour = Int(duration[..<index]) ?? 0
|
||||
duration.removeSubrange(...index)
|
||||
} else { hour = 0 }
|
||||
}
|
||||
if let index = duration.firstIndex(of: "M") {
|
||||
minute = Double(duration[..<index]) ?? 0
|
||||
minute = Int(duration[..<index]) ?? 0
|
||||
duration.removeSubrange(...index)
|
||||
} else { minute = 0 }
|
||||
if let index = duration.firstIndex(of: "S") {
|
||||
second = Double(duration[..<index]) ?? 0
|
||||
} else { second = 0 }
|
||||
return Formatter.positional.string(from: hour * 3600 + minute * 60 + second) ?? "0:00"
|
||||
}
|
||||
|
||||
if hour == 0 && minute != 0 {
|
||||
return "\(minute)min"
|
||||
}
|
||||
if hour != 0 && minute == 0 {
|
||||
return "\(hour)h"
|
||||
}
|
||||
if hour != 0 && minute != 0 {
|
||||
return "\(hour)h \(minute)"
|
||||
}
|
||||
return "--"
|
||||
}
|
||||
|
||||
@@ -13,23 +13,19 @@ struct CategoryCardView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Image("CookBook")
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
Image("cookbook-category")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.overlay(
|
||||
VStack {
|
||||
Spacer()
|
||||
Color.clear
|
||||
.background(
|
||||
.ultraThickMaterial
|
||||
)
|
||||
.overlay(
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
.font(.headline)
|
||||
)
|
||||
.frame(maxHeight: 25)
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
.font(.headline)
|
||||
.lineLimit(2)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
}
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,17 @@ import SwiftUI
|
||||
|
||||
struct RecipeBookView: View {
|
||||
@State var categoryName: String
|
||||
@State var searchText: String = ""
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack {
|
||||
if let recipes = viewModel.recipes[categoryName] {
|
||||
ForEach(recipes, id: \.recipe_id) { recipe in
|
||||
NavigationLink(destination: RecipeDetailView(viewModel: viewModel, recipe: recipe)) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
ForEach(recipesFiltered(), id: \.recipe_id) { recipe in
|
||||
NavigationLink(destination: RecipeDetailView(viewModel: viewModel, recipe: recipe)) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +43,7 @@ struct RecipeBookView: View {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchText, prompt: "Search recipes")
|
||||
.task {
|
||||
await viewModel.loadRecipeList(categoryName: categoryName)
|
||||
}
|
||||
@@ -52,6 +52,15 @@ struct RecipeBookView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func recipesFiltered() -> [Recipe] {
|
||||
guard let recipes = viewModel.recipes[categoryName] else { return [] }
|
||||
guard searchText != "" else { return recipes }
|
||||
return recipes.filter { recipe in
|
||||
recipe.name.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func downloadRecipes() {
|
||||
if let recipes = viewModel.recipes[categoryName] {
|
||||
let dispatchQueue = DispatchQueue(label: "RecipeDownload", qos: .background)
|
||||
|
||||
81
Nextcloud Cookbook iOS Client/Views/KeywordPickerView.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// KeywordPickerView.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 03.10.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct KeywordPickerView: View {
|
||||
@State var title: String
|
||||
@State var searchSuggestions: [String]
|
||||
@Binding var selection: [String]
|
||||
@State var searchText: String = ""
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 120), spacing: 0)]
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
TextField(title, text: $searchText)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding()
|
||||
LazyVGrid(columns: columns, spacing: 5) {
|
||||
if searchText != "" {
|
||||
HStack {
|
||||
if selection.contains(searchText) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
}
|
||||
Text(searchText)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(Color("backgroundHighlight"))
|
||||
)
|
||||
.onTapGesture {
|
||||
if selection.contains(searchText) {
|
||||
selection.removeAll(where: { s in
|
||||
s == searchText ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(searchText)
|
||||
searchSuggestions.append(searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(suggestionsFiltered(), id: \.self) { suggestion in
|
||||
HStack {
|
||||
if selection.contains(suggestion) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
}
|
||||
Text(suggestion)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(Color("backgroundHighlight"))
|
||||
)
|
||||
.onTapGesture {
|
||||
if selection.contains(suggestion) {
|
||||
selection.removeAll(where: { s in
|
||||
s == suggestion ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(suggestion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
func suggestionsFiltered() -> [String] {
|
||||
guard searchText != "" else { return searchSuggestions }
|
||||
return searchSuggestions.filter { suggestion in
|
||||
suggestion.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ struct MainView: View {
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
|
||||
@State var showEditView: Bool = false
|
||||
@State private var showEditView: Bool = false
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 0)]
|
||||
|
||||
var body: some View {
|
||||
@@ -33,6 +33,9 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
/*.navigationDestination(isPresented: $showEditView) {
|
||||
RecipeEditView()
|
||||
}*/
|
||||
.navigationTitle("Cookbooks")
|
||||
.toolbar {
|
||||
Menu {
|
||||
@@ -49,6 +52,7 @@ struct MainView: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
print("Create recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
HStack {
|
||||
@@ -67,6 +71,7 @@ struct MainView: View {
|
||||
.background(
|
||||
NavigationLink(destination: RecipeEditView(), isActive: $showEditView) { EmptyView() }
|
||||
)
|
||||
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.task {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct WelcomeTab: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image("CookBook")
|
||||
Image("cookbook-icon")
|
||||
.resizable()
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
|
||||
@@ -16,7 +16,7 @@ struct RecipeCardView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(uiImage: recipeThumb ?? UIImage(named: "CookBook")!)
|
||||
Image(uiImage: recipeThumb ?? UIImage(named: "cookbook-recipe")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
@@ -35,7 +35,7 @@ struct RecipeCardView: View {
|
||||
}
|
||||
}
|
||||
.background(Color.backgroundHighlight)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 17))
|
||||
.padding(.horizontal)
|
||||
.task {
|
||||
recipeThumb = await viewModel.loadImage(recipeId: recipe.recipe_id, thumb: true)
|
||||
|
||||
@@ -7,60 +7,124 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
|
||||
struct RecipeEditView: View {
|
||||
@State var recipe: RecipeDetail
|
||||
@State var recipe: RecipeDetail = RecipeDetail()
|
||||
|
||||
@State var image: PhotosPickerItem? = nil
|
||||
@State var times = [Date.zero, Date.zero, Date.zero]
|
||||
@State var searchText: String = ""
|
||||
@State var keywords: [String] = []
|
||||
|
||||
init(recipe: RecipeDetail? = nil) {
|
||||
|
||||
self.recipe = recipe ?? RecipeDetail()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Title", text: $recipe.name)
|
||||
TextField("Description", text: $recipe.description)
|
||||
PhotosPicker(selection: $image, matching: .images, photoLibrary: .shared()) {
|
||||
Image(systemName: "photo")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Section() {
|
||||
NavigationLink("Keywords") {
|
||||
KeywordPickerView(title: "Keyword", searchSuggestions: [], selection: $keywords)
|
||||
}
|
||||
} header: {
|
||||
Text("Keywords")
|
||||
} footer: {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(keywords, id: \.self) { keyword in
|
||||
Text(keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section() {
|
||||
Picker("Yield/Portions:", selection: $recipe.recipeYield) {
|
||||
ForEach(0..<99, id: \.self) { i in
|
||||
Text("\(i)").tag(i)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
DatePicker("Prep time:", selection: $times[0], displayedComponents: .hourAndMinute)
|
||||
DatePicker("Cook time:", selection: $times[1], displayedComponents: .hourAndMinute)
|
||||
DatePicker("Total time:", selection: $times[2], displayedComponents: .hourAndMinute)
|
||||
}
|
||||
|
||||
Section() {
|
||||
|
||||
List {
|
||||
ForEach(recipe.recipeInstructions.indices, id: \.self) { ix in
|
||||
HStack(alignment: .top) {
|
||||
Text("\(ix+1).")
|
||||
TextEditor(text: $recipe.recipeInstructions[ix])
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
.onMove { indexSet, offset in
|
||||
recipe.recipeInstructions.move(fromOffsets: indexSet, toOffset: offset)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
recipe.recipeInstructions.remove(atOffsets: indexSet)
|
||||
EditableListSection(title: "Ingredients", items: $recipe.recipeIngredient)
|
||||
EditableListSection(title: "Tools", items: $recipe.tool)
|
||||
EditableListSection(title: "Instructions", items: $recipe.recipeInstructions)
|
||||
}.navigationTitle("New Recipe")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct SearchField: View {
|
||||
@State var title: String
|
||||
@State var text: String
|
||||
@State var searchSuggestions: [String]
|
||||
|
||||
var body: some View {
|
||||
TextField(title, text: $text)
|
||||
.searchSuggestions {
|
||||
ForEach(searchSuggestions, id: \.self) { suggestion in
|
||||
Text(suggestion).searchCompletion(suggestion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct EditableListSection: View {
|
||||
@State var title: String
|
||||
@Binding var items: [String]
|
||||
|
||||
var body: some View {
|
||||
Section() {
|
||||
List {
|
||||
ForEach(items.indices, id: \.self) { ix in
|
||||
HStack(alignment: .top) {
|
||||
Text("\(ix+1).")
|
||||
.padding(.vertical, 10)
|
||||
TextEditor(text: $items[ix])
|
||||
.multilineTextAlignment(.leading)
|
||||
.textFieldStyle(.plain)
|
||||
.padding(.vertical, 1)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Add instruction")
|
||||
Button() {
|
||||
recipe.recipeInstructions.append("")
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
.onMove { indexSet, offset in
|
||||
items.move(fromOffsets: indexSet, toOffset: offset)
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Ingredients")
|
||||
Spacer()
|
||||
EditButton()
|
||||
.onDelete { indexSet in
|
||||
items.remove(atOffsets: indexSet)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Add")
|
||||
Button() {
|
||||
items.append("")
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
Text(title)
|
||||
Spacer()
|
||||
EditButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,12 +136,12 @@ struct TimePicker: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Picker("", selection: $hours){
|
||||
Picker("", selection: $hours) {
|
||||
ForEach(0..<99, id: \.self) { i in
|
||||
Text("\(i) hours").tag(i)
|
||||
}
|
||||
}.pickerStyle(.wheel)
|
||||
Picker("", selection: $minutes){
|
||||
Picker("", selection: $minutes) {
|
||||
ForEach(0..<60, id: \.self) { i in
|
||||
Text("\(i) min").tag(i)
|
||||
}
|
||||
@@ -86,3 +150,4 @@ struct TimePicker: View {
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ struct SettingsView: View {
|
||||
}
|
||||
.tint(.red)
|
||||
|
||||
Button("Delete local data.") {
|
||||
Button("Delete local data") {
|
||||
print("Clear cache.")
|
||||
alertType = .DELETE_CACHE
|
||||
showAlert = true
|
||||
@@ -72,7 +72,9 @@ struct SettingsView: View {
|
||||
.tint(.red)
|
||||
|
||||
} header: {
|
||||
Text("Danger Zone")
|
||||
Text("Other")
|
||||
} footer: {
|
||||
Text("Deleting local data will not affect the recipe data stored on your server.")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
|
||||