From 48b31a799797b00b34146e31a006a5afe864dce4 Mon Sep 17 00:00:00 2001 From: VincentMeilinger Date: Sat, 31 May 2025 11:12:14 +0200 Subject: [PATCH] Nextcloud Login refactoring --- .../project.pbxproj | 12 + .../UserInterfaceState.xcuserstate | Bin 314552 -> 281718 bytes Nextcloud Cookbook iOS Client/AppState.swift | 4 + .../Data/AuthManager.swift | 44 ++ .../Data/Recipe.swift | 74 ++-- .../Localizable.xcstrings | 122 +++--- .../Networking/ApiRequest.swift | 7 +- .../Networking/CookbookApi/CookbookApi.swift | 2 +- .../CookbookApi/CookbookApiV1.swift | 8 +- .../CookbookApi/CookbookModelsV1.swift | 6 +- .../Networking/NetworkError.swift | 14 +- .../NextcloudApi/NextcloudApi.swift | 11 +- .../Nextcloud_Cookbook_iOS_ClientApp.swift | 6 +- .../Views/MainView.swift | 119 ++---- .../Views/Onboarding/V2LoginView.swift | 388 ++++++++++++------ .../Views/Recipes/RecipeCardView.swift | 13 +- .../Views/Recipes/RecipeListView.swift | 47 +++ .../Views/Recipes/RecipeView.swift | 100 ++--- .../RecipeDurationSection.swift | 21 +- .../RecipeIngredientSection.swift | 126 ++++-- .../RecipeInstructionSection.swift | 64 ++- .../RecipeMetadataSection.swift | 14 +- .../RecipeNutritionSection.swift | 18 +- .../RecipeToolSection.swift | 14 +- .../Views/ReusableViews/CollapsibleView.swift | 4 +- .../Views/ReusableViews/ListVStack.swift | 38 ++ .../Views/Tabs/GroceryListTabView.swift | 320 +++++++-------- .../Views/Tabs/RecipeTabView.swift | 167 ++++---- .../Views/Tabs/SettingsTabView.swift | 234 +++++++++++ 29 files changed, 1277 insertions(+), 720 deletions(-) create mode 100644 Nextcloud Cookbook iOS Client/Data/AuthManager.swift create mode 100644 Nextcloud Cookbook iOS Client/Views/ReusableViews/ListVStack.swift create mode 100644 Nextcloud Cookbook iOS Client/Views/Tabs/SettingsTabView.swift diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj index ae272d7..1ad6293 100644 --- a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj +++ b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj @@ -59,6 +59,9 @@ A98F931E2C07B07400E34359 /* CookbookState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F931D2C07B07400E34359 /* CookbookState.swift */; }; A99A2D4E2BEFBC0900402B36 /* CookbookLoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99A2D4D2BEFBC0900402B36 /* CookbookLoginModels.swift */; }; A99A2D502BEFC44000402B36 /* CookbookProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99A2D4F2BEFC44000402B36 /* CookbookProtocols.swift */; }; + A9AAB04E2DE8620000A4C74B /* ListVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9AAB04D2DE861FA00A4C74B /* ListVStack.swift */; }; + A9AAB0502DE881FC00A4C74B /* SettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9AAB04F2DE881F600A4C74B /* SettingsTabView.swift */; }; + A9AAB0522DE911C600A4C74B /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9AAB0512DE911C300A4C74B /* AuthManager.swift */; }; A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */; }; A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */; }; A9BBB3902B91BE31002DA7FF /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38F2B91BE31002DA7FF /* Recipe.swift */; }; @@ -147,6 +150,9 @@ A98F931D2C07B07400E34359 /* CookbookState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookbookState.swift; sourceTree = ""; }; A99A2D4D2BEFBC0900402B36 /* CookbookLoginModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookbookLoginModels.swift; sourceTree = ""; }; A99A2D4F2BEFC44000402B36 /* CookbookProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookbookProtocols.swift; sourceTree = ""; }; + A9AAB04D2DE861FA00A4C74B /* ListVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListVStack.swift; sourceTree = ""; }; + A9AAB04F2DE881F600A4C74B /* SettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTabView.swift; sourceTree = ""; }; + A9AAB0512DE911C300A4C74B /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeaderView.swift; sourceTree = ""; }; A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomClipper.swift; sourceTree = ""; }; A9BBB38F2B91BE31002DA7FF /* Recipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipe.swift; sourceTree = ""; }; @@ -300,6 +306,7 @@ A70171C72AB4C4A100064C43 /* Data */ = { isa = PBXGroup; children = ( + A9AAB0512DE911C300A4C74B /* AuthManager.swift */, A70171C32AB4A31200064C43 /* DataStore.swift */, A70171CA2AB4CD1700064C43 /* UserSettings.swift */, A9BBB38F2B91BE31002DA7FF /* Recipe.swift */, @@ -382,6 +389,7 @@ A977D0DC2B6002DA009783A9 /* Tabs */ = { isa = PBXGroup; children = ( + A9AAB04F2DE881F600A4C74B /* SettingsTabView.swift */, A977D0DD2B600300009783A9 /* SearchTabView.swift */, A977D0DF2B600318009783A9 /* RecipeTabView.swift */, A977D0E12B60034E009783A9 /* GroceryListTabView.swift */, @@ -427,6 +435,7 @@ A9C3BE522B630F1300562C79 /* ReusableViews */ = { isa = PBXGroup; children = ( + A9AAB04D2DE861FA00A4C74B /* ListVStack.swift */, A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */, A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */, A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */, @@ -618,6 +627,7 @@ A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */, A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */, A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */, + A9AAB0522DE911C600A4C74B /* AuthManager.swift in Sources */, A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */, A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */, A70171B12AB211DF00064C43 /* NetworkError.swift in Sources */, @@ -626,12 +636,14 @@ A79AA8E62B02C3CB007D25F2 /* LoggerExtension.swift in Sources */, A70171C42AB4A31200064C43 /* DataStore.swift in Sources */, A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */, + A9AAB04E2DE8620000A4C74B /* ListVStack.swift in Sources */, A975061D2B920FCC00E86029 /* RecipeInstructionSection.swift in Sources */, A787B0782B2B1E6400C2DF1B /* DateExtension.swift in Sources */, A79AA8E92B062DD1007D25F2 /* CookbookApiV1.swift in Sources */, A9E78A2D2BE8E3AF00206866 /* DataInterface.swift in Sources */, A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */, A9E78A2B2BE7799F00206866 /* JsonAny.swift in Sources */, + A9AAB0502DE881FC00A4C74B /* SettingsTabView.swift in Sources */, A9BBB3902B91BE31002DA7FF /* Recipe.swift in Sources */, A98F931E2C07B07400E34359 /* CookbookState.swift in Sources */, A99A2D4E2BEFBC0900402B36 /* CookbookLoginModels.swift in Sources */, diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate index 3e004e3356d5513e34be27dae11a88954ed5b461..c9d1202e4a5f8bfc64ca074ae2a5d7cf1bc632c3 100644 GIT binary patch literal 281718 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%0DL!Deg)>(GuqXvg;29l;5l##dlw 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%0DL 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 BlaK%a diff --git a/Nextcloud Cookbook iOS Client/AppState.swift b/Nextcloud Cookbook iOS Client/AppState.swift index d7e36f5..33542ff 100644 --- a/Nextcloud Cookbook iOS Client/AppState.swift +++ b/Nextcloud Cookbook iOS Client/AppState.swift @@ -9,6 +9,10 @@ import Foundation import SwiftUI import UIKit +@Observable class AppState { + +} + /* @MainActor class AppState: ObservableObject { @Published var categories: [Category] = [] diff --git a/Nextcloud Cookbook iOS Client/Data/AuthManager.swift b/Nextcloud Cookbook iOS Client/Data/AuthManager.swift new file mode 100644 index 0000000..d295009 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Data/AuthManager.swift @@ -0,0 +1,44 @@ +// +// AuthManager.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 30.05.25. +// + +import Foundation +import KeychainSwift + +class AuthManager { + static let shared = AuthManager() + let keychain = KeychainSwift() + + var authString: String? = nil + + private let nextcloudUsernameKey = "nextcloud_username" + private let nextcloudAuthStringKey = "nextcloud_auth_string" // Stored as base64 + + func saveNextcloudCredentials(username: String, appPassword: String) { + keychain.set(username, forKey: nextcloudUsernameKey) + + let loginString = "\(username):\(appPassword)" + if let loginData = loginString.data(using: .utf8) { + keychain.set(loginData.base64EncodedString(), forKey: nextcloudAuthStringKey) + } + } + + func getNextcloudCredentials() -> (username: String?, authString: String?) { + let username = keychain.get(nextcloudUsernameKey) + let authString = keychain.get(nextcloudAuthStringKey) + + return (username, authString) + } + + func loadAuthString() { + authString = keychain.get(nextcloudAuthStringKey) + } + + func deleteNextcloudCredentials() { + keychain.delete(nextcloudUsernameKey) + keychain.delete(nextcloudAuthStringKey) + } +} diff --git a/Nextcloud Cookbook iOS Client/Data/Recipe.swift b/Nextcloud Cookbook iOS Client/Data/Recipe.swift index 227593e..9552d53 100644 --- a/Nextcloud Cookbook iOS Client/Data/Recipe.swift +++ b/Nextcloud Cookbook iOS Client/Data/Recipe.swift @@ -51,7 +51,7 @@ class Recipe { var name: String var keywords: [String] @Attribute(.externalStorage) var image: RecipeImage? - var thumbnail: RecipeThumbnail? + @Attribute(.externalStorage) var thumbnail: RecipeThumbnail? var dateCreated: String? = nil var dateModified: String? = nil var prepTime: String @@ -70,6 +70,16 @@ class Recipe { @Transient var ingredientMultiplier: Double = 1.0 + var prepTimeDurationComponent: DurationComponents { + DurationComponents.fromPTString(prepTime) + } + var cookTimeDurationComponent: DurationComponents { + DurationComponents.fromPTString(cookTime) + } + var totalTimeDurationComponent: DurationComponents { + DurationComponents.fromPTString(totalTime) + } + init( id: String, @@ -109,50 +119,24 @@ class Recipe { self.ingredientMultiplier = ingredientMultiplier } - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - name = try container.decode(String.self, forKey: .name) - keywords = try container.decode([String].self, forKey: .keywords) - dateCreated = try container.decodeIfPresent(String.self, forKey: .dateCreated) - dateModified = try container.decodeIfPresent(String.self, forKey: .dateModified) - prepTime = try container.decode(String.self, forKey: .prepTime) - cookTime = try container.decode(String.self, forKey: .cookTime) - totalTime = try container.decode(String.self, forKey: .totalTime) - recipeDescription = try container.decode(String.self, forKey: .recipeDescription) - url = try container.decodeIfPresent(String.self, forKey: .url) - yield = try container.decode(Int.self, forKey: .yield) - category = try container.decode(String.self, forKey: .category) - tools = try container.decode([String].self, forKey: .tools) - ingredients = try container.decode([String].self, forKey: .ingredients) - instructions = try container.decode([String].self, forKey: .instructions) - nutrition = try container.decode([String: String].self, forKey: .nutrition) - } -} - -extension Recipe: Codable { - enum CodingKeys: String, CodingKey { - case id, name, keywords, dateCreated, dateModified, prepTime, cookTime, totalTime, recipeDescription, url, yield, category, tools, ingredients, instructions, nutrition - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(name, forKey: .name) - try container.encode(keywords, forKey: .keywords) - try container.encode(dateCreated, forKey: .dateCreated) - try container.encode(dateModified, forKey: .dateModified) - try container.encode(prepTime, forKey: .prepTime) - try container.encode(cookTime, forKey: .cookTime) - try container.encode(totalTime, forKey: .totalTime) - try container.encode(recipeDescription, forKey: .recipeDescription) - try container.encode(url, forKey: .url) - try container.encode(yield, forKey: .yield) - try container.encode(category, forKey: .category) - try container.encode(tools, forKey: .tools) - try container.encode(ingredients, forKey: .ingredients) - try container.encode(instructions, forKey: .instructions) - try container.encode(nutrition, forKey: .nutrition) + init() { + self.id = UUID().uuidString + self.name = String(localized: "New Recipe") + self.keywords = [] + self.dateCreated = nil + self.dateModified = nil + self.prepTime = "0" + self.cookTime = "0" + self.totalTime = "0" + self.recipeDescription = "" + self.url = "" + self.yield = 1 + self.category = "" + self.tools = [] + self.ingredients = [] + self.instructions = [] + self.nutrition = [:] + self.ingredientMultiplier = 1 } } // MARK: - Recipe Stub diff --git a/Nextcloud Cookbook iOS Client/Localizable.xcstrings b/Nextcloud Cookbook iOS Client/Localizable.xcstrings index 26d53e7..7f69cc7 100644 --- a/Nextcloud Cookbook iOS Client/Localizable.xcstrings +++ b/Nextcloud Cookbook iOS Client/Localizable.xcstrings @@ -136,7 +136,6 @@ } }, "%@: %@" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -400,7 +399,6 @@ } }, "A simple-to-use PDF builder for Swift. Used for generating recipe PDF documents." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -423,7 +421,6 @@ } }, "About" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -446,7 +443,6 @@ } }, "Acknowledgements" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -491,7 +487,6 @@ } }, "Add" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -514,7 +509,6 @@ } }, "Add cooking steps for fellow chefs to follow." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -537,7 +531,6 @@ } }, "Add groceries to this list by either using the button next to an ingredient list in a recipe, or by swiping right on individual ingredients of a recipe." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -581,9 +574,11 @@ } } } + }, + "All Recipes" : { + }, "An HTML parsing and web scraping library for Swift. Used for importing schema.org recipes from websites." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -762,6 +757,9 @@ } } } + }, + "Categories" : { + }, "Category" : { "extractionState" : "stale", @@ -855,11 +853,10 @@ } } }, - "Client error" : { + "Client error: %lld" : { }, "Comma (e.g. 1,42)" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -882,7 +879,6 @@ } }, "Configure what is stored on your device." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -905,7 +901,6 @@ } }, "Configure which sections in your recipes are expanded by default." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1061,9 +1056,11 @@ } } } + }, + "Copy Error" : { + }, "Copy Link" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1108,7 +1105,6 @@ } }, "Created: %@" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1134,7 +1130,6 @@ }, "Decimal number format" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1182,7 +1177,6 @@ } }, "Delete local data" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1228,7 +1222,6 @@ } }, "Delete Recipe" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1273,7 +1266,6 @@ } }, "Deleting local data will not affect the recipe data stored on your server." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1318,7 +1310,6 @@ } }, "Description" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1339,6 +1330,9 @@ } } } + }, + "Dismiss" : { + }, "Done" : { "localizations" : { @@ -1363,7 +1357,6 @@ } }, "Downloads" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1454,7 +1447,6 @@ } }, "Edit" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1500,6 +1492,9 @@ } } } + }, + "Error: Login URL not available." : { + }, "Error." : { "localizations" : { @@ -1522,9 +1517,11 @@ } } } + }, + "example.com" : { + }, "Expand information section" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1547,7 +1544,6 @@ } }, "Expand keyword section" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1570,7 +1566,6 @@ } }, "Expand nutrition section" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1685,7 +1680,6 @@ } }, "Get support" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1708,7 +1702,6 @@ } }, "Grocery List" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1731,7 +1724,6 @@ } }, "Hours" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1754,7 +1746,6 @@ } }, "If 'Same as Device' is selected and your device language is not supported yet, this option will default to english." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1800,7 +1791,6 @@ } }, "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." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1823,7 +1813,6 @@ } }, "If you have any inquiries, feedback, or require assistance, please refer to the support page for contact information." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1844,6 +1833,9 @@ } } } + }, + "If your browser does not open automatically, copy the link above and paste it manually. After a successful login, return to this application." : { + }, "Import" : { "extractionState" : "stale", @@ -1892,7 +1884,6 @@ } }, "Ingredient" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1915,7 +1906,6 @@ } }, "Ingredients" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1984,7 +1974,6 @@ } }, "Instruction" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2007,7 +1996,6 @@ } }, "Instructions" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2036,7 +2024,6 @@ }, "Keep screen awake when viewing recipes" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2082,7 +2069,6 @@ } }, "Language" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2105,7 +2091,6 @@ } }, "Last modified: %@" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2151,7 +2136,6 @@ } }, "List your tools here. 🍴" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2172,9 +2156,14 @@ } } } + }, + "Log in" : { + + }, + "Log in to your Nextcloud account to sync your recipes. This requires a Nextcloud server with the Nextcloud Cookbook application installed." : { + }, "Log out" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2197,7 +2186,6 @@ } }, "Login" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2288,7 +2276,6 @@ } }, "Marked ingredients could not be adjusted!" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2311,7 +2298,6 @@ } }, "Minutes" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2428,7 +2414,6 @@ } }, "More information" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2471,9 +2456,6 @@ } } } - }, - "New" : { - }, "New recipe" : { "extractionState" : "stale", @@ -2519,9 +2501,11 @@ } } } + }, + "Nextcloud" : { + }, "Nextcloud Login" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2567,7 +2551,6 @@ } }, "No nutritional information." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2636,7 +2619,6 @@ } }, "Nutrition" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2659,7 +2641,6 @@ } }, "Nutrition (%@)" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2682,7 +2663,6 @@ } }, "Offline recipes" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2727,7 +2707,6 @@ } }, "Other" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2889,7 +2868,6 @@ } }, "Point (e.g. 1.42)" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2912,7 +2890,6 @@ } }, "Preparation" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3003,7 +2980,6 @@ } }, "Recipe Name" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3048,7 +3024,6 @@ } }, "Recipes" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3255,6 +3230,9 @@ } } } + }, + "Select a Recipe" : { + }, "Select Keywords" : { "extractionState" : "stale", @@ -3302,7 +3280,13 @@ } } }, - "Server error" : { + "Server address:" : { + + }, + "Server error: %lld" : { + + }, + "Server Protocol:" : { }, "Serving size" : { @@ -3464,7 +3448,6 @@ } }, "Share Recipe" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3533,7 +3516,6 @@ } }, "Start by adding your first ingredient! 🥬" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3556,7 +3538,6 @@ } }, "Store recipe images locally" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3579,7 +3560,6 @@ } }, "Store recipe thumbnails locally" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3670,7 +3650,6 @@ } }, "Support" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3693,7 +3672,6 @@ } }, "SwiftSoup" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3760,6 +3738,9 @@ } } } + }, + "The 'Login' button will open a web browser. Please follow the login instructions provided there.\nAfter a successful login, return to this application and press 'Validate'.\nIf the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually." : { + }, "The recipe has no image whose MIME type matches the Accept header" : { "extractionState" : "stale", @@ -3899,7 +3880,6 @@ } }, "This setting will take effect after the app is restarted. It affects the adjustment of ingredient quantities." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3965,9 +3945,11 @@ } } } + }, + "To add grocieries manually, type them in the box below and press the button. To add multiple items at once, separate them by a new line." : { + }, "Tool" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3990,7 +3972,6 @@ } }, "Tools" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4036,7 +4017,6 @@ } }, "Total time" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4059,7 +4039,6 @@ } }, "TPPDF" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4255,7 +4234,6 @@ } }, "Upload Changes" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4278,7 +4256,6 @@ } }, "Upload Recipe" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4324,7 +4301,6 @@ } }, "URL:" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4347,7 +4323,6 @@ } }, "Username: %@" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4393,7 +4368,6 @@ } }, "Visit the GitHub page" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4416,7 +4390,6 @@ } }, "You're all set for cooking 🍓" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4439,7 +4412,6 @@ } }, "Your grocery list is stored locally and therefore not synchronized across your devices." : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/Nextcloud Cookbook iOS Client/Networking/ApiRequest.swift b/Nextcloud Cookbook iOS Client/Networking/ApiRequest.swift index 4971767..ee51c85 100644 --- a/Nextcloud Cookbook iOS Client/Networking/ApiRequest.swift +++ b/Nextcloud Cookbook iOS Client/Networking/ApiRequest.swift @@ -69,7 +69,7 @@ struct ApiRequest { var response: URLResponse? = nil do { (data, response) = try await URLSession.shared.data(for: request) - Logger.network.debug("\(method.rawValue) \(path) SUCCESS!") + Logger.network.debug("\(method.rawValue) \(path) received response ...") if let error = decodeURLResponse(response: response as? HTTPURLResponse) { print("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)") return (nil, error) @@ -94,9 +94,8 @@ struct ApiRequest { switch response.statusCode { case 200...299: return (nil) case 300...399: return (NetworkError.redirectionError) - case 400...499: return (NetworkError.clientError) - case 500...599: return (NetworkError.serverError) - case 600: return (NetworkError.invalidRequest) + case 400...499: return (NetworkError.clientError(statusCode: response.statusCode)) + case 500...599: return (NetworkError.serverError(statusCode: response.statusCode)) default: return (NetworkError.unknownError) } } diff --git a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApi.swift b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApi.swift index d4fa2d5..b7b963d 100644 --- a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApi.swift +++ b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApi.swift @@ -99,7 +99,7 @@ protocol CookbookApi { /// - Returns: A list of categories. A NetworkError if the request fails. static func getCategories( auth: String - ) async -> ([Category]?, NetworkError?) + ) async -> ([CookbookApiCategory]?, NetworkError?) /// Get all recipes of a specified category. /// - Parameters: diff --git a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApiV1.swift b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApiV1.swift index 34d81be..9107076 100644 --- a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApiV1.swift +++ b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookApiV1.swift @@ -77,7 +77,7 @@ class CookbookApiV1: CookbookApi { if let id = json as? Int { return nil } else if let dict = json as? [String: Any] { - return .serverError + return .unknownError } } catch { return .decodingFailed @@ -103,7 +103,7 @@ class CookbookApiV1: CookbookApi { static func updateRecipe(auth: String, recipe: Recipe) async -> (NetworkError?) { let cookbookRecipe = CookbookApiRecipeDetailV1.fromRecipe(recipe) - guard let recipeData = JSONEncoder.safeEncode(recipe) else { + guard let recipeData = JSONEncoder.safeEncode(cookbookRecipe) else { return .dataError } let request = ApiRequest( @@ -121,7 +121,7 @@ class CookbookApiV1: CookbookApi { if let id = json as? Int { return nil } else if let dict = json as? [String: Any] { - return .serverError + return .unknownError } } catch { return .decodingFailed @@ -143,7 +143,7 @@ class CookbookApiV1: CookbookApi { return nil } - static func getCategories(auth: String) async -> ([Category]?, NetworkError?) { + static func getCategories(auth: String) async -> ([CookbookApiCategory]?, NetworkError?) { let request = ApiRequest( path: basePath + "/categories", method: .GET, diff --git a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookModelsV1.swift b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookModelsV1.swift index 986447b..5d3ae98 100644 --- a/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookModelsV1.swift +++ b/Nextcloud Cookbook iOS Client/Networking/CookbookApi/CookbookModelsV1.swift @@ -8,10 +8,10 @@ import Foundation import SwiftUI -struct Category: Codable, Identifiable, Hashable { +struct CookbookApiCategory: Codable, Identifiable, Hashable { var id: String { name } - let name: String - let recipe_count: Int + var name: String + var recipe_count: Int private enum CodingKeys: String, CodingKey { case name, recipe_count diff --git a/Nextcloud Cookbook iOS Client/Networking/NetworkError.swift b/Nextcloud Cookbook iOS Client/Networking/NetworkError.swift index 3e26c9e..0508bce 100644 --- a/Nextcloud Cookbook iOS Client/Networking/NetworkError.swift +++ b/Nextcloud Cookbook iOS Client/Networking/NetworkError.swift @@ -15,8 +15,8 @@ public enum NetworkError: UserAlert { case encodingFailed case decodingFailed case redirectionError - case clientError - case serverError + case clientError(statusCode: Int) + case serverError(statusCode: Int) case invalidRequest case unknownError case dataError @@ -33,10 +33,10 @@ public enum NetworkError: UserAlert { "Data decoding failed." case .redirectionError: "Redirection error" - case .clientError: - "Client error" - case .serverError: - "Server error" + case .clientError(let code): + "Client error: \(code)" + case .serverError(let code): + "Server error: \(code)" case .invalidRequest: "Invalid request" case .unknownError: @@ -47,7 +47,7 @@ public enum NetworkError: UserAlert { } var localizedDescription: LocalizedStringKey { - return "" // TODO: Add description + return self.localizedTitle } var alertButtons: [AlertButton] { diff --git a/Nextcloud Cookbook iOS Client/Networking/NextcloudApi/NextcloudApi.swift b/Nextcloud Cookbook iOS Client/Networking/NextcloudApi/NextcloudApi.swift index e4326cb..4e7371f 100644 --- a/Nextcloud Cookbook iOS Client/Networking/NextcloudApi/NextcloudApi.swift +++ b/Nextcloud Cookbook iOS Client/Networking/NextcloudApi/NextcloudApi.swift @@ -20,10 +20,9 @@ class NextcloudApi { /// - `LoginV2Request?`: An object containing the necessary information for the second step of the login process, if successful. /// - `NetworkError?`: An error encountered during the network request, if any. - static func loginV2Request() async -> (LoginV2Request?, NetworkError?) { - let path = UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + static func loginV2Request(_ baseAddress: String) async -> (LoginV2Request?, NetworkError?) { let request = ApiRequest( - path: path + "/index.php/login/v2", + path: baseAddress + "/index.php/login/v2", method: .POST ) @@ -52,16 +51,16 @@ class NextcloudApi { /// - `LoginV2Response?`: An object representing the response of the login process, if successful. /// - `NetworkError?`: An error encountered during the network request, if any. - static func loginV2Response(req: LoginV2Request) async -> (LoginV2Response?, NetworkError?) { + static func loginV2Poll(pollURL: String, pollToken: String) async -> (LoginV2Response?, NetworkError?) { let request = ApiRequest( - path: req.poll.endpoint, + path: pollURL, method: .POST, headerFields: [ HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .FORM) ], - body: "token=\(req.poll.token)".data(using: .utf8) + body: "token=\(pollToken)".data(using: .utf8) ) let (data, error) = await request.send(pathCompletion: false) diff --git a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift index fc21939..c2f2701 100644 --- a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift +++ b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift @@ -21,7 +21,7 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App { EmptyView() } else { MainView() - .modelContainer(for: Recipe.self) + .modelContainer(for: [Recipe.self, GroceryItem.self, RecipeGroceries.self]) } } .transition(.slide) @@ -30,6 +30,10 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App { .init(identifier: language == SupportedLanguage.DEVICE.rawValue ? (Locale.current.language.languageCode?.identifier ?? "en") : language) ) + .onAppear { + AuthManager.shared.loadAuthString() // Load the auth string as soon as possible + } } + } } diff --git a/Nextcloud Cookbook iOS Client/Views/MainView.swift b/Nextcloud Cookbook iOS Client/Views/MainView.swift index f3c4495..95f912e 100644 --- a/Nextcloud Cookbook iOS Client/Views/MainView.swift +++ b/Nextcloud Cookbook iOS Client/Views/MainView.swift @@ -8,89 +8,58 @@ import SwiftUI import SwiftData -struct MainView: View { - //@State var cookbookState: CookbookState = CookbookState() - @Environment(\.modelContext) var modelContext - @Query var recipes: [Recipe] = [] - +struct MainView: View { + // Tab ViewModels + enum Tab { + case recipes, settings, groceryList + } var body: some View { - VStack { - List { - ForEach(recipes) { recipe in - Text(recipe.name) + TabView { + RecipeTabView() + .tabItem { + Label("Recipes", systemImage: "book.closed.fill") } - } - Button("New") { - let recipe = Recipe(id: UUID().uuidString, name: "Neues Rezept", keywords: [], prepTime: "", cookTime: "", totalTime: "", recipeDescription: "", yield: 0, category: "", tools: [], ingredients: [], instructions: [], nutrition: [:], ingredientMultiplier: 0) - modelContext.insert(recipe) - } + .tag(Tab.recipes) + + GroceryListTabView() + .tabItem { + if #available(iOS 17.0, *) { + Label("Grocery List", systemImage: "storefront") + } else { + Label("Grocery List", systemImage: "heart.text.square") + } + } + .tag(Tab.groceryList) + + SettingsTabView() + .tabItem { + Label("Settings", systemImage: "gear") + } + .tag(Tab.settings) + } - /*NavigationSplitView { - VStack { - List(selection: $cookbookState.selectedCategory) { - ForEach(cookbookState.categories) { category in - Text(category.name) - .tag(category) - } - } - .listStyle(.plain) - .onAppear { - Task { - await cookbookState.loadCategories() + .task { + /* + recipeViewModel.presentLoadingIndicator = true + await appState.getCategories() + await appState.updateAllRecipeDetails() + + // Open detail view for default category + if UserSettings.shared.defaultCategory != "" { + if let cat = appState.categories.first(where: { c in + if c.name == UserSettings.shared.defaultCategory { + return true } + return false + }) { + recipeViewModel.selectedCategory = cat } } - } content: { - if let selectedCategory = cookbookState.selectedCategory { - List(selection: $cookbookState.selectedRecipeStub) { - ForEach(cookbookState.recipeStubs[selectedCategory.name] ?? [], id: \.id) { recipeStub in - Text(recipeStub.title) - .tag(recipeStub) - } - } - .onAppear { - Task { - await cookbookState.loadRecipeStubs(category: selectedCategory.name) - } - } - } else { - Text("Please select a category.") - .foregroundColor(.secondary) - } - } detail: { - if let selectedRecipe = cookbookState.selectedRecipe { - if let recipe = cookbookState.recipes[selectedRecipe.id] { - RecipeView(recipe: recipe) - } else { - ProgressView() - .onAppear { - Task { - await cookbookState.loadRecipe(id: selectedRecipe.id) - } - } - } - } else { - Text("Please select a recipe.") - .foregroundColor(.secondary) - } + await groceryList.load() + recipeViewModel.presentLoadingIndicator = false + */ } - .toolbar { - ToolbarItem(placement: .bottomBar) { - Button(action: { - cookbookState.showGroceries = true - }) { - Label("Grocery List", systemImage: "cart") - } - } - ToolbarItem(placement: .topBarLeading) { - Button(action: { - cookbookState.showSettings = true - }) { - Label("Settings", systemImage: "gearshape") - } - } - }*/ } } diff --git a/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift b/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift index da65d42..5b8c9b9 100644 --- a/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Onboarding/V2LoginView.swift @@ -8,7 +8,14 @@ import Foundation import SwiftUI import WebKit -/* +import AuthenticationServices + + +protocol LoginStage { + func next() -> Self + func previous() -> Self +} + enum V2LoginStage: LoginStage { case login, validate @@ -30,12 +37,27 @@ enum V2LoginStage: LoginStage { struct V2LoginView: View { - @Binding var showAlert: Bool - @Binding var alertMessage: String + @Environment(\.dismiss) var dismiss + @State var showAlert: Bool = false + @State var alertMessage: String = "" @State var loginStage: V2LoginStage = .login @State var loginRequest: LoginV2Request? = nil @State var presentBrowser = false + + @State var serverAddress: String = "" + @State var serverProtocol: ServerProtocol = .https + @State var loginPressed: Bool = false + @State var isLoading: Bool = false + + // Task reference for polling, to cancel if needed + @State private var pollTask: Task? = nil + + enum ServerProtocol: String { + case https="https://", http="http://" + + static let all = [https, http] + } // TextField handling enum Field { @@ -45,114 +67,205 @@ struct V2LoginView: View { } var body: some View { - ScrollView { - VStack(alignment: .leading) { - ServerAddressField() - CollapsibleView { - VStack(alignment: .leading) { - Text("Make sure to enter the server address in the form 'example.com', or \n':'\n when a non-standard port is used.") - .padding(.bottom) - Text("The 'Login' button will open a web browser. Please follow the login instructions provided there.\nAfter a successful login, return to this application and press 'Validate'.") - .padding(.bottom) - Text("If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually.") - } - } title: { - Text("Show help") - .foregroundColor(.white) - .font(.headline) - }.padding() - - if loginRequest != nil { - Button("Copy Link") { - UIPasteboard.general.string = loginRequest!.login - } - .font(.headline) - .foregroundStyle(.white) - .padding() + VStack { + HStack { + Button("Cancel") { + dismiss() } + Spacer() - HStack { - Button { - if UserSettings.shared.serverAddress == "" { - alertMessage = "Please enter a valid server address." - showAlert = true - return - } - - Task { - let error = await sendLoginV2Request() - if let error = error { - alertMessage = "A network error occured (\(error.localizedDescription))." - showAlert = true - } - if let loginRequest = loginRequest { - presentBrowser = true - //await UIApplication.shared.open(URL(string: loginRequest.login)!) - } else { - alertMessage = "Unable to reach server. Please check your server address and internet connection." - showAlert = true - } - } - loginStage = loginStage.next() - } label: { - Text("Login") - .foregroundColor(.white) - .font(.headline) - .padding() - .background( - RoundedRectangle(cornerRadius: 10) - .stroke(Color.white, lineWidth: 2) - .foregroundColor(.clear) - ) - }.padding() + if isLoading { + ProgressView() + } + }.padding() + + Form { + Section { + HStack { + Text("Server address:") + TextField("example.com", text: $serverAddress) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + } - if loginStage == .validate { - Spacer() - - Button { - // fetch login v2 response - Task { - let (response, error) = await fetchLoginV2Response() - checkLogin(response: response, error: error) - } - } label: { - Text("Validate") - .foregroundColor(.white) - .font(.headline) - .padding() - .background( - RoundedRectangle(cornerRadius: 10) - .stroke(Color.white, lineWidth: 2) - .foregroundColor(.clear) - ) + Picker("Server Protocol:", selection: $serverProtocol) { + ForEach(ServerProtocol.all, id: \.self) { + Text($0.rawValue) } - .disabled(loginRequest == nil ? true : false) - .padding() + } + + HStack { + Button("Login") { + initiateLoginV2() + } + Spacer() + Text(serverProtocol.rawValue + serverAddress.trimmingCharacters(in: .whitespacesAndNewlines)) + .foregroundStyle(Color.secondary) + } + + + } header: { + Text("Nextcloud Login") + } footer: { + Text( + """ + The 'Login' button will open a web browser. Please follow the login instructions provided there. + After a successful login, return to this application and press 'Validate'. + If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually. + """ + ) + }.disabled(loginPressed) + + if let loginRequest = loginRequest { + Section { + Text(loginRequest.login) + .font(.caption) + .foregroundStyle(.secondary) + Button("Copy Link") { + UIPasteboard.general.string = loginRequest.login + } + } footer: { + Text("If your browser does not open automatically, copy the link above and paste it manually. After a successful login, return to this application.") } } } } - .sheet(isPresented: $presentBrowser, onDismiss: { - Task { - let (response, error) = await fetchLoginV2Response() - checkLogin(response: response, error: error) + .sheet(isPresented: $presentBrowser) { + if let loginReq = loginRequest { + LoginBrowserView(authURL: URL(string: loginReq.login) ?? URL(string: "")!, callbackURLScheme: "nc") { result in + switch result { + case .success(let url): + print("Login completed with URL: \(url)") + + dismiss() + case .failure(let error): + print("Login failed: \(error.localizedDescription)") + self.alertMessage = error.localizedDescription + self.isLoading = false + self.loginPressed = false + self.showAlert = true + } + } + } else { + Text("Error: Login URL not available.") } - }) { - if let loginRequest = loginRequest { - WebViewSheet(url: loginRequest.login) + } + .alert("Error", isPresented: $showAlert) { + Button("Copy Error") { + print("Error copied: \(alertMessage)") + UIPasteboard.general.string = alertMessage + isLoading = false + loginPressed = false } + Button("Dismiss") { + print("Error dismissed.") + isLoading = false + loginPressed = false + } + } message: { + Text(alertMessage) } } - func sendLoginV2Request() async -> NetworkError? { - let (req, error) = await NextcloudApi.loginV2Request() - self.loginRequest = req - return error + func initiateLoginV2() { + isLoading = true + loginPressed = true + + Task { + let baseAddress = serverProtocol.rawValue + serverAddress.trimmingCharacters(in: .whitespacesAndNewlines) + let (req, error) = await NextcloudApi.loginV2Request(baseAddress) + + if let error = error { + self.alertMessage = error.localizedDescription + self.showAlert = true + self.isLoading = false + self.loginPressed = false + return + } + + guard let req = req else { + self.alertMessage = "Failed to get login URL from server." + self.showAlert = true + self.isLoading = false + self.loginPressed = false + return + } + + self.loginRequest = req + + // Present the browser session + presentBrowser = true + + // Start polling in a separate task + startPolling(pollURL: req.poll.endpoint, pollToken: req.poll.token) + } } - func fetchLoginV2Response() async -> (LoginV2Response?, NetworkError?) { - guard let loginRequest = loginRequest else { return (nil, .parametersNil) } - return await NextcloudApi.loginV2Response(req: loginRequest) + func startPolling(pollURL: String, pollToken: String) { + // Cancel any existing poll task first + pollTask?.cancel() + var pollingFailed = true + + pollTask = Task { + let maxRetries = 60 * 10 // Poll for up to 60 * 1 second = 1 minute + for _ in 0..) -> Void -struct WebViewSheet: View { - @Environment(\.dismiss) var dismiss - @State var url: String + func makeUIViewController(context: Context) -> UIViewController { + UIViewController() + } - var body: some View { - NavigationView { - WebView(url: URL(string: url)!) - .navigationBarTitle(Text("Nextcloud Login"), displayMode: .inline) - .navigationBarItems(trailing: Button("Done") { - dismiss() - }) + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + + if !context.coordinator.sessionStarted { + context.coordinator.sessionStarted = true + + let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: callbackURLScheme) { callbackURL, error in + context.coordinator.sessionStarted = false // Reset for potential retry + if let callbackURL = callbackURL { + completion(.success(callbackURL)) + } else if let error = error { + completion(.failure(error)) + } else { + // Handle unexpected nil URL and error + completion(.failure(LoginError.unknownError)) + } + } + + session.presentationContextProvider = context.coordinator + + session.prefersEphemeralWebBrowserSession = false + session.start() + } + } + + // MARK: - Coordinator for ASWebAuthenticationPresentationContextProviding + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, ASWebAuthenticationPresentationContextProviding { + var parent: LoginBrowserView + var sessionStarted: Bool = false // Prevent starting multiple sessions + + init(_ parent: LoginBrowserView) { + self.parent = parent + } + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { + return windowScene.windows.first! + } + return ASPresentationAnchor() + } + } + + enum LoginError: Error, LocalizedError { + case unknownError + var errorDescription: String? { + switch self { + case .unknownError: return "An unknown error occurred during login." + } } } } -struct WebView: UIViewRepresentable { - let url: URL - func makeUIView(context: Context) -> WKWebView { - return WKWebView() - } - - func updateUIView(_ uiView: WKWebView, context: Context) { - let request = URLRequest(url: url) - uiView.load(request) - } +#Preview { + V2LoginView() } -*/ diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift index e5611ed..0a038ec 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift @@ -8,10 +8,10 @@ import Foundation import SwiftUI -/* + struct RecipeCardView: View { - @EnvironmentObject var appState: AppState - @State var recipe: CookbookApiRecipeV1 + //@EnvironmentObject var appState: AppState + @State var recipe: Recipe @State var recipeThumb: UIImage? @State var isDownloaded: Bool? = nil @@ -50,6 +50,7 @@ struct RecipeCardView: View { .background(Color.backgroundHighlight) .clipShape(RoundedRectangle(cornerRadius: 17)) .task { + /* recipeThumb = await appState.getImage( id: recipe.recipe_id, size: .THUMB, @@ -59,18 +60,20 @@ struct RecipeCardView: View { recipe.storedLocally = appState.recipeDetailExists(recipeId: recipe.recipe_id) } isDownloaded = recipe.storedLocally + */ } .refreshable { + /* recipeThumb = await appState.getImage( id: recipe.recipe_id, size: .THUMB, fetchMode: UserSettings.shared.storeThumb ? .preferServer : .onlyServer - ) + )*/ } .frame(height: 80) } } -*/ + /* struct RecipeCardView: View { @State var state: AccountState diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift index 9f92c1d..d4569e6 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift @@ -7,6 +7,53 @@ import Foundation import SwiftUI +import SwiftData + + +struct RecipeListView: View { + @Environment(\.modelContext) var modelContext + @Query var recipes: [Recipe] + @Binding var selectedRecipe: Recipe? + @Binding var selectedCategory: String? + + init(selectedCategory: Binding, selectedRecipe: Binding) { + var predicate: Predicate? = nil + + if let category = selectedCategory.wrappedValue, category != "*" { + predicate = #Predicate { + $0.category == category + } + } + _recipes = Query(filter: predicate, sort: \.name) + _selectedRecipe = selectedRecipe + _selectedCategory = selectedCategory + } + + var body: some View { + List(selection: $selectedRecipe) { + ForEach(recipes) { recipe in + RecipeCardView(recipe: recipe) + .shadow(radius: 2) + .background( + NavigationLink(value: recipe) { + EmptyView() + } + .buttonStyle(.plain) + .opacity(0) + ) + .frame(height: 85) + .listRowInsets(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) + .listRowSeparatorTint(.clear) + } + } + .listStyle(.plain) + .navigationTitle("Recipes") + .toolbar { + + } + } + +} /* diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift index 4d77500..7e74af6 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift @@ -8,15 +8,15 @@ import Foundation import SwiftUI -/* + struct RecipeView: View { - @EnvironmentObject var appState: AppState + @Bindable var recipe: Recipe @Environment(\.dismiss) private var dismiss @StateObject var viewModel: ViewModel @GestureState private var dragOffset = CGSize.zero var imageHeight: CGFloat { - if let image = viewModel.recipeImage { + if let recipeImage = recipe.image, let image = recipeImage.image { return image.size.height < 350 ? image.size.height : 350 } return 200 @@ -33,8 +33,8 @@ struct RecipeView: View { coordinateSpace: CoordinateSpaces.scrollView, defaultHeight: imageHeight ) { - if let recipeImage = viewModel.recipeImage { - Image(uiImage: recipeImage) + if let recipeImage = recipe.image, let image = recipeImage.image { + Image(uiImage: image) .resizable() .scaledToFill() .frame(maxHeight: imageHeight + 200) @@ -54,15 +54,12 @@ struct RecipeView: View { VStack(alignment: .leading) { if viewModel.editMode { - RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe) - } - - if viewModel.editMode { - RecipeMetadataSection(viewModel: viewModel) + //RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe) + //RecipeMetadataSection(viewModel: viewModel) } HStack { - EditableText(text: $viewModel.observableRecipeDetail.name, editMode: $viewModel.editMode, titleKey: "Recipe Name") + EditableText(text: $recipe.name, editMode: $viewModel.editMode, titleKey: "Recipe Name") .font(.title) .bold() @@ -74,36 +71,37 @@ struct RecipeView: View { } }.padding([.top, .horizontal]) - if viewModel.observableRecipeDetail.description != "" || viewModel.editMode { - EditableText(text: $viewModel.observableRecipeDetail.description, editMode: $viewModel.editMode, titleKey: "Description", lineLimit: 0...5, axis: .vertical) + if recipe.recipeDescription != "" || viewModel.editMode { + EditableText(text: $recipe.recipeDescription, editMode: $viewModel.editMode, titleKey: "Description", lineLimit: 0...5, axis: .vertical) .fontWeight(.medium) .padding(.horizontal) .padding(.top, 2) } // Recipe Body Section - RecipeDurationSection(viewModel: viewModel) + RecipeDurationSection(recipe: recipe, editMode: $viewModel.editMode) Divider() LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { - if(!viewModel.observableRecipeDetail.recipeIngredient.isEmpty || viewModel.editMode) { - RecipeIngredientSection(viewModel: viewModel) + + if(!recipe.ingredients.isEmpty || viewModel.editMode) { + RecipeIngredientSection(recipe: recipe, editMode: $viewModel.editMode, presentIngredientEditView: $viewModel.presentIngredientEditView) } - if(!viewModel.observableRecipeDetail.recipeInstructions.isEmpty || viewModel.editMode) { - RecipeInstructionSection(viewModel: viewModel) + if(!recipe.instructions.isEmpty || viewModel.editMode) { + RecipeInstructionSection(recipe: recipe, editMode: $viewModel.editMode, presentInstructionEditView: $viewModel.presentInstructionEditView) } - if(!viewModel.observableRecipeDetail.tool.isEmpty || viewModel.editMode) { - RecipeToolSection(viewModel: viewModel) + if(!recipe.tools.isEmpty || viewModel.editMode) { + RecipeToolSection(recipe: recipe, editMode: $viewModel.editMode, presentToolEditView: $viewModel.presentToolEditView) } - RecipeNutritionSection(viewModel: viewModel) + RecipeNutritionSection(recipe: recipe, editMode: $viewModel.editMode) } if !viewModel.editMode { Divider() LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { - RecipeKeywordSection(viewModel: viewModel) - MoreInformationSection(viewModel: viewModel) + //RecipeKeywordSection(viewModel: viewModel) + MoreInformationSection(recipe: recipe) } } } @@ -115,21 +113,21 @@ struct RecipeView: View { .ignoresSafeArea(.container, edges: .top) .navigationBarTitleDisplayMode(.inline) .toolbar(.visible, for: .navigationBar) - //.toolbarTitleDisplayMode(.inline) + .navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "") .toolbar { RecipeViewToolBar(viewModel: viewModel) } .sheet(isPresented: $viewModel.presentShareSheet) { - ShareView(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), + /*ShareView(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), recipeImage: viewModel.recipeImage, - presentShareSheet: $viewModel.presentShareSheet) + presentShareSheet: $viewModel.presentShareSheet)*/ } .sheet(isPresented: $viewModel.presentInstructionEditView) { EditableListView( isPresented: $viewModel.presentInstructionEditView, - items: $viewModel.observableRecipeDetail.recipeInstructions, + items: $recipe.instructions, title: "Instructions", emptyListText: "Add cooking steps for fellow chefs to follow.", titleKey: "Instruction", @@ -139,7 +137,7 @@ struct RecipeView: View { .sheet(isPresented: $viewModel.presentIngredientEditView) { EditableListView( isPresented: $viewModel.presentIngredientEditView, - items: $viewModel.observableRecipeDetail.recipeIngredient, + items: $recipe.ingredients, title: "Ingredients", emptyListText: "Start by adding your first ingredient! 🥬", titleKey: "Ingredient", @@ -149,7 +147,7 @@ struct RecipeView: View { .sheet(isPresented: $viewModel.presentToolEditView) { EditableListView( isPresented: $viewModel.presentToolEditView, - items: $viewModel.observableRecipeDetail.tool, + items: $recipe.tools, title: "Tools", emptyListText: "List your tools here. 🍴", titleKey: "Tool", @@ -158,6 +156,7 @@ struct RecipeView: View { } .task { + /* // Load recipe detail if !viewModel.newRecipe { // For existing recipes, load the recipeDetail and image @@ -185,7 +184,7 @@ struct RecipeView: View { viewModel.setupView(recipeDetail: CookbookApiRecipeDetailV1()) viewModel.editMode = true viewModel.isDownloaded = false - } + }*/ } .alert(viewModel.alertType.localizedTitle, isPresented: $viewModel.presentAlert) { ForEach(viewModel.alertType.alertButtons) { buttonType in @@ -217,13 +216,14 @@ struct RecipeView: View { UIApplication.shared.isIdleTimerDisabled = false } .onChange(of: viewModel.editMode) { newValue in + /* if newValue && appState.allKeywords.isEmpty { Task { appState.allKeywords = await appState.getKeywords(fetchMode: .preferServer).sorted(by: { a, b in a.recipe_count > b.recipe_count }) } - } + }*/ } } @@ -231,9 +231,8 @@ struct RecipeView: View { // MARK: - RecipeView ViewModel class ViewModel: ObservableObject { - @Published var observableRecipeDetail: Recipe = Recipe() - @Published var recipeDetail: CookbookApiRecipeDetailV1 = CookbookApiRecipeDetailV1.error - @Published var recipeImage: UIImage? = nil + @Published var recipe: Recipe + @Published var editMode: Bool = false @Published var showTitle: Bool = false @Published var isDownloaded: Bool? = nil @@ -244,7 +243,6 @@ struct RecipeView: View { @Published var presentIngredientEditView: Bool = false @Published var presentToolEditView: Bool = false - var recipe: CookbookApiRecipeV1 var sharedURL: URL? = nil var newRecipe: Bool = false @@ -254,26 +252,13 @@ struct RecipeView: View { var alertAction: () async -> () = { } // Initializers - init(recipe: CookbookApiRecipeV1) { + init(recipe: Recipe) { self.recipe = recipe } init() { self.newRecipe = true - self.recipe = CookbookApiRecipeV1( - name: String(localized: "New Recipe"), - keywords: "", - dateCreated: "", - dateModified: "", - imageUrl: "", - imagePlaceholderUrl: "", - recipe_id: 0) - } - - // View setup - func setupView(recipeDetail: CookbookApiRecipeDetailV1) { - self.recipeDetail = recipeDetail - self.observableRecipeDetail = Recipe(recipeDetail) + self.recipe = Recipe() } func presentAlert(_ type: UserAlert, action: @escaping () async -> () = {}) { @@ -285,7 +270,7 @@ struct RecipeView: View { } - +/* extension RecipeView { func importRecipe(from url: String) async -> UserAlert? { let (scrapedRecipe, error) = await appState.importRecipe(url: url) @@ -309,13 +294,12 @@ extension RecipeView { return nil } } - +*/ // MARK: - Tool Bar struct RecipeViewToolBar: ToolbarContent { - @EnvironmentObject var appState: AppState @Environment(\.dismiss) private var dismiss @ObservedObject var viewModel: RecipeView.ViewModel @@ -385,6 +369,7 @@ struct RecipeViewToolBar: ToolbarContent { } func handleUpload() async { + /* if viewModel.newRecipe { print("Uploading new recipe.") if let recipeValidationError = recipeValid() { @@ -416,9 +401,11 @@ struct RecipeViewToolBar: ToolbarContent { } viewModel.editMode = false viewModel.presentAlert(RecipeAlert.UPLOAD_SUCCESS) + */ } func handleDelete() async { + /* let category = viewModel.observableRecipeDetail.recipeCategory guard let id = Int(viewModel.observableRecipeDetail.id) else { viewModel.presentAlert(RequestAlert.REQUEST_DROPPED) @@ -432,11 +419,13 @@ struct RecipeViewToolBar: ToolbarContent { await appState.getCategory(named: category, fetchMode: .preferServer) viewModel.presentAlert(RecipeAlert.DELETE_SUCCESS) dismiss() + */ } func recipeValid() -> RecipeAlert? { + /* // Check if the recipe has a name - if viewModel.observableRecipeDetail.name.replacingOccurrences(of: " ", with: "") == "" { + if viewModel.recipe.name.replacingOccurrences(of: " ", with: "") == "" { return RecipeAlert.NO_TITLE } @@ -454,12 +443,11 @@ struct RecipeViewToolBar: ToolbarContent { } } } + */ return nil } } -*/ - diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeDurationSection.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeDurationSection.swift index 3c1e0c3..c0a29a9 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeDurationSection.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeDurationSection.swift @@ -9,19 +9,20 @@ import Foundation import SwiftUI // MARK: - RecipeView Duration Section -/* + struct RecipeDurationSection: View { - @State var viewModel: RecipeView.ViewModel + @Bindable var recipe: Recipe + @Binding var editMode: Bool @State var presentPopover: Bool = false var body: some View { VStack(alignment: .leading) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) { - DurationView(time: viewModel.recipe.prepTime, title: LocalizedStringKey("Preparation")) - DurationView(time: viewModel.recipe.cookTime, title: LocalizedStringKey("Cooking")) - DurationView(time: viewModel.recipe.totalTime, title: LocalizedStringKey("Total time")) + DurationView(time: recipe.prepTimeDurationComponent, title: LocalizedStringKey("Preparation")) + DurationView(time: recipe.cookTimeDurationComponent, title: LocalizedStringKey("Cooking")) + DurationView(time: recipe.totalTimeDurationComponent, title: LocalizedStringKey("Total time")) } - if viewModel.editMode { + if editMode { Button { presentPopover.toggle() } label: { @@ -34,9 +35,9 @@ struct RecipeDurationSection: View { .padding() .popover(isPresented: $presentPopover) { EditableDurationView( - prepTime: viewModel.recipe.prepTime, - cookTime: viewModel.recipe.cookTime, - totalTime: viewModel.recipe.totalTime + prepTime: recipe.prepTimeDurationComponent, + cookTime: recipe.cookTimeDurationComponent, + totalTime: recipe.totalTimeDurationComponent ) } } @@ -143,4 +144,4 @@ fileprivate struct TimePickerView: View { } } -*/ + diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeIngredientSection.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeIngredientSection.swift index 61ba3c3..3d7b980 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeIngredientSection.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeIngredientSection.swift @@ -7,18 +7,24 @@ import Foundation import SwiftUI +import SwiftData // MARK: - RecipeView Ingredients Section -/* + struct RecipeIngredientSection: View { - @Environment(CookbookState.self) var cookbookState - @State var viewModel: RecipeView.ViewModel + @Environment(\.modelContext) var modelContext + @Bindable var recipe: Recipe + @Binding var editMode: Bool + @Binding var presentIngredientEditView: Bool + @State var recipeGroceries: RecipeGroceries? = nil + var body: some View { VStack(alignment: .leading) { HStack { Button { withAnimation { + /* if cookbookState.groceryList.containsRecipe(viewModel.recipe.id) { cookbookState.groceryList.deleteGroceryRecipe(viewModel.recipe.id) } else { @@ -28,6 +34,7 @@ struct RecipeIngredientSection: View { recipeName: viewModel.recipe.name ) } + */ } } label: { if #available(iOS 17.0, *) { @@ -35,7 +42,7 @@ struct RecipeIngredientSection: View { } else { Image(systemName: "heart.text.square") } - }.disabled(viewModel.editMode) + }.disabled(editMode) SecondaryLabel(text: LocalizedStringKey("Ingredients")) @@ -45,26 +52,30 @@ struct RecipeIngredientSection: View { .foregroundStyle(.secondary) .bold() - ServingPickerView(selectedServingSize: $viewModel.recipe.ingredientMultiplier) + ServingPickerView(selectedServingSize: $recipe.ingredientMultiplier) } - ForEach(0.. { $0.id == categoryId } + let fetchDescriptor = FetchDescriptor(predicate: categoryPredicate) + + if let existingCategory = try modelContext.fetch(fetchDescriptor).first { + // Delete category if it exists + modelContext.delete(existingCategory) + } else { + // Create the category if it doesn't exist + let newCategory = RecipeGroceries(id: categoryId, name: name) + modelContext.insert(newCategory) + + // Add new GroceryItems to the category + for itemName in itemNames { + let newItem = GroceryItem(name: itemName, isChecked: false) + newCategory.items.append(newItem) + } + + try modelContext.save() + } + } catch { + print("Error adding grocery items: \(error.localizedDescription)") + } + } + + func toggleGroceryItem(_ itemName: String, inCategory categoryId: String, named name: String) { + do { + // Find or create the target category + let categoryPredicate = #Predicate { $0.id == categoryId } + let fetchDescriptor = FetchDescriptor(predicate: categoryPredicate) + + if let existingCategory = try modelContext.fetch(fetchDescriptor).first { + // Delete item if it exists + if existingCategory.items.contains(where: { $0.name == itemName }) { + existingCategory.items.removeAll { $0.name == itemName } + + // Delete category if empty + if existingCategory.items.isEmpty { + modelContext.delete(existingCategory) + } + } else { + existingCategory.items.append(GroceryItem(name: itemName, isChecked: false)) + } + } else { + // Add the category if it doesn't exist + let newCategory = RecipeGroceries(id: categoryId, name: name) + modelContext.insert(newCategory) + + // Add the item to the new category + newCategory.items.append(GroceryItem(name: itemName, isChecked: false)) + } + + try modelContext.save() + } catch { + print("Error adding grocery items: \(error.localizedDescription)") + } } } // MARK: - RecipeIngredientSection List Item - +/* fileprivate struct IngredientListItem: View { - @Environment(CookbookState.self) var cookbookState + @Environment(\.modelContext) var modelContext + @Bindable var recipeGroceries: RecipeGroceries @Binding var ingredient: String @Binding var servings: Double @State var recipeYield: Double @State var recipeId: String - let addToGroceryListAction: () -> Void + @State var modifiedIngredient: AttributedString = "" @State var isSelected: Bool = false @@ -110,7 +182,7 @@ fileprivate struct IngredientListItem: View { var body: some View { HStack(alignment: .top) { - if cookbookState.groceryList.containsItem(at: recipeId, item: ingredient) { + if recipeGroceries.items.contains(ingredient) { if #available(iOS 17.0, *) { Image(systemName: "storefront") .foregroundStyle(Color.green) @@ -168,7 +240,7 @@ fileprivate struct IngredientListItem: View { .onEnded { gesture in withAnimation { if dragOffset > maxDragDistance * 0.3 { // Swipe threshold - if cookbookState.groceryList.containsItem(at: recipeId, item: ingredient) { + if recipeGroceries.items.contains(ingredient) { cookbookState.groceryList.deleteItem(ingredient, fromRecipe: recipeId) } else { addToGroceryListAction() @@ -182,7 +254,7 @@ fileprivate struct IngredientListItem: View { ) } } - +*/ struct ServingPickerView: View { @@ -217,4 +289,4 @@ struct ServingPickerView: View { } } -*/ + diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeInstructionSection.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeInstructionSection.swift index de56f77..353c058 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeInstructionSection.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeInstructionSection.swift @@ -9,22 +9,26 @@ import Foundation import SwiftUI // MARK: - RecipeView Instructions Section -/* -struct RecipeInstructionSection: View { - @State var viewModel: RecipeView.ViewModel +struct RecipeInstructionSection: View { + @Bindable var recipe: Recipe + @Binding var editMode: Bool + @Binding var presentInstructionEditView: Bool + + var body: some View { + VStack(alignment: .leading) { HStack { SecondaryLabel(text: LocalizedStringKey("Instructions")) Spacer() } - ForEach(viewModel.recipe.recipeInstructions.indices, id: \.self) { ix in - RecipeInstructionListItem(instruction: $viewModel.recipe.recipeInstructions[ix], index: ix+1) + ForEach(recipe.instructions.indices, id: \.self) { ix in + RecipeInstructionListItem(instruction: $recipe.instructions[ix], index: ix+1) } - if viewModel.editMode { + if editMode { Button { - viewModel.presentInstructionEditView.toggle() + presentInstructionEditView.toggle() } label: { Text("Edit") } @@ -32,11 +36,10 @@ struct RecipeInstructionSection: View { } } .padding() - } } - +// MARK: - Preview fileprivate struct RecipeInstructionListItem: View { @Binding var instruction: String @@ -56,4 +59,45 @@ fileprivate struct RecipeInstructionListItem: View { .animation(.easeInOut, value: isSelected) } } -*/ + +struct RecipeInstructionSection_Previews: PreviewProvider { + static var previews: some View { + // Create a mock recipe + @State var mockRecipe = createRecipe() + + // Create mock state variables for the @Binding properties + @State var mockEditMode = true + @State var mockPresentInstructionEditView = false + + // Provide the mock data to the view + RecipeInstructionSection( + recipe: mockRecipe, + editMode: $mockEditMode, + presentInstructionEditView: $mockPresentInstructionEditView + ) + .previewDisplayName("Instructions - Edit Mode") + + RecipeInstructionSection( + recipe: mockRecipe, + editMode: $mockEditMode, + presentInstructionEditView: $mockPresentInstructionEditView + ) + .previewDisplayName("Instructions - Read Only") + .environment(\.editMode, .constant(.inactive)) + } + + static func createRecipe() -> Recipe { + let recipe = Recipe() + recipe.name = "Mock Recipe" + recipe.instructions = [ + "Step 1: Gather all ingredients and equipment.", + "Step 2: Preheat oven to 180°C (350°F) and prepare baking dish.", + "Step 3: Combine dry ingredients in a large bowl and mix thoroughly.", + "Step 4: In a separate bowl, whisk wet ingredients until smooth.", + "Step 5: Gradually add wet ingredients to dry ingredients, mixing until just combined. Do not overmix.", + "Step 6: Pour the mixture into the prepared baking dish and bake for 30-35 minutes, or until golden brown and a toothpick inserted into the center comes out clean.", + "Step 7: Let cool before serving. Enjoy!" + ] + return recipe + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeMetadataSection.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeMetadataSection.swift index 3027574..d5a519d 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeMetadataSection.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeMetadataSection.swift @@ -121,27 +121,27 @@ fileprivate struct PickerPopoverView Binding { Binding( - get: { viewModel.recipe.nutrition[key, default: ""] }, - set: { viewModel.recipe.nutrition[key] = $0 } + get: { recipe.nutrition[key, default: ""] }, + set: { recipe.nutrition[key] = $0 } ) } func nutritionEmpty() -> Bool { for nutrition in Nutrition.allCases { - if let value = viewModel.recipe.nutrition[nutrition.dictKey] { + if let value = recipe.nutrition[nutrition.dictKey] { return false } } @@ -71,4 +72,3 @@ struct RecipeNutritionSection: View { } } -*/ diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeToolSection.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeToolSection.swift index 5f23660..de51ac0 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeToolSection.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeViewSections/RecipeToolSection.swift @@ -9,9 +9,11 @@ import Foundation import SwiftUI // MARK: - RecipeView Tool Section -/* + struct RecipeToolSection: View { - @State var viewModel: RecipeView.ViewModel + @Bindable var recipe: Recipe + @Binding var editMode: Bool + @Binding var presentToolEditView: Bool var body: some View { VStack(alignment: .leading) { @@ -20,11 +22,11 @@ struct RecipeToolSection: View { Spacer() } - RecipeListSection(list: $viewModel.recipe.tool) + RecipeListSection(list: $recipe.tools) - if viewModel.editMode { + if editMode { Button { - viewModel.presentToolEditView.toggle() + presentToolEditView.toggle() } label: { Text("Edit") } @@ -36,4 +38,4 @@ struct RecipeToolSection: View { } -*/ + diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift index c0d16f4..fa64d32 100644 --- a/Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/CollapsibleView.swift @@ -7,7 +7,7 @@ import Foundation import SwiftUI -/* + struct CollapsibleView: View { @State var titleColor: Color = .white @State var isCollapsed: Bool = true @@ -48,4 +48,4 @@ struct CollapsibleView: View { } } } -*/ + diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/ListVStack.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ListVStack.swift new file mode 100644 index 0000000..0a34cb6 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ListVStack.swift @@ -0,0 +1,38 @@ +// +// ListVStack.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 29.05.25. +// +import SwiftUI + +struct ListVStack: View { + @Binding var items: [Element] + let header: () -> HeaderContent + let rows: (Int, Binding) -> RowContent + + init(_ items: Binding<[Element]>, header: @escaping () -> HeaderContent, rows: @escaping (Int, Binding) -> RowContent) { + self._items = items + self.header = header + self.rows = rows + } + + var body: some View { + VStack(alignment: .leading) { + header() + .padding(.horizontal, 30) + VStack(alignment: .leading, spacing: 0) { + ForEach(items.indices, id: \.self) { index in + rows(index, $items[index]) + .padding(10) + + + } + } + .padding(4) + .background(Color.secondary.opacity(0.1)) + .clipShape(RoundedRectangle(cornerRadius: 15)) + .padding(.horizontal) + } + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift index ad273b9..2a3e64d 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/GroceryListTabView.swift @@ -7,55 +7,158 @@ import Foundation import SwiftUI +import SwiftData + +@Model class GroceryItem { + var name: String + var isChecked: Bool + + init(name: String, isChecked: Bool) { + self.name = name + self.isChecked = isChecked + } +} + +@Model class RecipeGroceries: Identifiable { + var id: String + var name: String + @Relationship(deleteRule: .cascade) var items: [GroceryItem] + var multiplier: Double + + init(id: String, name: String, items: [GroceryItem], multiplier: Double) { + self.id = id + self.name = name + self.items = items + self.multiplier = multiplier + } + + init(id: String, name: String) { + self.id = id + self.name = name + self.items = [] + self.multiplier = 1 + } +} -/* struct GroceryListTabView: View { - @Environment(CookbookState.self) var cookbookState - + @Environment(\.modelContext) var modelContext + @Query var groceryList: [RecipeGroceries] = [] + @State var newGroceries: String = "" + @FocusState private var isFocused: Bool + var body: some View { NavigationStack { - if cookbookState.groceryList.groceryDict.isEmpty { - EmptyGroceryListView() - } else { - List { - ForEach(cookbookState.groceryList.groceryDict.keys.sorted(), id: \.self) { key in - Section { - ForEach(cookbookState.groceryList.groceryDict[key]!.items) { item in - GroceryListItemView(item: item, toggleAction: { - cookbookState.groceryList.toggleItemChecked(item) - }, deleteAction: { - withAnimation { - cookbookState.groceryList.deleteItem(item.name, fromRecipe: key) - } - }) + List { + HStack(alignment: .top) { + TextEditor(text: $newGroceries) + .padding(4) + .overlay(RoundedRectangle(cornerRadius: 8) + .stroke(Color.secondary).opacity(0.5)) + .focused($isFocused) + Button { + if !newGroceries.isEmpty { + let items = newGroceries + .split(separator: "\n") + .compactMap { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + Task { + await addGroceryItems(items, toCategory: "Other", named: String(localized: "Other")) } - } header: { - HStack { - Text(cookbookState.groceryList.groceryDict[key]!.name) + } + newGroceries = "" + + } label: { + Text("Add") + } + .disabled(newGroceries.isEmpty) + .buttonStyle(.borderedProminent) + } + + + ForEach(groceryList, id: \.name) { category in + Section { + ForEach(category.items, id: \.self) { item in + GroceryListItemView(item: item) + } + } header: { + HStack { + Text(category.name) + .foregroundStyle(Color.nextcloudBlue) + Spacer() + Button { + modelContext.delete(category) + } label: { + Image(systemName: "trash") .foregroundStyle(Color.nextcloudBlue) - Spacer() - Button { - cookbookState.groceryList.deleteGroceryRecipe(key) - } label: { - Image(systemName: "trash") - .foregroundStyle(Color.nextcloudBlue) - } } } } } - .listStyle(.plain) - .navigationTitle("Grocery List") - .toolbar { - Button { - cookbookState.groceryList.deleteAll() - } label: { - Text("Delete") - .foregroundStyle(Color.nextcloudBlue) - } + if groceryList.isEmpty { + Text("You're all set for cooking 🍓") + .font(.headline) + Text("Add groceries to this list by either using the button next to an ingredient list in a recipe, or by swiping right on individual ingredients of a recipe.") + .foregroundStyle(.secondary) + Text("To add grocieries manually, type them in the box below and press the button. To add multiple items at once, separate them by a new line.") + .foregroundStyle(.secondary) + Text("Your grocery list is stored locally and therefore not synchronized across your devices.") + .foregroundStyle(.secondary) } } + + .listStyle(.plain) + .navigationTitle("Grocery List") + .toolbar { + Button { + do { + try modelContext.delete(model: RecipeGroceries.self) + } catch { + print("Failed to delete all GroceryCategory models.") + } + } label: { + Text("Delete") + .foregroundStyle(Color.nextcloudBlue) + } + } + + } + } + + private func addGroceryItems(_ itemNames: [String], toCategory categoryId: String, named name: String) async { + do { + // Find or create the target category + let categoryPredicate = #Predicate { $0.id == categoryId } + let fetchDescriptor = FetchDescriptor(predicate: categoryPredicate) + + var targetCategory: RecipeGroceries? + if let existingCategory = try modelContext.fetch(fetchDescriptor).first { + targetCategory = existingCategory + } else { + // Create the category if it doesn't exist + let newCategory = RecipeGroceries(id: categoryId, name: name) + modelContext.insert(newCategory) + targetCategory = newCategory + } + + guard let category = targetCategory else { return } + + // Add new GroceryItems to the category + for itemName in itemNames { + let newItem = GroceryItem(name: itemName, isChecked: false) + category.items.append(newItem) + } + + try modelContext.save() + } catch { + print("Error adding grocery items: \(error.localizedDescription)") + } + } + + private func deleteGroceryItems(at offsets: IndexSet, in category: RecipeGroceries) { + for index in offsets { + let itemToDelete = category.items[index] + modelContext.delete(itemToDelete) } } } @@ -63,9 +166,8 @@ struct GroceryListTabView: View { fileprivate struct GroceryListItemView: View { - let item: GroceryRecipeItem - let toggleAction: () -> Void - let deleteAction: () -> Void + @Environment(\.modelContext) var modelContext + @Bindable var item: GroceryItem var body: some View { HStack(alignment: .top) { @@ -81,149 +183,13 @@ fileprivate struct GroceryListItemView: View { } .padding(5) .foregroundStyle(item.isChecked ? Color.secondary : Color.primary) - .onTapGesture(perform: toggleAction) + .onTapGesture(perform: { item.isChecked.toggle() }) .animation(.easeInOut, value: item.isChecked) .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(action: deleteAction) { + Button(action: { modelContext.delete(item) }) { Label("Delete", systemImage: "trash") } .tint(.red) } } } - - - -fileprivate struct EmptyGroceryListView: View { - var body: some View { - List { - Text("You're all set for cooking 🍓") - .font(.headline) - Text("Add groceries to this list by either using the button next to an ingredient list in a recipe, or by swiping right on individual ingredients of a recipe.") - .foregroundStyle(.secondary) - Text("Your grocery list is stored locally and therefore not synchronized across your devices.") - .foregroundStyle(.secondary) - } - .navigationTitle("Grocery List") - } -} - - -// Grocery List Logic - - -class GroceryRecipe: Identifiable, Codable { - let name: String - var items: [GroceryRecipeItem] - - init(name: String, items: [GroceryRecipeItem]) { - self.name = name - self.items = items - } - - init(name: String, item: GroceryRecipeItem) { - self.name = name - self.items = [item] - } -} - - - -class GroceryRecipeItem: Identifiable, Codable { - let name: String - var isChecked: Bool - - init(_ name: String, isChecked: Bool = false) { - self.name = name - self.isChecked = isChecked - } -} - - - -@Observable class GroceryList { - let dataStore: DataStore = DataStore() - var groceryDict: [String: GroceryRecipe] = [:] - var sortBySimilarity: Bool = false - - - func addItem(_ itemName: String, toRecipe recipeId: String, recipeName: String? = nil, saveGroceryDict: Bool = true) { - print("Adding item of recipe \(String(describing: recipeName))") - if self.groceryDict[recipeId] != nil { - self.groceryDict[recipeId]?.items.append(GroceryRecipeItem(itemName)) - } else { - let newRecipe = GroceryRecipe(name: recipeName ?? "-", items: [GroceryRecipeItem(itemName)]) - self.groceryDict[recipeId] = newRecipe - } - if saveGroceryDict { - self.save() - } - } - - func addItems(_ items: [String], toRecipe recipeId: String, recipeName: String? = nil) { - for item in items { - addItem(item, toRecipe: recipeId, recipeName: recipeName, saveGroceryDict: false) - } - save() - } - - func deleteItem(_ itemName: String, fromRecipe recipeId: String) { - print("Deleting item \(itemName)") - guard let recipe = groceryDict[recipeId] else { return } - guard let itemIndex = groceryDict[recipeId]?.items.firstIndex(where: { $0.name == itemName }) else { return } - groceryDict[recipeId]?.items.remove(at: itemIndex) - if groceryDict[recipeId]!.items.isEmpty { - groceryDict.removeValue(forKey: recipeId) - } - save() - } - - func deleteGroceryRecipe(_ recipeId: String) { - print("Deleting grocery recipe with id \(recipeId)") - groceryDict.removeValue(forKey: recipeId) - save() - } - - func deleteAll() { - print("Deleting all grocery items") - groceryDict = [:] - save() - } - - func toggleItemChecked(_ groceryItem: GroceryRecipeItem) { - print("Item checked: \(groceryItem.name)") - groceryItem.isChecked.toggle() - save() - } - - func containsItem(at recipeId: String, item: String) -> Bool { - guard let recipe = groceryDict[recipeId] else { return false } - if recipe.items.contains(where: { $0.name == item }) { - return true - } - return false - } - - func containsRecipe(_ recipeId: String) -> Bool { - return groceryDict[recipeId] != nil - } - - func save() { - Task { - await dataStore.save(data: groceryDict, toPath: "grocery_list.data") - } - } - - func load() async { - do { - guard let groceryDict: [String: GroceryRecipe] = try await dataStore.load( - fromPath: "grocery_list.data" - ) else { return } - self.groceryDict = groceryDict - } catch { - print("Unable to load grocery list") - } - } -} - -*/ diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift index 580bb42..0b5c396 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift @@ -7,88 +7,86 @@ import Foundation import SwiftUI +import SwiftData + -/* struct RecipeTabView: View { - @EnvironmentObject var appState: AppState - @EnvironmentObject var groceryList: GroceryList - @EnvironmentObject var viewModel: RecipeTabView.ViewModel + //@State var cookbookState: CookbookState = CookbookState() + @Environment(\.modelContext) var modelContext + @Query var recipes: [Recipe] + @State var categories: [(String, Int)] = [] + @State private var selectedRecipe: Recipe? + @State private var selectedCategory: String? = "*" var body: some View { NavigationSplitView { - List(selection: $viewModel.selectedCategory) { - // Categories - ForEach(appState.categories) { category in - NavigationLink(value: category) { - HStack(alignment: .center) { - if viewModel.selectedCategory != nil && - category.name == viewModel.selectedCategory!.name { - Image(systemName: "book") - } else { - Image(systemName: "book.closed.fill") - } - - if category.name == "*" { - Text("Other") - .font(.system(size: 20, weight: .medium, design: .default)) - } else { - Text(category.name) - .font(.system(size: 20, weight: .medium, design: .default)) - } - - Spacer() - Text("\(category.recipe_count)") - .font(.system(size: 15, weight: .bold, design: .default)) - .foregroundStyle(Color.background) - .frame(width: 25, height: 25, alignment: .center) - .minimumScaleFactor(0.5) - .background { - Circle() - .foregroundStyle(Color.secondary) - } - }.padding(7) + List(selection: $selectedCategory) { + CategoryListItem(category: "All Recipes", count: recipes.count, isSelected: selectedCategory == "*") + .tag("*") // Tag nil to select all recipes + + Section("Categories") { + ForEach(categories, id: \.0.self) { category in + CategoryListItem(category: category.0, count: category.1, isSelected: selectedCategory == category.0) + .tag(category.0) } } } - .navigationTitle("Cookbooks") - .toolbar { - RecipeTabViewToolBar() - } - .navigationDestination(isPresented: $viewModel.presentSettingsView) { - SettingsView() - .environmentObject(appState) - } - .navigationDestination(isPresented: $viewModel.presentEditView) { - RecipeView(viewModel: RecipeView.ViewModel()) - .environmentObject(appState) - .environmentObject(groceryList) - } + .navigationTitle("Categories") + } content: { + RecipeListView(selectedCategory: $selectedCategory, selectedRecipe: $selectedRecipe) } detail: { - NavigationStack { - if let category = viewModel.selectedCategory { - RecipeListView( - categoryName: category.name, - showEditView: $viewModel.presentEditView - ) - .id(category.id) // Workaround: This is needed to update the detail view when the selection changes - } - + // Use a conditional view based on selection + if let selectedRecipe { + //RecipeDetailView(recipe: recipe) // Create a dedicated detail view + RecipeView(recipe: selectedRecipe, viewModel: RecipeView.ViewModel(recipe: selectedRecipe)) + } else { + ContentUnavailableView("Select a Recipe", systemImage: "fork.knife.circle") } } - .tint(.nextcloudBlue) .task { - let connection = await appState.checkServerConnection() - DispatchQueue.main.async { - viewModel.serverConnection = connection + initCategories() + return + do { + try modelContext.delete(model: Recipe.self) + } catch { + print("Failed to delete recipes and categories.") + } + + guard let categories = await CookbookApiV1.getCategories(auth: UserSettings.shared.authString).0 else { return } + for category in categories { + guard let recipeStubs = await CookbookApiV1.getCategory(auth: UserSettings.shared.authString, named: category.name).0 else { return } + for recipeStub in recipeStubs { + guard let recipe = await CookbookApiV1.getRecipe(auth: UserSettings.shared.authString, id: recipeStub.id).0 else { return } + modelContext.insert(recipe) + } + } + + }/* + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button(action: { + //cookbookState.showSettings = true + }) { + Label("Settings", systemImage: "gearshape") + } + } + }*/ + } + + func initCategories() { + // Load Categories + var categoryDict: [String: Int] = [:] + for recipe in recipes { + // Ensure "Uncategorized" is a valid category if used + if !recipe.category.isEmpty { + categoryDict[recipe.category, default: 0] += 1 + } else { + categoryDict["Other", default: 0] += 1 } } - .refreshable { - let connection = await appState.checkServerConnection() - DispatchQueue.main.async { - viewModel.serverConnection = connection - } - await appState.getCategories() - } + categories = categoryDict.map { + ($0.key, $0.value) + }.sorted { $0.0 < $1.0 } } class ViewModel: ObservableObject { @@ -98,13 +96,40 @@ struct RecipeTabView: View { @Published var presentLoadingIndicator: Bool = false @Published var presentConnectionPopover: Bool = false @Published var serverConnection: Bool = false - - @Published var selectedCategory: Category? = nil } } - +fileprivate struct CategoryListItem: View { + var category: String + var count: Int + var isSelected: Bool + + var body: some View { + HStack(alignment: .center) { + if isSelected { + Image(systemName: "book") + } else { + Image(systemName: "book.closed.fill") + } + + Text(category) + .font(.system(size: 20, weight: .medium, design: .default)) + + Spacer() + Text("\(count)") + .font(.system(size: 15, weight: .bold, design: .default)) + .foregroundStyle(Color.background) + .frame(width: 25, height: 25, alignment: .center) + .minimumScaleFactor(0.5) + .background { + Circle() + .foregroundStyle(Color.secondary) + } + }.padding(7) + } +} +/* fileprivate struct RecipeTabViewToolBar: ToolbarContent { @EnvironmentObject var appState: AppState @EnvironmentObject var viewModel: RecipeTabView.ViewModel diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/SettingsTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/SettingsTabView.swift new file mode 100644 index 0000000..cc75ee9 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/SettingsTabView.swift @@ -0,0 +1,234 @@ +// +// SettingsTabView.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 29.05.25. +// + +import Foundation +import SwiftUI + + +struct SettingsTabView: View { + @ObservedObject var userSettings = UserSettings.shared + + @State private var avatarImage: UIImage? + @State private var userData: UserData? + + @State private var showAlert: Bool = false + @State private var alertType: SettingsAlert = .NONE + + @State private var presentLoginSheet: Bool = false + + enum SettingsAlert { + case LOG_OUT, + DELETE_CACHE, + NONE + + func getTitle() -> String { + switch self { + case .LOG_OUT: return "Log out" + case .DELETE_CACHE: return "Delete local data" + default: return "Please confirm your action." + } + } + + func getMessage() -> String { + switch self { + case .LOG_OUT: return "Are you sure that you want to log out of your account?" + case .DELETE_CACHE: return "Are you sure that you want to delete the downloaded recipes? This action will not affect any recipes stored on your server." + default: return "" + } + } + } + + var body: some View { + Form { + Section { + if userSettings.authString.isEmpty { + HStack(alignment: .center) { + if let avatarImage = avatarImage { + Image(uiImage: avatarImage) + .resizable() + .clipShape(Circle()) + .frame(width: 100, height: 100) + + } + if let userData = userData { + VStack(alignment: .leading) { + Text(userData.userDisplayName) + .font(.title) + .padding(.leading) + Text("Username: \(userData.userId)") + .font(.subheadline) + .padding(.leading) + + + // TODO: Add actions + } + } + Spacer() + } + + Button("Log out") { + print("Log out.") + alertType = .LOG_OUT + showAlert = true + } + .tint(.red) + } else { + Button("Log in") { + print("Log in.") + presentLoginSheet.toggle() + } + + } + + } header: { + Text("Nextcloud") + } footer: { + Text("Log in to your Nextcloud account to sync your recipes. This requires a Nextcloud server with the Nextcloud Cookbook application installed.") + } + + Section { + Toggle(isOn: $userSettings.expandNutritionSection) { + Text("Expand nutrition section") + } + Toggle(isOn: $userSettings.expandKeywordSection) { + Text("Expand keyword section") + } + Toggle(isOn: $userSettings.expandInfoSection) { + Text("Expand information section") + } + } header: { + Text("Recipes") + } footer: { + Text("Configure which sections in your recipes are expanded by default.") + } + + Section { + Toggle(isOn: $userSettings.keepScreenAwake) { + Text("Keep screen awake when viewing recipes") + } + } + + Section { + HStack { + Text("Decimal number format") + Spacer() + Picker("", selection: $userSettings.decimalNumberSeparator) { + Text("Point (e.g. 1.42)").tag(".") + Text("Comma (e.g. 1,42)").tag(",") + } + .pickerStyle(.menu) + } + } footer: { + Text("This setting will take effect after the app is restarted. It affects the adjustment of ingredient quantities.") + } + + Section { + Toggle(isOn: $userSettings.storeRecipes) { + Text("Offline recipes") + } + Toggle(isOn: $userSettings.storeImages) { + Text("Store recipe images locally") + } + Toggle(isOn: $userSettings.storeThumb) { + Text("Store recipe thumbnails locally") + } + } header: { + Text("Downloads") + } footer: { + Text("Configure what is stored on your device.") + } + + Section { + Picker("Language", selection: $userSettings.language) { + ForEach(SupportedLanguage.allValues, id: \.self) { lang in + Text(lang.descriptor()).tag(lang.rawValue) + } + } + } footer: { + Text("If \'Same as Device\' is selected and your device language is not supported yet, this option will default to english.") + } + + + Section { + Link("Visit the GitHub page", destination: URL(string: "https://github.com/VincentMeilinger/Nextcloud-Cookbook-iOS")!) + } header: { + Text("About") + } footer: { + Text("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.") + } + + Section { + Link("Get support", destination: URL(string: "https://vincentmeilinger.github.io/Nextcloud-Cookbook-Client-Support/")!) + } header: { + Text("Support") + } footer: { + Text("If you have any inquiries, feedback, or require assistance, please refer to the support page for contact information.") + } + + Section { + Button("Delete local data") { + print("Clear cache.") + alertType = .DELETE_CACHE + showAlert = true + } + .tint(.red) + + } header: { + Text("Other") + } footer: { + Text("Deleting local data will not affect the recipe data stored on your server.") + } + + Section(header: Text("Acknowledgements")) { + VStack(alignment: .leading) { + if let url = URL(string: "https://github.com/scinfu/SwiftSoup") { + Link("SwiftSoup", destination: url) + .font(.headline) + Text("An HTML parsing and web scraping library for Swift. Used for importing schema.org recipes from websites.") + } + } + VStack(alignment: .leading) { + if let url = URL(string: "https://github.com/techprimate/TPPDF") { + Link("TPPDF", destination: url) + .font(.headline) + Text("A simple-to-use PDF builder for Swift. Used for generating recipe PDF documents.") + } + } + } + } + + .navigationTitle("Settings") + .alert(alertType.getTitle(), isPresented: $showAlert) { + Button("Cancel", role: .cancel) { } + if alertType == .DELETE_CACHE { + Button("Delete", role: .destructive) { deleteCachedData() } + } + } message: { + Text(alertType.getMessage()) + } + .task { + await getUserData() + } + .sheet(isPresented: $presentLoginSheet, onDismiss: {}) { + V2LoginView() + } + } + + func getUserData() async { + let (data, _) = await NextcloudApi.getAvatar() + let (userData, _) = await NextcloudApi.getHoverCard() + + DispatchQueue.main.async { + self.avatarImage = data + self.userData = userData + } + } + + func deleteCachedData() { + print("TODO: Delete cached data\n") + } +}