Keyword suggestions and language support.
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */; };
|
||||
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */; };
|
||||
A70D7CA32AC74B3B00D53DBF /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */; };
|
||||
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; };
|
||||
A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; };
|
||||
A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -83,6 +84,7 @@
|
||||
A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
|
||||
A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeEditView.swift; sourceTree = "<group>"; };
|
||||
A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; 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>"; };
|
||||
A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -142,6 +144,7 @@
|
||||
A70171B22AB211F000064C43 /* Network */,
|
||||
A703226B2ABAF60D00D7C4ED /* Extensions */,
|
||||
A70171852AA8E71F00064C43 /* Assets.xcassets */,
|
||||
A7AEAE632AD5521400135378 /* Localizable.xcstrings */,
|
||||
A70171872AA8E71F00064C43 /* Nextcloud_Cookbook_iOS_Client.entitlements */,
|
||||
A70171882AA8E71F00064C43 /* Preview Content */,
|
||||
);
|
||||
@@ -315,6 +318,7 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
de,
|
||||
);
|
||||
mainGroup = A70171752AA8E71900064C43;
|
||||
productRefGroup = A701717F2AA8E71900064C43 /* Products */;
|
||||
@@ -335,6 +339,7 @@
|
||||
files = (
|
||||
A701718A2AA8E71F00064C43 /* Preview Assets.xcassets in Resources */,
|
||||
A70171862AA8E71F00064C43 /* Assets.xcassets in Resources */,
|
||||
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -556,7 +561,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
@@ -597,7 +602,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
||||
@@ -115,6 +115,11 @@ struct RecipeImage {
|
||||
var full: UIImage?
|
||||
}
|
||||
|
||||
struct RecipeKeyword: Codable {
|
||||
let name: String
|
||||
let recipe_count: Int
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,13 @@ class DataStore {
|
||||
}
|
||||
}
|
||||
|
||||
func delete(path: String) {
|
||||
Task {
|
||||
let fileURL = try Self.fileURL(appending: path)
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
}
|
||||
|
||||
func recipeDetailExists(recipeId: Int) -> Bool {
|
||||
let filePath = "recipe\(recipeId).data"
|
||||
guard let folderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?.path() else { return false }
|
||||
|
||||
@@ -40,11 +40,18 @@ class UserSettings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var language: String {
|
||||
didSet {
|
||||
UserDefaults.standard.set(language, forKey: "language")
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
|
||||
self.token = UserDefaults.standard.object(forKey: "token") as? String ?? ""
|
||||
self.serverAddress = UserDefaults.standard.object(forKey: "serverAddress") as? String ?? ""
|
||||
self.onboarding = UserDefaults.standard.object(forKey: "onboarding") as? Bool ?? true
|
||||
self.defaultCategory = UserDefaults.standard.object(forKey: "defaultCategory") as? String ?? ""
|
||||
self.language = UserDefaults.standard.object(forKey: "language") as? String ?? SupportedLanguage.EN.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
746
Nextcloud Cookbook iOS Client/Localizable.xcstrings
Normal file
746
Nextcloud Cookbook iOS Client/Localizable.xcstrings
Normal file
@@ -0,0 +1,746 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld hours" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld Std."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld min" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld Min."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"•" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "•"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"A recipe with that name already exists." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ein Rezept mit diesem Namen existiert bereits."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"About" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Über uns"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Add" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hinzufügen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"An unknown error occured." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ein unbekannter Fehler ist aufgetreten."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"App Token Login" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "App Token Login"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cancel" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Abbrechen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Category" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kategorie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Category: %@" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kategorie: %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cook time" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kochzeit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cook time:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kochdauer:"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cookbook Client" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cookbook Client"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cookbooks" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kochbücher"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Löschen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete local data" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Lokale Daten löschen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete recipe" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rezept Löschen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete recipe?" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Löschen bestätigen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Deleting local data will not affect the recipe data stored on your server." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Das Löschen lokaler Daten hat keine Auswirkungen auf die Rezeptdaten, die auf Ihrem Server gespeichert sind."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Description" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Beschreibung"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Discoverability" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kategorisierung"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Download all recipes" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Alle Rezepte herunterladen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Download recipes" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rezepte herunterladen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Duplicate recipe." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rezept bereits vorhanden."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bearbeiten"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entering the server address will open a web browser. Please follow the login instructions provided there. If the browser does not open, click the link 'Open in browser'\nAfter a successfull login, return to this application and press 'Validate'." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Das Eingeben der Serveradresse wird einen Webbrowser öffnen. Bitte folgen Sie dort den bereitgestellten Anmeldeanweisungen. Falls der Browser nicht geöffnet wird, klicken Sie auf den Link 'Im Browser öffnen'.\nNach erfolgreicher Anmeldung kehren Sie zu dieser Anwendung zurück und drücken Sie 'Überprüfen'."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Error." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fehler."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"General" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Allgemein"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Get support" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kontakt-Seite öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"If you are interested in contributing to this project or simply wish to review its source code, we encourage you to visit the GitHub repository for this application." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wenn Sie Interesse daran haben, zu diesem Projekt beizutragen oder einfach den Quellcode überprüfen möchten, ermutigen wir Sie, das GitHub-Repository für diese Anwendung zu besuchen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"If you have any inquiries, feedback, or require assistance, please refer to the support page for contact information." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wenn Sie Anfragen oder Rückmeldungen haben, oder Unterstützung benötigen, finden Sie unter diesem Link die Kontaktinformationen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Ingredients" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Zutaten"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Ingredients for %lld servings" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Zutaten für %lld Portionen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Ingredients per serving" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Zutaten pro Portion"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Instructions" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Anleitung"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Keywords" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Schlagwörter"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Language" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sprache"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Log out" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Abmelden"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Login Method" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Login-Methode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Missing recipe name." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fehlender Rezeptname."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Network error." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Netzwerkfehler"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"New recipe" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Neues Rezept"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Nextcloud Login" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nextcloud Login"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"None" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Keines"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Ok" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ok"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Open in browser" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Im Browser öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Other" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Andere"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Please enter a recipe name." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bitte tragen Sie einen Rezeptnamen ein."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Prep time" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vorbereitungszeit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Prep time:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vorbereitungszeit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Search recipes" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rezepte durchsuchen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Select a default cookbook" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wählen Sie ein Standard-Kochbuch"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Selected keywords:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ausgewählte Schlagwörter:"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Settings" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Einstellungen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Submit" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Eingeben"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Support" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kontakt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tank you for downloading" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vielen Dank für's herunterladen!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"The selected cookbook will open on app launch by default." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Das ausgewählte Kochbuch wird standardmäßig beim Start der App geöffnet."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"This action is not reversible!" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Diese Aktion lässt sich nicht Rückgängig machen!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"This application is an open source effort. If you're interested in suggesting or contributing new features, or you encounter any problems, please use the support link or visit the GitHub repository in the app settings." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Diese Anwendung ist ein Open-Source-Projekt. Wenn Sie daran interessiert sind, neue Funktionen vorzuschlagen oder beizutragen, oder wenn Sie auf Probleme stoßen, nutzen Sie bitte den Kontakt-Link oder besuchen Sie das GitHub-Repository in den App-Einstellungen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Title" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tools" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Küchenutensilien"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Total time" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Gesamtdauer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Total time:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Gesamtdauer:"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to upload your recipe. Please check your internet connection." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Es ist nicht möglich, Ihr Rezept hochzuladen. Bitte überprüfen Sie Ihre Internetverbindung."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Upload" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Speichern"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Validate" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Überprüfen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Visit the GitHub page" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "GitHub öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Yield/Portions:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Portionen:"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -19,7 +19,8 @@ enum RequestPath {
|
||||
RECIPE_LIST(categoryName: String),
|
||||
RECIPE_DETAIL(recipeId: Int),
|
||||
NEW_RECIPE,
|
||||
IMAGE(recipeId: Int, thumb: Bool)
|
||||
IMAGE(recipeId: Int, thumb: Bool),
|
||||
KEYWORDS
|
||||
|
||||
case LOGINV2REQ,
|
||||
CUSTOM(path: String),
|
||||
@@ -32,6 +33,7 @@ enum RequestPath {
|
||||
case .RECIPE_DETAIL(recipeId: let recipeId): return "recipes/\(recipeId)"
|
||||
case .IMAGE(recipeId: let recipeId, thumb: let thumb): return "recipes/\(recipeId)/image?size=\(thumb ? "thumb" : "full")"
|
||||
case .NEW_RECIPE: return "recipes"
|
||||
case .KEYWORDS: return "keywords"
|
||||
|
||||
case .LOGINV2REQ: return "/index.php/login/v2"
|
||||
case .CUSTOM(path: let path): return path
|
||||
|
||||
@@ -11,6 +11,8 @@ import SwiftUI
|
||||
struct Nextcloud_Cookbook_iOS_ClientApp: App {
|
||||
@StateObject var userSettings = UserSettings()
|
||||
@StateObject var mainViewModel = MainViewModel()
|
||||
@State(initialValue: "en") var language: String
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ZStack {
|
||||
@@ -22,7 +24,14 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App {
|
||||
mainViewModel.apiController = APIController(userSettings: userSettings)
|
||||
}
|
||||
}
|
||||
}.transition(.slide)
|
||||
}
|
||||
.transition(.slide)
|
||||
.onAppear {
|
||||
language = userSettings.language
|
||||
print(userSettings.language)
|
||||
}
|
||||
.environment(\.locale, .init(identifier: language))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,17 @@ import SwiftUI
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKeywords() async -> [String] {
|
||||
if let keywords: [RecipeKeyword] = await self.loadObject(
|
||||
localPath: "keywords.data",
|
||||
networkPath: .KEYWORDS,
|
||||
needsUpdate: true
|
||||
) {
|
||||
return keywords.map { $0.name }
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
func deleteAllData() {
|
||||
if dataStore.clearAll() {
|
||||
self.categories = []
|
||||
@@ -183,6 +194,16 @@ import SwiftUI
|
||||
self.recipeDetails = [:]
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRecipe(withId id: Int, categoryName: String) {
|
||||
let path = "recipe\(id).data"
|
||||
dataStore.delete(path: path)
|
||||
guard recipes[categoryName] != nil else { return }
|
||||
recipes[categoryName]!.removeAll(where: { recipe in
|
||||
recipe.recipe_id == id ? true : false
|
||||
})
|
||||
recipeDetails.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,16 +19,16 @@ struct CategoryDetailView: View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack {
|
||||
ForEach(recipesFiltered(), id: \.recipe_id) { recipe in
|
||||
NavigationLink() {
|
||||
RecipeDetailView(viewModel: viewModel, recipe: recipe).id(recipe.recipe_id)
|
||||
} label: {
|
||||
NavigationLink(value: recipe) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Recipe.self) { recipe in
|
||||
RecipeDetailView(viewModel: viewModel, recipe: recipe)//.id(recipe.recipe_id)
|
||||
}
|
||||
.navigationTitle(categoryName == "*" ? "Other" : categoryName)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
|
||||
@@ -31,10 +31,6 @@ struct CategoryPickerView: View {
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(Color("backgroundHighlight"))
|
||||
)
|
||||
.onTapGesture {
|
||||
selection = searchText
|
||||
}
|
||||
@@ -47,10 +43,6 @@ struct CategoryPickerView: View {
|
||||
Text(suggestion)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(Color("backgroundHighlight"))
|
||||
)
|
||||
.onTapGesture {
|
||||
selection = suggestion
|
||||
}
|
||||
|
||||
@@ -8,20 +8,14 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct Keyword: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
|
||||
init(_ name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
struct KeywordPickerView: View {
|
||||
@State var title: String
|
||||
@State var searchSuggestions: [Keyword]
|
||||
@State var searchSuggestions: [String]
|
||||
@Binding var selection: [String]
|
||||
@State var searchText: String = ""
|
||||
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 5)]
|
||||
|
||||
var body: some View {
|
||||
@@ -33,32 +27,32 @@ struct KeywordPickerView: View {
|
||||
LazyVGrid(columns: columns, spacing: 5) {
|
||||
if searchText != "" {
|
||||
KeywordItemView(
|
||||
keyword: Keyword(searchText),
|
||||
keyword: searchText,
|
||||
isSelected: selection.contains(searchText)
|
||||
) { keyword in
|
||||
if selection.contains(keyword.name) {
|
||||
if selection.contains(keyword) {
|
||||
selection.removeAll(where: { s in
|
||||
s == keyword.name ? true : false
|
||||
s == keyword ? true : false
|
||||
})
|
||||
searchSuggestions.removeAll(where: { s in
|
||||
s.name == keyword.name ? true : false
|
||||
s == keyword ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(keyword.name)
|
||||
selection.append(keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(suggestionsFiltered(), id: \.id) { suggestion in
|
||||
ForEach(suggestionsFiltered(), id: \.self) { suggestion in
|
||||
KeywordItemView(
|
||||
keyword: suggestion,
|
||||
isSelected: selection.contains(suggestion.name)
|
||||
isSelected: selection.contains(suggestion)
|
||||
) { keyword in
|
||||
if selection.contains(keyword.name) {
|
||||
if selection.contains(keyword) {
|
||||
selection.removeAll(where: { s in
|
||||
s == keyword.name ? true : false
|
||||
s == keyword ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(keyword.name)
|
||||
selection.append(keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,15 +67,15 @@ struct KeywordPickerView: View {
|
||||
LazyVGrid(columns: columns, spacing: 5) {
|
||||
ForEach(selection, id: \.self) { suggestion in
|
||||
KeywordItemView(
|
||||
keyword: Keyword(suggestion),
|
||||
keyword: suggestion,
|
||||
isSelected: true
|
||||
) { keyword in
|
||||
if selection.contains(keyword.name) {
|
||||
if selection.contains(keyword) {
|
||||
selection.removeAll(where: { s in
|
||||
s == keyword.name ? true : false
|
||||
s == keyword ? true : false
|
||||
})
|
||||
} else {
|
||||
selection.append(keyword.name)
|
||||
selection.append(keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,12 +84,13 @@ struct KeywordPickerView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle(title)
|
||||
|
||||
}
|
||||
|
||||
func suggestionsFiltered() -> [Keyword] {
|
||||
func suggestionsFiltered() -> [String] {
|
||||
guard searchText != "" else { return searchSuggestions }
|
||||
return searchSuggestions.filter { suggestion in
|
||||
suggestion.name.lowercased().contains(searchText.lowercased())
|
||||
suggestion.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,17 +98,18 @@ struct KeywordPickerView: View {
|
||||
|
||||
|
||||
struct KeywordItemView: View {
|
||||
var keyword: Keyword
|
||||
var keyword: String
|
||||
var isSelected: Bool
|
||||
var tapped: (Keyword) -> ()
|
||||
var tapped: (String) -> ()
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
}
|
||||
Text(keyword.name)
|
||||
Text(keyword)
|
||||
.lineLimit(2)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
|
||||
@@ -14,6 +14,8 @@ struct MainView: View {
|
||||
|
||||
@State private var selectedCategory: Category? = nil
|
||||
@State private var showEditView: Bool = false
|
||||
@State private var showSettingsView: Bool = false
|
||||
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 0)]
|
||||
|
||||
var body: some View {
|
||||
@@ -31,6 +33,9 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("Cookbooks")
|
||||
.navigationDestination(isPresented: $showSettingsView) {
|
||||
SettingsView(userSettings: userSettings, viewModel: viewModel)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Menu {
|
||||
@@ -47,26 +52,27 @@ struct MainView: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
print("Create recipe")
|
||||
showEditView = true
|
||||
self.showSettingsView = true
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Create new recipe")
|
||||
Image(systemName: "plus.circle")
|
||||
}
|
||||
Text("Settings")
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
NavigationLink( destination: SettingsView(userSettings: userSettings, viewModel: viewModel)) {
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
Button {
|
||||
print("Create recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
} detail: {
|
||||
NavigationStack {
|
||||
if let category = selectedCategory {
|
||||
@@ -77,7 +83,6 @@ struct MainView: View {
|
||||
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.sheet(isPresented: $showEditView) {
|
||||
|
||||
@@ -33,13 +33,13 @@ struct WelcomeTab: View {
|
||||
.resizable()
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
Text("Tank you for downloading the")
|
||||
Text("Tank you for downloading")
|
||||
.font(.headline)
|
||||
Text("Cookbook Client")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Spacer()
|
||||
Text("This application is an open source effort and still in development. If you encounter any problems, please report them on our GitHub page.")
|
||||
Text("This application is an open source effort. If you're interested in suggesting or contributing new features, or you encounter any problems, please use the support link or visit the GitHub repository in the app settings.")
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ struct RecipeDurationSection: View {
|
||||
HStack(alignment: .center) {
|
||||
if let prepTime = recipeDetail.prepTime {
|
||||
VStack {
|
||||
SecondaryLabel(text: "Prep time")
|
||||
SecondaryLabel(text: String(localized: "Prep time"))
|
||||
Text(DateFormatter.formatDate(duration: prepTime))
|
||||
.lineLimit(1)
|
||||
}.padding()
|
||||
@@ -115,7 +115,7 @@ struct RecipeDurationSection: View {
|
||||
|
||||
if let cookTime = recipeDetail.cookTime {
|
||||
VStack {
|
||||
SecondaryLabel(text: "Cook time")
|
||||
SecondaryLabel(text: String(localized: "Cook time"))
|
||||
Text(DateFormatter.formatDate(duration: cookTime))
|
||||
.lineLimit(1)
|
||||
}.padding()
|
||||
@@ -123,7 +123,7 @@ struct RecipeDurationSection: View {
|
||||
|
||||
if let totalTime = recipeDetail.totalTime {
|
||||
VStack {
|
||||
SecondaryLabel(text: "Total time")
|
||||
SecondaryLabel(text: String(localized: "Total time"))
|
||||
Text(DateFormatter.formatDate(duration: totalTime))
|
||||
.lineLimit(1)
|
||||
}.padding()
|
||||
@@ -140,11 +140,11 @@ struct RecipeIngredientSection: View {
|
||||
Divider()
|
||||
HStack {
|
||||
if recipeDetail.recipeYield == 0 {
|
||||
SecondaryLabel(text: "Ingredients")
|
||||
SecondaryLabel(text: String(localized: "Ingredients"))
|
||||
} else if recipeDetail.recipeYield == 1 {
|
||||
SecondaryLabel(text: "Ingredients per serving")
|
||||
SecondaryLabel(text: String(localized: "Ingredients per serving"))
|
||||
} else {
|
||||
SecondaryLabel(text: "Ingredients for \(recipeDetail.recipeYield) servings")
|
||||
SecondaryLabel(text: String(localized: "Ingredients for \(recipeDetail.recipeYield) servings"))
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
@@ -166,7 +166,7 @@ struct RecipeToolSection: View {
|
||||
VStack(alignment: .leading) {
|
||||
Divider()
|
||||
HStack {
|
||||
SecondaryLabel(text: "Tools")
|
||||
SecondaryLabel(text: String(localized: "Tools"))
|
||||
Spacer()
|
||||
}
|
||||
ForEach(recipeDetail.tool, id: \.self) { tool in
|
||||
@@ -187,7 +187,7 @@ struct RecipeInstructionSection: View {
|
||||
VStack(alignment: .leading) {
|
||||
Divider()
|
||||
HStack {
|
||||
SecondaryLabel(text: "Instructions")
|
||||
SecondaryLabel(text: String(localized: "Instructions"))
|
||||
Spacer()
|
||||
}
|
||||
ForEach(0..<recipeDetail.recipeInstructions.count) { ix in
|
||||
|
||||
@@ -65,12 +65,14 @@ struct RecipeEditView: View {
|
||||
@State private var times = [Date.zero, Date.zero, Date.zero]
|
||||
@State private var searchText: String = ""
|
||||
@State private var keywords: [String] = []
|
||||
@State private var keywordSuggestions: [String] = []
|
||||
|
||||
@State private var alertMessage: ErrorMessages = .GENERIC
|
||||
@State private var presentAlert: Bool = false
|
||||
@State private var waitingForUpload: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
HStack {
|
||||
Button() {
|
||||
@@ -110,7 +112,7 @@ struct RecipeEditView: View {
|
||||
}
|
||||
}.padding()
|
||||
HStack {
|
||||
Text(recipe.name == "" ? "New recipe" : recipe.name)
|
||||
Text(recipe.name == "" ? String(localized: "New recipe") : recipe.name)
|
||||
.font(.title)
|
||||
.bold()
|
||||
.padding()
|
||||
@@ -132,28 +134,24 @@ struct RecipeEditView: View {
|
||||
*/
|
||||
Section() {
|
||||
NavigationLink(recipe.recipeCategory == "" ? "Category" : "Category: \(recipe.recipeCategory)") {
|
||||
CategoryPickerView(title: "Category", searchSuggestions: [], selection: $recipe.recipeCategory)
|
||||
CategoryPickerView(
|
||||
title: "Category",
|
||||
searchSuggestions: viewModel.categories.map({ category in
|
||||
category.name == "*" ? "Other" : category.name
|
||||
}),
|
||||
selection: $recipe.recipeCategory)
|
||||
}
|
||||
NavigationLink("Keywords") {
|
||||
KeywordPickerView(
|
||||
title: "Keywords",
|
||||
searchSuggestions: [
|
||||
Keyword("Hauptspeisen"),
|
||||
Keyword("Lecker"),
|
||||
Keyword("Trinken"),
|
||||
Keyword("Essen"),
|
||||
Keyword("Nachspeisen"),
|
||||
Keyword("Futter"),
|
||||
Keyword("Apfel"),
|
||||
Keyword("test")
|
||||
],
|
||||
searchSuggestions: keywordSuggestions,
|
||||
selection: $keywords
|
||||
)
|
||||
}
|
||||
} header: {
|
||||
Text("Discoverability")
|
||||
} footer: {
|
||||
ScrollView(.horizontal) {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
ForEach(keywords, id: \.self) { keyword in
|
||||
Text(keyword)
|
||||
@@ -174,11 +172,15 @@ struct RecipeEditView: View {
|
||||
DatePicker("Total time:", selection: $times[2], displayedComponents: .hourAndMinute)
|
||||
}
|
||||
|
||||
EditableListSection(title: "Ingredients", items: $recipe.recipeIngredient)
|
||||
EditableListSection(title: "Tools", items: $recipe.tool)
|
||||
EditableListSection(title: "Instructions", items: $recipe.recipeInstructions)
|
||||
EditableListSection(title: String(localized: "Ingredients"), items: $recipe.recipeIngredient)
|
||||
EditableListSection(title: String(localized: "Tools"), items: $recipe.tool)
|
||||
EditableListSection(title: String(localized: "Instructions"), items: $recipe.recipeInstructions)
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
self.keywordSuggestions = await viewModel.getKeywords()
|
||||
}
|
||||
.onAppear {
|
||||
if uploadNew { return }
|
||||
if let prepTime = recipe.prepTime {
|
||||
@@ -301,6 +303,9 @@ struct RecipeEditView: View {
|
||||
]
|
||||
)
|
||||
sendRequest(request)
|
||||
if let recipeIdInt = Int(recipe.id) {
|
||||
viewModel.deleteRecipe(withId: recipeIdInt, categoryName: recipe.recipeCategory)
|
||||
}
|
||||
dismissEditView()
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,22 @@ fileprivate enum SettingsAlert {
|
||||
}
|
||||
}
|
||||
|
||||
enum SupportedLanguage: String, Codable {
|
||||
case EN = "en",
|
||||
DE = "de"
|
||||
|
||||
func descriptor() -> String {
|
||||
switch self {
|
||||
case .EN:
|
||||
return "English"
|
||||
case .DE:
|
||||
return "Deutsch"
|
||||
}
|
||||
}
|
||||
|
||||
static let allValues = [EN, DE]
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@@ -40,21 +56,21 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Picker("Select a cookbook", selection: $userSettings.defaultCategory) {
|
||||
Text("")
|
||||
Picker("Select a default cookbook", selection: $userSettings.defaultCategory) {
|
||||
Text("None").tag("None")
|
||||
ForEach(viewModel.categories, id: \.name) { category in
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
Text(category.name == "*" ? "Other" : category.name).tag(category)
|
||||
}
|
||||
}
|
||||
Button {
|
||||
userSettings.defaultCategory = ""
|
||||
} label: {
|
||||
Text("Clear default category")
|
||||
Picker("Language", selection: $userSettings.language) {
|
||||
ForEach(SupportedLanguage.allValues, id: \.self) { lang in
|
||||
Text(lang.descriptor()).tag(lang.rawValue)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Default cookbook")
|
||||
Text("General")
|
||||
} footer: {
|
||||
Text("The selected cookbook will be opened on app launch by default.")
|
||||
Text("The selected cookbook will open on app launch by default.")
|
||||
}
|
||||
Section() {
|
||||
Link("Visit the GitHub page", destination: URL(string: "https://github.com/VincentMeilinger/Nextcloud-Cookbook-iOS")!)
|
||||
@@ -105,6 +121,7 @@ struct SettingsView: View {
|
||||
} message: {
|
||||
Text(alertType.getMessage())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func logOut() {
|
||||
@@ -116,7 +133,7 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
func deleteCache() {
|
||||
//viewModel.deleteAllData()
|
||||
viewModel.deleteAllData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user