Created RecipeEditViewModel to cleanup RecipeEditView

This commit is contained in:
Vicnet
2023-11-11 12:11:13 +01:00
parent 13b025771c
commit 63730732b6
6 changed files with 256 additions and 197 deletions

View File

@@ -35,6 +35,7 @@
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; }; A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; };
A76B8A712AE002AE00096CEC /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A702AE002AE00096CEC /* Alerts.swift */; }; A76B8A712AE002AE00096CEC /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A702AE002AE00096CEC /* Alerts.swift */; };
A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */; }; A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */; };
A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79AA8E12AFF8C14007D25F2 /* RecipeEditViewModel.swift */; };
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; }; A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; };
A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; }; A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; };
A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; }; A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; };
@@ -90,6 +91,7 @@
A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; }; A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; };
A76B8A702AE002AE00096CEC /* Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; }; A76B8A702AE002AE00096CEC /* Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; };
A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationComponents.swift; sourceTree = "<group>"; }; A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationComponents.swift; sourceTree = "<group>"; };
A79AA8E12AFF8C14007D25F2 /* RecipeEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeEditViewModel.swift; sourceTree = "<group>"; };
A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; }; A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordPickerView.swift; sourceTree = "<group>"; }; A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordPickerView.swift; sourceTree = "<group>"; };
A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = "<group>"; }; A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = "<group>"; };
@@ -201,6 +203,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A70171AC2AA8EF4700064C43 /* MainViewModel.swift */, A70171AC2AA8EF4700064C43 /* MainViewModel.swift */,
A79AA8E12AFF8C14007D25F2 /* RecipeEditViewModel.swift */,
); );
path = ViewModels; path = ViewModels;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -398,6 +401,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */,
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */, A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */,
A70171B12AB211DF00064C43 /* CustomError.swift in Sources */, A70171B12AB211DF00064C43 /* CustomError.swift in Sources */,
A76B8A712AE002AE00096CEC /* Alerts.swift in Sources */, A76B8A712AE002AE00096CEC /* Alerts.swift in Sources */,

View File

@@ -883,6 +883,9 @@
} }
} }
} }
},
"Import" : {
}, },
"Import Recipe" : { "Import Recipe" : {

View File

@@ -0,0 +1,193 @@
//
// RecipeEditViewModel.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 11.11.23.
//
import Foundation
import SwiftUI
@MainActor class RecipeEditViewModel: ObservableObject {
@ObservedObject var mainViewModel: MainViewModel
@Published var isPresented: Binding<Bool>
@Published var recipe: RecipeDetail = RecipeDetail()
@Published var prepDuration: DurationComponents = DurationComponents()
@Published var cookDuration: DurationComponents = DurationComponents()
@Published var totalDuration: DurationComponents = DurationComponents()
@Published var searchText: String = ""
@Published var keywords: [String] = []
@Published var keywordSuggestions: [String] = []
@Published var showImportSection: Bool = false
@Published var importURL: String = ""
@Published var presentAlert = false
var alertType: UserAlert = RecipeCreationError.GENERIC
var alertAction: @MainActor () -> () = {}
var uploadNew: Bool = true
var waitingForUpload: Bool = false
init(mainViewModel: MainViewModel, isPresented: Binding<Bool>, uploadNew: Bool) {
self.mainViewModel = mainViewModel
self.isPresented = isPresented
self.uploadNew = uploadNew
}
init(mainViewModel: MainViewModel, recipeDetail: RecipeDetail, isPresented: Binding<Bool>, uploadNew: Bool) {
self.mainViewModel = mainViewModel
self.recipe = recipeDetail
self.isPresented = isPresented
self.uploadNew = uploadNew
}
func createRecipe() {
self.recipe.prepTime = prepDuration.toPTString()
self.recipe.cookTime = cookDuration.toPTString()
self.recipe.totalTime = totalDuration.toPTString()
self.recipe.setKeywordsFromArray(keywords)
}
func recipeValid() -> Bool {
// Check if the recipe has a name
if recipe.name.replacingOccurrences(of: " ", with: "") == "" {
alertType = RecipeCreationError.NO_TITLE
alertAction = {}
presentAlert = true
return false
}
// Check if the recipe has a unique name
for recipeList in mainViewModel.recipes.values {
for r in recipeList {
if r.name
.replacingOccurrences(of: " ", with: "")
.lowercased() ==
recipe.name
.replacingOccurrences(of: " ", with: "")
.lowercased()
{
alertType = RecipeCreationError.DUPLICATE
alertAction = {}
presentAlert = true
return false
}
}
}
return true
}
func uploadNewRecipe() {
print("Uploading new recipe.")
waitingForUpload = true
createRecipe()
guard recipeValid() else { return }
let request = RequestWrapper.customRequest(
method: .POST,
path: .NEW_RECIPE,
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true),
HeaderField.contentType(value: .JSON)
],
body: JSONEncoder.safeEncode(self.recipe)
)
sendRequest(request)
dismissEditView()
}
func uploadEditedRecipe() {
waitingForUpload = true
print("Uploading changed recipe.")
guard let recipeId = Int(recipe.id) else { return }
createRecipe()
let request = RequestWrapper.customRequest(
method: .PUT,
path: .RECIPE_DETAIL(recipeId: recipeId),
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true),
HeaderField.contentType(value: .JSON)
],
body: JSONEncoder.safeEncode(self.recipe)
)
sendRequest(request)
dismissEditView()
}
func deleteRecipe() {
guard let recipeId = Int(recipe.id) else { return }
let request = RequestWrapper.customRequest(
method: .DELETE,
path: .RECIPE_DETAIL(recipeId: recipeId),
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true)
]
)
sendRequest(request)
if let recipeIdInt = Int(recipe.id) {
mainViewModel.deleteRecipe(withId: recipeIdInt, categoryName: recipe.recipeCategory)
}
dismissEditView()
}
func sendRequest(_ request: RequestWrapper) {
Task {
guard let apiController = mainViewModel.apiController else { return }
let (data, _): (Data?, Error?) = await apiController.sendDataRequest(request)
guard let data = data else { return }
do {
let error = try JSONDecoder().decode(ServerMessage.self, from: data)
// TODO: Better error handling (Show error to user!)
} catch {
}
}
}
func dismissEditView() {
Task {
await mainViewModel.loadCategoryList(needsUpdate: true)
await mainViewModel.loadRecipeList(categoryName: recipe.recipeCategory, needsUpdate: true)
}
isPresented.wrappedValue = false
}
func prepareView() {
if let prepTime = recipe.prepTime {
prepDuration.fromPTString(prepTime)
}
if let cookTime = recipe.cookTime {
cookDuration.fromPTString(cookTime)
}
if let totalTime = recipe.totalTime {
totalDuration.fromPTString(totalTime)
}
self.keywords = recipe.getKeywordsArray()
}
func importRecipe() {
Task {
do {
let (scrapedRecipe, error) = try await RecipeScraper().scrape(url: importURL)
if let scrapedRecipe = scrapedRecipe {
self.recipe = scrapedRecipe
prepareView()
}
if let error = error {
self.alertType = error
self.alertAction = {}
self.presentAlert = true
}
} catch {
print("Error")
}
}
}
}

View File

@@ -80,7 +80,13 @@ struct MainView: View {
} }
.tint(.nextcloudBlue) .tint(.nextcloudBlue)
.sheet(isPresented: $showEditView) { .sheet(isPresented: $showEditView) {
RecipeEditView(viewModel: viewModel, isPresented: $showEditView) RecipeEditView(viewModel:
RecipeEditViewModel(
mainViewModel: viewModel,
isPresented: $showEditView,
uploadNew: true
)
)
} }
.task { .task {
self.serverConnection = await viewModel.checkServerConnection() self.serverConnection = await viewModel.checkServerConnection()

View File

@@ -95,7 +95,14 @@ struct RecipeDetailView: View {
} }
.sheet(isPresented: $presentEditView) { .sheet(isPresented: $presentEditView) {
if let recipeDetail = recipeDetail { if let recipeDetail = recipeDetail {
RecipeEditView(viewModel: viewModel, recipe: recipeDetail, isPresented: $presentEditView, uploadNew: false) RecipeEditView(viewModel:
RecipeEditViewModel(
mainViewModel: viewModel,
recipeDetail: recipeDetail,
isPresented: $presentEditView,
uploadNew: false
)
)
} }
} }
.task { .task {

View File

@@ -12,43 +12,25 @@ import PhotosUI
struct RecipeEditView: View { struct RecipeEditView: View {
@ObservedObject var viewModel: MainViewModel @ObservedObject var viewModel: RecipeEditViewModel
@State var recipe: RecipeDetail = RecipeDetail()
@Binding var isPresented: Bool
@State var uploadNew: Bool = true
@State private var presentAlert = false
@State private var alertType: UserAlert = RecipeCreationError.GENERIC
@State private var alertAction: () -> () = {}
@StateObject private var prepDuration: DurationComponents = DurationComponents()
@StateObject private var cookDuration: DurationComponents = DurationComponents()
@StateObject private var totalDuration: DurationComponents = DurationComponents()
@State private var searchText: String = ""
@State private var keywords: [String] = []
@State private var keywordSuggestions: [String] = []
@State private var importURL: String = ""
@State private var showImportSection: Bool = false
@State private var waitingForUpload: Bool = false
var body: some View { var body: some View {
NavigationStack { NavigationStack {
VStack { VStack {
HStack { HStack {
Button() { Button() {
isPresented = false viewModel.isPresented.wrappedValue = false
} label: { } label: {
Text("Cancel") Text("Cancel")
.bold() .bold()
} }
if !uploadNew { if !viewModel.uploadNew {
Menu { Menu {
Button { Button {
print("Delete recipe.") print("Delete recipe.")
alertType = RecipeCreationError.CONFIRM_DELETE viewModel.alertType = RecipeCreationError.CONFIRM_DELETE
alertAction = deleteRecipe viewModel.alertAction = viewModel.deleteRecipe
presentAlert = true viewModel.presentAlert = true
} label: { } label: {
Image(systemName: "trash") Image(systemName: "trash")
.foregroundStyle(.red) .foregroundStyle(.red)
@@ -63,10 +45,10 @@ struct RecipeEditView: View {
} }
Spacer() Spacer()
Button() { Button() {
if uploadNew { if viewModel.uploadNew {
uploadNewRecipe() viewModel.uploadNewRecipe()
} else { } else {
uploadEditedRecipe() viewModel.uploadEditedRecipe()
} }
} label: { } label: {
Text("Upload") Text("Upload")
@@ -74,33 +56,23 @@ struct RecipeEditView: View {
} }
}.padding() }.padding()
HStack { HStack {
Text(recipe.name == "" ? LocalizedStringKey("New recipe") : LocalizedStringKey(recipe.name)) Text(viewModel.recipe.name == "" ? LocalizedStringKey("New recipe") : LocalizedStringKey(viewModel.recipe.name))
.font(.title) .font(.title)
.bold() .bold()
.padding() .padding()
Spacer() Spacer()
} }
Form { Form {
if showImportSection { if viewModel.showImportSection {
Section { Section {
TextField("URL (e.g. example.com/recipe)", text: $importURL) TextField("URL (e.g. example.com/recipe)", text: $viewModel.importURL)
.onSubmit { .onSubmit {
Task { viewModel.importRecipe()
do {
let (scrapedRecipe, error) = try await RecipeScraper().scrape(url: importURL)
if let scrapedRecipe = scrapedRecipe {
self.recipe = scrapedRecipe
prepareView()
}
if let error = error {
self.alertType = error
self.alertAction = {}
self.presentAlert = true
}
} catch {
print("Error")
}
} }
Button {
viewModel.importRecipe()
} label: {
Text("Import")
} }
} header: { } header: {
Text("Import Recipe") Text("Import Recipe")
@@ -112,7 +84,7 @@ struct RecipeEditView: View {
Section { Section {
Button() { Button() {
withAnimation{ withAnimation{
showImportSection = true viewModel.showImportSection = true
} }
} label: { } label: {
Text("Import recipe from a website") Text("Import recipe from a website")
@@ -120,33 +92,33 @@ struct RecipeEditView: View {
} }
} }
TextField("Title", text: $recipe.name) TextField("Title", text: $viewModel.recipe.name)
Section { Section {
TextEditor(text: $recipe.description) TextEditor(text: $viewModel.recipe.description)
} header: { } header: {
Text("Description") Text("Description")
} }
Section() { Section() {
NavigationLink(recipe.recipeCategory == "" ? "Category" : "Category: \(recipe.recipeCategory)") { NavigationLink(viewModel.recipe.recipeCategory == "" ? "Category" : "Category: \(viewModel.recipe.recipeCategory)") {
CategoryPickerView( CategoryPickerView(
title: "Category", title: "Category",
searchSuggestions: viewModel.categories.map({ category in searchSuggestions: viewModel.mainViewModel.categories.map({ category in
category.name == "*" ? "Other" : category.name category.name == "*" ? "Other" : category.name
}), }),
selection: $recipe.recipeCategory) selection: $viewModel.recipe.recipeCategory)
} }
NavigationLink("Keywords") { NavigationLink("Keywords") {
KeywordPickerView( KeywordPickerView(
title: "Keywords", title: "Keywords",
searchSuggestions: keywordSuggestions, searchSuggestions: viewModel.keywordSuggestions,
selection: $keywords selection: $viewModel.keywords
) )
} }
} footer: { } footer: {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack { HStack {
ForEach(keywords, id: \.self) { keyword in ForEach(viewModel.keywords, id: \.self) { keyword in
Text(keyword) Text(keyword)
} }
} }
@@ -154,173 +126,47 @@ struct RecipeEditView: View {
} }
Section() { Section() {
Picker("Servings:", selection: $recipe.recipeYield) { Picker("Servings:", selection: $viewModel.recipe.recipeYield) {
ForEach(0..<99, id: \.self) { i in ForEach(0..<99, id: \.self) { i in
Text("\(i)").tag(i) Text("\(i)").tag(i)
} }
} }
.pickerStyle(.menu) .pickerStyle(.menu)
DurationPicker(title: LocalizedStringKey("Preparation duration:"), duration: prepDuration) DurationPicker(title: LocalizedStringKey("Preparation duration:"), duration: viewModel.prepDuration)
DurationPicker(title: LocalizedStringKey("Cooking duration:"), duration: cookDuration) DurationPicker(title: LocalizedStringKey("Cooking duration:"), duration: viewModel.cookDuration)
DurationPicker(title: LocalizedStringKey("Total duration:"), duration: totalDuration) DurationPicker(title: LocalizedStringKey("Total duration:"), duration: viewModel.totalDuration)
} }
EditableListSection(title: LocalizedStringKey("Ingredients"), items: $recipe.recipeIngredient) EditableListSection(title: LocalizedStringKey("Ingredients"), items: $viewModel.recipe.recipeIngredient)
EditableListSection(title: LocalizedStringKey("Tools"), items: $recipe.tool) EditableListSection(title: LocalizedStringKey("Tools"), items: $viewModel.recipe.tool)
EditableListSection(title: LocalizedStringKey("Instructions"), items: $recipe.recipeInstructions) EditableListSection(title: LocalizedStringKey("Instructions"), items: $viewModel.recipe.recipeInstructions)
} }
} }
} }
.task { .task {
self.keywordSuggestions = await viewModel.getKeywords() viewModel.keywordSuggestions = await viewModel.mainViewModel.getKeywords()
} }
.onAppear { .onAppear {
prepareView() viewModel.prepareView()
} }
.alert(alertType.localizedTitle, isPresented: $presentAlert) { .alert(viewModel.alertType.localizedTitle, isPresented: $viewModel.presentAlert) {
ForEach(alertType.alertButtons) { buttonType in ForEach(viewModel.alertType.alertButtons) { buttonType in
if buttonType == .OK { if buttonType == .OK {
Button(AlertButton.OK.rawValue, role: .cancel) { Button(AlertButton.OK.rawValue, role: .cancel) {
alertAction() viewModel.alertAction()
} }
} else if buttonType == .CANCEL { } else if buttonType == .CANCEL {
Button(AlertButton.CANCEL.rawValue, role: .cancel) { } Button(AlertButton.CANCEL.rawValue, role: .cancel) { }
} else if buttonType == .DELETE { } else if buttonType == .DELETE {
Button(AlertButton.DELETE.rawValue, role: .destructive) { Button(AlertButton.DELETE.rawValue, role: .destructive) {
alertAction() viewModel.alertAction()
} }
} }
} }
} message: { } message: {
Text(alertType.localizedDescription) Text(viewModel.alertType.localizedDescription)
} }
} }
func createRecipe() {
self.recipe.prepTime = prepDuration.toPTString()
self.recipe.cookTime = cookDuration.toPTString()
self.recipe.totalTime = totalDuration.toPTString()
self.recipe.setKeywordsFromArray(keywords)
}
func recipeValid() -> Bool {
// Check if the recipe has a name
if recipe.name.replacingOccurrences(of: " ", with: "") == "" {
alertType = RecipeCreationError.NO_TITLE
alertAction = {}
presentAlert = true
return false
}
// Check if the recipe has a unique name
for recipeList in viewModel.recipes.values {
for r in recipeList {
if r.name
.replacingOccurrences(of: " ", with: "")
.lowercased() ==
recipe.name
.replacingOccurrences(of: " ", with: "")
.lowercased()
{
alertType = RecipeCreationError.DUPLICATE
alertAction = {}
presentAlert = true
return false
}
}
}
return true
}
func uploadNewRecipe() {
print("Uploading new recipe.")
waitingForUpload = true
createRecipe()
guard recipeValid() else { return }
let request = RequestWrapper.customRequest(
method: .POST,
path: .NEW_RECIPE,
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true),
HeaderField.contentType(value: .JSON)
],
body: JSONEncoder.safeEncode(self.recipe)
)
sendRequest(request)
dismissEditView()
}
func uploadEditedRecipe() {
waitingForUpload = true
print("Uploading changed recipe.")
guard let recipeId = Int(recipe.id) else { return }
createRecipe()
let request = RequestWrapper.customRequest(
method: .PUT,
path: .RECIPE_DETAIL(recipeId: recipeId),
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true),
HeaderField.contentType(value: .JSON)
],
body: JSONEncoder.safeEncode(self.recipe)
)
sendRequest(request)
dismissEditView()
}
func deleteRecipe() {
guard let recipeId = Int(recipe.id) else { return }
let request = RequestWrapper.customRequest(
method: .DELETE,
path: .RECIPE_DETAIL(recipeId: recipeId),
headerFields: [
HeaderField.accept(value: .JSON),
HeaderField.ocsRequest(value: true)
]
)
sendRequest(request)
if let recipeIdInt = Int(recipe.id) {
viewModel.deleteRecipe(withId: recipeIdInt, categoryName: recipe.recipeCategory)
}
dismissEditView()
}
func sendRequest(_ request: RequestWrapper) {
Task {
guard let apiController = viewModel.apiController else { return }
let (data, _): (Data?, Error?) = await apiController.sendDataRequest(request)
guard let data = data else { return }
do {
let error = try JSONDecoder().decode(ServerMessage.self, from: data)
// TODO: Better error handling (Show error to user!)
} catch {
}
}
}
func dismissEditView() {
Task {
await self.viewModel.loadCategoryList(needsUpdate: true)
await self.viewModel.loadRecipeList(categoryName: self.recipe.recipeCategory, needsUpdate: true)
}
self.isPresented = false
}
func prepareView() {
if let prepTime = recipe.prepTime {
prepDuration.fromPTString(prepTime)
}
if let cookTime = recipe.cookTime {
cookDuration.fromPTString(cookTime)
}
if let totalTime = recipe.totalTime {
totalDuration.fromPTString(totalTime)
}
self.keywords = recipe.getKeywordsArray()
}
} }