From d3e0366ce64b647ae3a75ea12d3de573bfc5df25 Mon Sep 17 00:00:00 2001 From: VincentMeilinger Date: Sun, 18 Feb 2024 12:25:18 +0100 Subject: [PATCH] New Recipe Edit View --- .../project.pbxproj | 76 ++- .../UserInterfaceState.xcuserstate | Bin 112038 -> 117266 bytes .../{Models => }/AppState.swift | 0 .../Data/DataModels.swift | 160 +---- .../Data/RecipeModels.swift | 214 ++++++ .../Localizable.xcstrings | 118 +++- .../Network/ApiRequest.swift | 4 +- .../Network/CustomError.swift | 70 -- .../Network/NetworkError.swift | 23 + .../Network/NetworkHandler.swift | 80 --- .../Network/NetworkRequests.swift | 169 ----- .../Network/NetworkUtils.swift | 50 ++ .../{ => Util}/Alerts.swift | 0 .../{Data => Util}/DurationComponents.swift | 0 .../{ => Util}/SupportedLanguage.swift | 0 .../Views/Recipes/RecipeDetailView.swift | 553 --------------- ...yDetailView.swift => RecipeListView.swift} | 4 +- .../Views/Recipes/RecipeView.swift | 633 ++++++++++++++++++ .../Views/Recipes/ShareView.swift | 54 ++ .../ReusableViews/ReorderableForEach.swift | 118 ++++ .../Views/Tabs/RecipeTabView.swift | 2 +- .../Views/Tabs/SearchTabView.swift | 2 +- Nextcloud-Cookbook-iOS-Client-Info.plist | 17 + 23 files changed, 1261 insertions(+), 1086 deletions(-) rename Nextcloud Cookbook iOS Client/{Models => }/AppState.swift (100%) create mode 100644 Nextcloud Cookbook iOS Client/Data/RecipeModels.swift delete mode 100644 Nextcloud Cookbook iOS Client/Network/CustomError.swift create mode 100644 Nextcloud Cookbook iOS Client/Network/NetworkError.swift delete mode 100644 Nextcloud Cookbook iOS Client/Network/NetworkHandler.swift delete mode 100644 Nextcloud Cookbook iOS Client/Network/NetworkRequests.swift create mode 100644 Nextcloud Cookbook iOS Client/Network/NetworkUtils.swift rename Nextcloud Cookbook iOS Client/{ => Util}/Alerts.swift (100%) rename Nextcloud Cookbook iOS Client/{Data => Util}/DurationComponents.swift (100%) rename Nextcloud Cookbook iOS Client/{ => Util}/SupportedLanguage.swift (100%) delete mode 100644 Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift rename Nextcloud Cookbook iOS Client/Views/Recipes/{CategoryDetailView.swift => RecipeListView.swift} (95%) create mode 100644 Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift create mode 100644 Nextcloud Cookbook iOS Client/Views/Recipes/ShareView.swift create mode 100644 Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift create mode 100644 Nextcloud-Cookbook-iOS-Client-Info.plist diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj index 48d3d9b..c714523 100644 --- a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj +++ b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj @@ -15,11 +15,10 @@ A701719E2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701719D2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift */; }; A70171A02AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701719F2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift */; }; A70171AD2AA8EF4700064C43 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171AC2AA8EF4700064C43 /* AppState.swift */; }; - A70171AF2AB2116B00064C43 /* NetworkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171AE2AB2116B00064C43 /* NetworkHandler.swift */; }; - A70171B12AB211DF00064C43 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B02AB211DF00064C43 /* CustomError.swift */; }; - A70171B42AB2122900064C43 /* NetworkRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B32AB2122900064C43 /* NetworkRequests.swift */; }; - A70171BE2AB4987900064C43 /* CategoryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171BD2AB4987900064C43 /* CategoryDetailView.swift */; }; - A70171C02AB498A900064C43 /* RecipeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171BF2AB498A900064C43 /* RecipeDetailView.swift */; }; + A70171B12AB211DF00064C43 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B02AB211DF00064C43 /* NetworkError.swift */; }; + A70171B42AB2122900064C43 /* NetworkUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171B32AB2122900064C43 /* NetworkUtils.swift */; }; + A70171BE2AB4987900064C43 /* RecipeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171BD2AB4987900064C43 /* RecipeListView.swift */; }; + A70171C02AB498A900064C43 /* RecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171BF2AB498A900064C43 /* RecipeView.swift */; }; A70171C22AB498C600064C43 /* RecipeCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C12AB498C600064C43 /* RecipeCardView.swift */; }; A70171C42AB4A31200064C43 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C32AB4A31200064C43 /* DataStore.swift */; }; A70171C62AB4C43A00064C43 /* DataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C52AB4C43A00064C43 /* DataModels.swift */; }; @@ -47,9 +46,12 @@ A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D792B25C66600A3469E /* OnboardingView.swift */; }; A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */; }; A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */; }; + A95364672B7E89F1001018B0 /* ReorderableForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95364662B7E89F1001018B0 /* ReorderableForEach.swift */; }; A977D0DE2B600300009783A9 /* SearchTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977D0DD2B600300009783A9 /* SearchTabView.swift */; }; A977D0E02B600318009783A9 /* RecipeTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977D0DF2B600318009783A9 /* RecipeTabView.swift */; }; A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977D0E12B60034E009783A9 /* GroceryListTabView.swift */; }; + A97B4D322B80B3E900EC1A88 /* RecipeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */; }; + A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D342B80B82A00EC1A88 /* ShareView.swift */; }; A99DC7BC2B6411A7000118AA /* SimilaritySearchKit in Frameworks */ = {isa = PBXBuildFile; productRef = A99DC7BB2B6411A7000118AA /* SimilaritySearchKit */; }; A9CA6CEF2B4C086100F78AB5 /* RecipeExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */; }; A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */ = {isa = PBXBuildFile; productRef = A9CA6CF52B4C63F200F78AB5 /* TPPDF */; }; @@ -87,11 +89,10 @@ A701719D2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nextcloud_Cookbook_iOS_ClientUITests.swift; sourceTree = ""; }; A701719F2AA8E72000064C43 /* Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nextcloud_Cookbook_iOS_ClientUITestsLaunchTests.swift; sourceTree = ""; }; A70171AC2AA8EF4700064C43 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; - A70171AE2AB2116B00064C43 /* NetworkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkHandler.swift; sourceTree = ""; }; - A70171B02AB211DF00064C43 /* CustomError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; - A70171B32AB2122900064C43 /* NetworkRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequests.swift; sourceTree = ""; }; - A70171BD2AB4987900064C43 /* CategoryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailView.swift; sourceTree = ""; }; - A70171BF2AB498A900064C43 /* RecipeDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailView.swift; sourceTree = ""; }; + A70171B02AB211DF00064C43 /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; + A70171B32AB2122900064C43 /* NetworkUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkUtils.swift; sourceTree = ""; }; + A70171BD2AB4987900064C43 /* RecipeListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeListView.swift; sourceTree = ""; }; + A70171BF2AB498A900064C43 /* RecipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeView.swift; sourceTree = ""; }; A70171C12AB498C600064C43 /* RecipeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeCardView.swift; sourceTree = ""; }; A70171C32AB4A31200064C43 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; A70171C52AB4C43A00064C43 /* DataModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModels.swift; sourceTree = ""; }; @@ -118,11 +119,15 @@ A7FB0D792B25C66600A3469E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenLoginView.swift; sourceTree = ""; }; A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2LoginView.swift; sourceTree = ""; }; + A95364662B7E89F1001018B0 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = ""; }; A977D0DD2B600300009783A9 /* SearchTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTabView.swift; sourceTree = ""; }; A977D0DF2B600318009783A9 /* RecipeTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeTabView.swift; sourceTree = ""; }; A977D0E12B60034E009783A9 /* GroceryListTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryListTabView.swift; sourceTree = ""; }; + A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeModels.swift; sourceTree = ""; }; + A97B4D342B80B82A00EC1A88 /* ShareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareView.swift; sourceTree = ""; }; A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeExporter.swift; sourceTree = ""; }; A9D89AAF2B4FE97800F49D92 /* TimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerView.swift; sourceTree = ""; }; + A9DA25D42B82096B0061FC2B /* Nextcloud-Cookbook-iOS-Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Nextcloud-Cookbook-iOS-Client-Info.plist"; sourceTree = SOURCE_ROOT; }; A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = alarm_sound_0.mp3; sourceTree = ""; }; /* End PBXFileReference section */ @@ -179,16 +184,17 @@ A70171802AA8E71900064C43 /* Nextcloud Cookbook iOS Client */ = { isa = PBXGroup; children = ( + A9DA25D42B82096B0061FC2B /* Nextcloud-Cookbook-iOS-Client-Info.plist */, A70171812AA8E71900064C43 /* Nextcloud_Cookbook_iOS_ClientApp.swift */, + A70171AC2AA8EF4700064C43 /* AppState.swift */, A70171C72AB4C4A100064C43 /* Data */, A70171BA2AB4980100064C43 /* Views */, A70171B72AB2445700064C43 /* Models */, + A97B4D332B80B51700EC1A88 /* Util */, A70171B22AB211F000064C43 /* Network */, A781E75F2AF8228100452F6F /* RecipeImport */, A9CA6CED2B4C084100F78AB5 /* RecipeExport */, A703226B2ABAF60D00D7C4ED /* Extensions */, - A76B8A702AE002AE00096CEC /* Alerts.swift */, - A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */, A7AEAE632AD5521400135378 /* Localizable.xcstrings */, A70171852AA8E71F00064C43 /* Assets.xcassets */, A70171872AA8E71F00064C43 /* Nextcloud_Cookbook_iOS_Client.entitlements */, @@ -226,11 +232,10 @@ isa = PBXGroup; children = ( A79AA8EA2B062E15007D25F2 /* ApiRequest.swift */, - A79AA8EE2B063B33007D25F2 /* NextcloudApi */, A79AA8E72B062DB6007D25F2 /* CookbookApi */, - A70171B32AB2122900064C43 /* NetworkRequests.swift */, - A70171AE2AB2116B00064C43 /* NetworkHandler.swift */, - A70171B02AB211DF00064C43 /* CustomError.swift */, + A79AA8EE2B063B33007D25F2 /* NextcloudApi */, + A70171B32AB2122900064C43 /* NetworkUtils.swift */, + A70171B02AB211DF00064C43 /* NetworkError.swift */, ); path = Network; sourceTree = ""; @@ -238,7 +243,6 @@ A70171B72AB2445700064C43 /* Models */ = { isa = PBXGroup; children = ( - A70171AC2AA8EF4700064C43 /* AppState.swift */, A79AA8E12AFF8C14007D25F2 /* RecipeEditViewModel.swift */, ); path = Models; @@ -262,9 +266,9 @@ isa = PBXGroup; children = ( A70171C32AB4A31200064C43 /* DataStore.swift */, - A70171C52AB4C43A00064C43 /* DataModels.swift */, A70171CA2AB4CD1700064C43 /* UserSettings.swift */, - A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */, + A70171C52AB4C43A00064C43 /* DataModels.swift */, + A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */, ); path = Data; sourceTree = ""; @@ -332,13 +336,24 @@ path = Tabs; sourceTree = ""; }; + A97B4D332B80B51700EC1A88 /* Util */ = { + isa = PBXGroup; + children = ( + A76B8A702AE002AE00096CEC /* Alerts.swift */, + A79AA8DF2AFF80E3007D25F2 /* DurationComponents.swift */, + A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */, + ); + path = Util; + sourceTree = ""; + }; A9C3BE502B630E3900562C79 /* Recipes */ = { isa = PBXGroup; children = ( - A70171BD2AB4987900064C43 /* CategoryDetailView.swift */, + A70171BD2AB4987900064C43 /* RecipeListView.swift */, A70171C12AB498C600064C43 /* RecipeCardView.swift */, - A70171BF2AB498A900064C43 /* RecipeDetailView.swift */, + A70171BF2AB498A900064C43 /* RecipeView.swift */, A9D89AAF2B4FE97800F49D92 /* TimerView.swift */, + A97B4D342B80B82A00EC1A88 /* ShareView.swift */, ); path = Recipes; sourceTree = ""; @@ -357,6 +372,7 @@ isa = PBXGroup; children = ( A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */, + A95364662B7E89F1001018B0 /* ReorderableForEach.swift */, ); path = ReusableViews; sourceTree = ""; @@ -522,28 +538,30 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */, A9D89AB02B4FE97800F49D92 /* TimerView.swift in Sources */, A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */, A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */, A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */, A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */, - A70171B12AB211DF00064C43 /* CustomError.swift in Sources */, + A70171B12AB211DF00064C43 /* NetworkError.swift in Sources */, A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */, A76B8A712AE002AE00096CEC /* Alerts.swift in Sources */, A79AA8E62B02C3CB007D25F2 /* LoggerExtension.swift in Sources */, A70171C42AB4A31200064C43 /* DataStore.swift in Sources */, A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */, + A95364672B7E89F1001018B0 /* ReorderableForEach.swift in Sources */, A787B0782B2B1E6400C2DF1B /* DateExtension.swift in Sources */, A79AA8E92B062DD1007D25F2 /* CookbookApiV1.swift in Sources */, - A70171AF2AB2116B00064C43 /* NetworkHandler.swift in Sources */, A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */, - A70171B42AB2122900064C43 /* NetworkRequests.swift in Sources */, - A70171BE2AB4987900064C43 /* CategoryDetailView.swift in Sources */, + A70171B42AB2122900064C43 /* NetworkUtils.swift in Sources */, + A97B4D322B80B3E900EC1A88 /* RecipeModels.swift in Sources */, + A70171BE2AB4987900064C43 /* RecipeListView.swift in Sources */, A70171C62AB4C43A00064C43 /* DataModels.swift in Sources */, A79AA8EB2B062E15007D25F2 /* ApiRequest.swift in Sources */, A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */, A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */, - A70171C02AB498A900064C43 /* RecipeDetailView.swift in Sources */, + A70171C02AB498A900064C43 /* RecipeView.swift in Sources */, A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */, A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */, A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */, @@ -726,6 +744,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Cookbook; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -742,7 +761,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.8.2; + MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -769,6 +788,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Cookbook; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -785,7 +805,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.8.2; + MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; 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 689b43238f4fa696278b4e4be1c2b223c1958285..1a36d6750376575e7fce6e6ef03e2b273a3807e6 100644 GIT binary patch literal 117266 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

Gp9J8riNogKG3g}zJZyM?}I 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�Y`{irR+NQ&e_;*OLS2xPh3oJf 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 lmcH~Skz{l+HI2g [String] { - if keywords == "" { return [] } - return keywords.components(separatedBy: ",") - } - - mutating func setKeywordsFromArray(_ keywordsArray: [String]) { - if !keywordsArray.isEmpty { - self.keywords = keywordsArray.joined(separator: ",") - } - } - - func getNutritionList() -> [String]? { - var stringList: [String] = [] - if let value = nutrition["calories"] { stringList.append("Calories: \(value)") } - if let value = nutrition["carbohydrateContent"] { stringList.append("Carbohydrates: \(value)") } - if let value = nutrition["cholesterolContent"] { stringList.append("Cholesterol: \(value)") } - if let value = nutrition["fatContent"] { stringList.append("Fat: \(value)") } - if let value = nutrition["saturatedFatContent"] { stringList.append("Saturated fat: \(value)") } - if let value = nutrition["unsaturatedFatContent"] { stringList.append("Unsaturated fat: \(value)") } - if let value = nutrition["transFatContent"] { stringList.append("Trans fat: \(value)") } - if let value = nutrition["fiberContent"] { stringList.append("Fibers: \(value)") } - if let value = nutrition["proteinContent"] { stringList.append("Protein: \(value)") } - if let value = nutrition["sodiumContent"] { stringList.append("Sodium: \(value)") } - if let value = nutrition["sugarContent"] { stringList.append("Sugar: \(value)") } - return stringList.isEmpty ? nil : stringList - } -} - - - -struct RecipeImage { - enum RecipeImageSize: String { - case THUMB="thumb", FULL="full" - } - var imageExists: Bool = true - var thumb: UIImage? - var full: UIImage? -} - - - -struct RecipeKeyword: Codable { - let name: String - let recipe_count: Int -} - -struct RecipeImportRequest: Codable { - let url: String -} -// Login flow + +// MARK: - Login flow struct LoginV2Request: Codable { let poll: LoginV2Poll diff --git a/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift b/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift new file mode 100644 index 0000000..4d4e68b --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift @@ -0,0 +1,214 @@ +// +// RecipeModels.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 17.02.24. +// + +import Foundation +import SwiftUI + + +struct Recipe: Codable { + let name: String + let keywords: String? + let dateCreated: String + let dateModified: String + let imageUrl: String + let imagePlaceholderUrl: String + let recipe_id: Int + + // Properties excluded from Codable + var storedLocally: Bool? = nil + + private enum CodingKeys: String, CodingKey { + case name, keywords, dateCreated, dateModified, imageUrl, imagePlaceholderUrl, recipe_id + } +} + + +extension Recipe: Identifiable, Hashable { + var id: String { name } +} + + +struct RecipeDetail: Codable { + var name: String + var keywords: String + var dateCreated: String + var dateModified: String + var imageUrl: String + var id: String + var prepTime: String? + var cookTime: String? + var totalTime: String? + var description: String + var url: String + var recipeYield: Int + var recipeCategory: String + var tool: [String] + var recipeIngredient: [String] + var recipeInstructions: [String] + var nutrition: [String:String] + + init(name: String, keywords: String, dateCreated: String, dateModified: String, imageUrl: String, id: String, prepTime: String? = nil, cookTime: String? = nil, totalTime: String? = nil, description: String, url: String, recipeYield: Int, recipeCategory: String, tool: [String], recipeIngredient: [String], recipeInstructions: [String], nutrition: [String:String]) { + self.name = name + self.keywords = keywords + self.dateCreated = dateCreated + self.dateModified = dateModified + self.imageUrl = imageUrl + self.id = id + self.prepTime = prepTime + self.cookTime = cookTime + self.totalTime = totalTime + self.description = description + self.url = url + self.recipeYield = recipeYield + self.recipeCategory = recipeCategory + self.tool = tool + self.recipeIngredient = recipeIngredient + self.recipeInstructions = recipeInstructions + self.nutrition = nutrition + } + + init() { + name = "" + keywords = "" + dateCreated = "" + dateModified = "" + imageUrl = "" + id = "" + prepTime = "" + cookTime = "" + totalTime = "" + description = "" + url = "" + recipeYield = 0 + recipeCategory = "" + tool = [] + recipeIngredient = [] + recipeInstructions = [] + nutrition = [:] + } +} + + +extension RecipeDetail { + static var error: RecipeDetail { + return RecipeDetail( + name: "Error: Unable to load recipe.", + keywords: "", + dateCreated: "", + dateModified: "", + imageUrl: "", + id: "", + prepTime: "", + cookTime: "", + totalTime: "", + description: "", + url: "", + recipeYield: 0, + recipeCategory: "", + tool: [], + recipeIngredient: [], + recipeInstructions: [], + nutrition: [:] + ) + } + + func getKeywordsArray() -> [String] { + if keywords == "" { return [] } + return keywords.components(separatedBy: ",") + } + + mutating func setKeywordsFromArray(_ keywordsArray: [String]) { + if !keywordsArray.isEmpty { + self.keywords = keywordsArray.joined(separator: ",") + } + } +} + + +struct RecipeImage { + enum RecipeImageSize: String { + case THUMB="thumb", FULL="full" + } + var imageExists: Bool = true + var thumb: UIImage? + var full: UIImage? +} + + +struct RecipeKeyword: Codable { + let name: String + let recipe_count: Int +} + + +enum Nutrition: CaseIterable { + case calories, + carbohydrateContent, + cholesterolContent, + fatContent, + saturatedFatContent, + unsaturatedFatContent, + transFatContent, + fiberContent, + proteinContent, + sodiumContent, + sugarContent + + var localizedDescription: LocalizedStringKey { + switch self { + case .calories: + "Calories" + case .carbohydrateContent: + "Carbohydrate content" + case .cholesterolContent: + "Cholesterol content" + case .fatContent: + "Fat content" + case .saturatedFatContent: + "Saturated fat content" + case .unsaturatedFatContent: + "Unsaturated fat content" + case .transFatContent: + "Trans fat content" + case .fiberContent: + "Fiber content" + case .proteinContent: + "Protein content" + case .sodiumContent: + "Sodium content" + case .sugarContent: + "Sugar content" + } + } + + var dictKey: String { + switch self { + case .calories: + "calories" + case .carbohydrateContent: + "carbohydrateContent" + case .cholesterolContent: + "cholesterolContent" + case .fatContent: + "fatContent" + case .saturatedFatContent: + "saturatedFatContent" + case .unsaturatedFatContent: + "unsaturatedFatContent" + case .transFatContent: + "transFatContent" + case .fiberContent: + "fiberContent" + case .proteinContent: + "proteinContent" + case .sodiumContent: + "sodiumContent" + case .sugarContent: + "sugarContent" + } + } +} diff --git a/Nextcloud Cookbook iOS Client/Localizable.xcstrings b/Nextcloud Cookbook iOS Client/Localizable.xcstrings index 4ff1deb..6400302 100644 --- a/Nextcloud Cookbook iOS Client/Localizable.xcstrings +++ b/Nextcloud Cookbook iOS Client/Localizable.xcstrings @@ -408,7 +408,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fügen Sie dieser Liste Rezeptzutaten hinzu, indem Sie entweder den Button neben einer Zutatenliste in einem Rezept verwenden, um alle Zutaten hinzuzufügen, oder indem Sie einzelne Zutaten eines Rezepts nach rechts wischen." + "value" : "Wenn du alle Zutaten eines Rezepts auf einmal hinzufügen möchtest, klicke einfach auf den „Einkaufsliste“-Button, den du neben der Zutatenliste des Rezepts findest. Möchtest du nur einzelne Zutaten hinzufügen, wische die gewünschte Zutat in der Liste des Rezepts einfach nach rechts." } }, "es" : { @@ -492,6 +492,7 @@ } }, "An unknown server error occured." : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -556,6 +557,9 @@ } } } + }, + "Calories" : { + }, "Cancel" : { "localizations" : { @@ -578,6 +582,9 @@ } } } + }, + "Carbohydrate content" : { + }, "Category" : { "localizations" : { @@ -622,13 +629,16 @@ } } } + }, + "Cholesterol content" : { + }, "Configure what is stored on your device." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Legen Sie fest, was lokal auf diesem Gerät gespeichert werden soll." + "value" : "Legt fest, was lokal auf diesem Gerät gespeichert werden soll." } }, "es" : { @@ -650,7 +660,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Legen Sie fest, welche Rezept-Abschnitte standardmäßig gezeigt werden." + "value" : "Legt fest, welche Rezept-Abschnitte standardmäßig gezeigt werden." } }, "es" : { @@ -980,7 +990,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das Löschen lokaler Daten hat keine Auswirkungen auf die Rezeptdaten, die auf Ihrem Server gespeichert sind." + "value" : "Das Löschen lokaler Daten hat keine Auswirkungen auf die Rezeptdaten, die auf dem Server gespeichert sind." } }, "es" : { @@ -1019,8 +1029,18 @@ } } }, - "Done" : { + "Disable deletion" : { + }, + "Done" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fertig" + } + } + } }, "Downloads" : { "localizations" : { @@ -1045,6 +1065,7 @@ } }, "Duplicate Recipe" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1131,6 +1152,9 @@ } } } + }, + "Enable deletion" : { + }, "Error" : { "localizations" : { @@ -1241,6 +1265,12 @@ } } } + }, + "Fat content" : { + + }, + "Fiber content" : { + }, "General" : { "localizations" : { @@ -1313,7 +1343,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn \"Systemsprache\" ausgewählt ist und Ihre Systemsprache noch nicht unterstützt wird, wird standardmäßig Englisch verwendet." + "value" : "Wenn \"Systemsprache\" ausgewählt ist und die Systemsprache noch nicht unterstützt wird, wird standardmäßig Englisch verwendet." } }, "es" : { @@ -1335,7 +1365,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn der Anmeldebutton Ihren Browser nicht öffnet, verwenden Sie den 'Link kopieren' Button und fügen Sie den Link manuell in Ihren Browser ein." + "value" : "Falls sich der Browser beim Klicken auf den Anmeldebutton nicht öffnet, klicke einfach auf den Button „Link kopieren“. Anschließend kann der kopierte Link manuell in den Browser eingefügt werden." } }, "es" : { @@ -1357,7 +1387,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn Sie Interesse daran haben, zu diesem Projekt beizutragen oder einfach den Quellcode überprüfen möchten, ermutigen wir Sie, das GitHub-Repository für diese Anwendung zu besuchen." + "value" : "Möchtest du einen Beitrag zu diesem Projekt leisten oder einfach nur einen Blick in den Quellcode werfen? Wir freuen uns über jedes Interesse und laden dich ein, das GitHub-Repository unserer Anwendung zu besuchen." } }, "es" : { @@ -1379,7 +1409,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn Sie Anfragen oder Rückmeldungen haben, oder Unterstützung benötigen, finden Sie unter diesem Link die Kontaktinformationen." + "value" : "Bei Fragen, Feedback oder benötigter Unterstützung finden sich alle Kontaktinformationen unter diesem Link." } }, "es" : { @@ -1397,6 +1427,7 @@ } }, "Image MIME Error" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1489,6 +1520,9 @@ } } } + }, + "Ingredient" : { + }, "Ingredients" : { "localizations" : { @@ -1555,6 +1589,9 @@ } } } + }, + "Instruction" : { + }, "Instructions" : { "localizations" : { @@ -1599,6 +1636,9 @@ } } } + }, + "Keyword" : { + }, "Keywords" : { "localizations" : { @@ -1781,7 +1821,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Stellen Sie sicher, dass Sie die Serveradresse in der Form 'beispiel.com' eingeben, oder ':', wenn ein nicht standardmäßiger Port verwendet wird." + "value" : "Stelle sicher, dass die Serveradresse im Format 'beispiel.com' eingegeben wurde oder als ':', falls ein nicht standardmäßiger Port genutzt wird." } }, "es" : { @@ -1799,6 +1839,7 @@ } }, "Missing Name" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1843,6 +1884,7 @@ } }, "Missing Request Body" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2155,7 +2197,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fügen Sie hier den Link einer Website ein, von der Sie ein Rezept importieren möchten. Dies funktioniert nicht mit allen Seiten. Falls eine Seite nicht unterstützt wird, kontaktieren Sie uns gerne um uns darauf Aufmerksam zu machen." + "value" : "Den Link zum Importieren eines Rezepts hier einfügen. Dies funktioniert nicht mit allen Webseiten. Sollte eine Seite nicht unterstützt werden, kontaktiere uns gerne um uns darauf aufmerksam zu machen. Jedes Feedback hilft dabei, den Service zu verbessern." } }, "es" : { @@ -2199,7 +2241,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte überprüfen Sie die eingegebenen Link." + "value" : "Bitte überprüfe den eingegebenen Link." } }, "es" : { @@ -2221,7 +2263,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte überprüfen Sie Ihre Anmeldedaten oder Ihre Internetverbindung." + "value" : "Bitte überprüfe die Anmeldedaten oder die Internetverbindung." } }, "es" : { @@ -2243,7 +2285,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte tragen Sie einen Rezeptnamen ein." + "value" : "Bitte einen Rezeptnamen eintragen." } }, "es" : { @@ -2303,6 +2345,9 @@ } } } + }, + "Protein content" : { + }, "Recipe" : { "localizations" : { @@ -2391,6 +2436,9 @@ } } } + }, + "Saturated fat content" : { + }, "Search" : { "localizations" : { @@ -2463,7 +2511,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wählen Sie ein Standard-Kochbuch" + "value" : "Standard-Kochbuch" } }, "es" : { @@ -2633,6 +2681,9 @@ } } } + }, + "Sodium content" : { + }, "Store recipe images locally" : { "localizations" : { @@ -2699,6 +2750,9 @@ } } } + }, + "Sugar content" : { + }, "Support" : { "localizations" : { @@ -2771,7 +2825,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der 'Anmelden'-Button wird einen Webbrowser öffnen. Bitte folgen Sie den dort angegebenen Anmeldeanweisungen.\nNach einer erfolgreichen Anmeldung kehren Sie zu dieser Anwendung zurück und drücken Sie 'Validieren'." + "value" : "Durch Klicken auf den 'Anmelden'-Button wird ein Webbrowser geöffnet. Bitte den dort angegebenen Anmeldeanweisungen folgen. Nach erfolgreicher Anmeldung zur Anwendung zurückkehren und 'Validieren' drücken." } }, "es" : { @@ -2789,6 +2843,7 @@ } }, "The recipe has no image whose MIME type matches the Accept header" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2833,6 +2888,7 @@ } }, "There was no name in the request given for the recipe. Cannot save the recipe." : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2881,7 +2937,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Anwendung ist ein Open-Source-Projekt. Wenn Sie daran interessiert sind, neue Funktionen vorzuschlagen oder beizutragen, oder wenn Sie auf Probleme stoßen, nutzen Sie bitte den Kontakt-Link oder besuchen Sie das GitHub-Repository in den App-Einstellungen." + "value" : "Diese Anwendung ist ein Open-Source-Projekt. Bei bestehendem Interesse neue Funktionen vorzuschlagen oder beizutragen, oder wenn Probleme auftreten, nutze den Kontakt-Link oder besuche das GitHub-Repository in den App-Einstellungen." } }, "es" : { @@ -2903,7 +2959,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Website wird möglicherweise noch nicht unterstützt. Falls Sie das ändern möchten, können Sie uns gerne darauf aufmerksam machen." + "value" : "Diese Website wird möglicherweise derzeit nicht unterstützt. Wenn sich das ändern soll, kann die Support-Option in den App-Einstellungen genutzt werden, um auf dieses Problem aufmerksam zu machen." } }, "es" : { @@ -2941,6 +2997,9 @@ } } } + }, + "Tool" : { + }, "Tools" : { "localizations" : { @@ -3029,6 +3088,9 @@ } } } + }, + "Trans fat content" : { + }, "Unable to complete action." : { "localizations" : { @@ -3079,7 +3141,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der Inhalt der Website konnte nicht geladen werden. Bitte überprüfen Sie Ihre Internetverbindung." + "value" : "Der Inhalt der Website konnte nicht geladen werden. Bitte überprüfe die Internetverbindung." } }, "es" : { @@ -3101,7 +3163,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Es ist nicht möglich, Ihr Rezept hochzuladen. Bitte überprüfen Sie Ihre Internetverbindung." + "value" : "Es ist nicht möglich, das Rezept hochzuladen. Bitte überprüfe die Internetverbindung." } }, "es" : { @@ -3117,6 +3179,9 @@ } } } + }, + "Unsaturated fat content" : { + }, "Upload" : { "localizations" : { @@ -3185,7 +3250,14 @@ } }, "Username: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzername: %@" + } + } + } }, "Validate" : { "localizations" : { @@ -3236,7 +3308,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sie sind bereit zum Kochen 🍓" + "value" : "Bereit zum Kochen 🍓" } }, "es" : { @@ -3258,7 +3330,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ihre Einkaufsliste wird lokal gespeichert und daher nicht auf andere Geräte übertragen." + "value" : "Die Einkaufsliste wird lokal gespeichert und daher nicht auf andere Geräte übertragen." } }, "es" : { diff --git a/Nextcloud Cookbook iOS Client/Network/ApiRequest.swift b/Nextcloud Cookbook iOS Client/Network/ApiRequest.swift index fd95965..4971767 100644 --- a/Nextcloud Cookbook iOS Client/Network/ApiRequest.swift +++ b/Nextcloud Cookbook iOS Client/Network/ApiRequest.swift @@ -14,9 +14,7 @@ struct ApiRequest { let authString: String? let headerFields: [HeaderField] let body: Data? - - /// The path to the Cookbook application on the nextcloud server. - + init( path: String, method: RequestMethod, diff --git a/Nextcloud Cookbook iOS Client/Network/CustomError.swift b/Nextcloud Cookbook iOS Client/Network/CustomError.swift deleted file mode 100644 index c823741..0000000 --- a/Nextcloud Cookbook iOS Client/Network/CustomError.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// CustomError.swift -// Nextcloud Cookbook iOS Client -// -// Created by Vincent Meilinger on 13.09.23. -// - -import Foundation -import SwiftUI - -public enum NotImplementedError: Error, CustomStringConvertible { - case notImplemented - public var description: String { - return "Function not implemented." - } -} - -public enum NetworkError: String, Error { - case missingUrl = "Missing URL." - case parametersNil = "Parameters are nil." - case encodingFailed = "Parameter encoding failed." - case decodingFailed = "Data decoding failed." - case redirectionError = "Redirection error" - case clientError = "Client error" - case serverError = "Server error" - case invalidRequest = "Invalid request" - case unknownError = "Unknown error" - case dataError = "Invalid data error." -} - -public enum ServerError: Error { - case unknownError, missingRequestBody, duplicateRecipe, noImage, missingRecipeName, recipeNotFound, deleteFailed, requestUnsuccessful - - - static func decodeFromURLResponse(response: HTTPURLResponse?) -> ServerError? { - guard let response = response else { - return ServerError.unknownError - } - print("Status code: ", response.statusCode) - switch response.statusCode { - case 200...299: return nil - case 400: return .missingRequestBody - case 404: return .recipeNotFound - case 409: return .duplicateRecipe - case 406: return .noImage - case 422: return .missingRecipeName - case 500: return .requestUnsuccessful - case 502: return .deleteFailed - default: return ServerError.unknownError - } - } - - var localizedDescription: LocalizedStringKey { - switch self { - case .noImage: return "The recipe has no image whose MIME type matches the Accept header" - case .missingRecipeName: return "There was no name in the request given for the recipe. Cannot save the recipe." - default: return "An unknown server error occured." - } - } - - var localizedTitle: LocalizedStringKey { - switch self { - case .missingRequestBody: return "Missing Request Body" - case .duplicateRecipe: return "Duplicate Recipe" - case .noImage: return "Image MIME Error" - case .missingRecipeName: return "Missing Name" - default: return "Error" - } - } -} diff --git a/Nextcloud Cookbook iOS Client/Network/NetworkError.swift b/Nextcloud Cookbook iOS Client/Network/NetworkError.swift new file mode 100644 index 0000000..cf6b6e1 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Network/NetworkError.swift @@ -0,0 +1,23 @@ +// +// CustomError.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 13.09.23. +// + +import Foundation + +public enum NetworkError: String, Error { + case missingUrl = "Missing URL." + case parametersNil = "Parameters are nil." + case encodingFailed = "Parameter encoding failed." + case decodingFailed = "Data decoding failed." + case redirectionError = "Redirection error" + case clientError = "Client error" + case serverError = "Server error" + case invalidRequest = "Invalid request" + case unknownError = "Unknown error" + case dataError = "Invalid data error." +} + + diff --git a/Nextcloud Cookbook iOS Client/Network/NetworkHandler.swift b/Nextcloud Cookbook iOS Client/Network/NetworkHandler.swift deleted file mode 100644 index 67cd3a9..0000000 --- a/Nextcloud Cookbook iOS Client/Network/NetworkHandler.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// NetworkHandler.swift -// Nextcloud Cookbook iOS Client -// -// Created by Vincent Meilinger on 13.09.23. -// - -import Foundation - - - -struct NetworkHandler { - static func sendHTTPRequest( - _ requestWrapper: RequestWrapper, - hostPath: String, - authString: String? - ) async throws -> (Data?, NetworkError?) { - print("Sending \(requestWrapper.getMethod()) request (path: \(requestWrapper.getPath())) ...") - - // Prepare URL - let urlString = hostPath + requestWrapper.getPath() - let urlStringSanitized = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) - let url = URL(string: urlStringSanitized!)! - - // Create URL request - var request = URLRequest(url: url) - - // Set URL method - request.httpMethod = requestWrapper.getMethod() - - // Set authentication string, if needed - if let authString = authString { - request.setValue( - "Basic \(authString)", - forHTTPHeaderField: "Authorization" - ) - } - - // Set other header fields - for headerField in requestWrapper.getHeaderFields() { - request.setValue( - headerField.getValue(), - forHTTPHeaderField: headerField.getField() - ) - } - - // Set http body - if let body = requestWrapper.getBody() { - request.httpBody = body - } - - print("Request:\nMethod: \(request.httpMethod)\nPath: \(request.url?.absoluteString)\nHeaders: \(request.allHTTPHeaderFields)\nBody: \(request.httpBody)") - - // Wait for and return data and (decoded) response - var data: Data? = nil - var response: URLResponse? = nil - do { - (data, response) = try await URLSession.shared.data(for: request) - print("Response: ", response) - print("Data: ", data?.description, data, String(data: data ?? Data(), encoding: .utf8)) - return (data, nil) - } catch { - return (nil, decodeURLResponse(response: response as? HTTPURLResponse)) - } - } - - private static func decodeURLResponse(response: HTTPURLResponse?) -> NetworkError? { - guard let response = response else { - return NetworkError.unknownError - } - 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) - default: return (NetworkError.unknownError) - } - } -} diff --git a/Nextcloud Cookbook iOS Client/Network/NetworkRequests.swift b/Nextcloud Cookbook iOS Client/Network/NetworkRequests.swift deleted file mode 100644 index f782a7f..0000000 --- a/Nextcloud Cookbook iOS Client/Network/NetworkRequests.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// NetworkRequests.swift -// Nextcloud Cookbook iOS Client -// -// Created by Vincent Meilinger on 13.09.23. -// - -import Foundation - -enum RequestMethod: String { - case GET = "GET", - POST = "POST", - PUT = "PUT", - DELETE = "DELETE" -} - - - -enum ContentType: String { - case JSON = "application/json", - IMAGE = "image/jpeg", - FORM = "application/x-www-form-urlencoded" -} - -struct HeaderField { - private let _field: String - private let _value: String - - func getField() -> String { - return _field - } - - func getValue() -> String { - return _value - } - - static func accept(value: ContentType) -> HeaderField { - return HeaderField(_field: "accept", _value: value.rawValue) - } - - static func ocsRequest(value: Bool) -> HeaderField { - return HeaderField(_field: "OCS-APIRequest", _value: value ? "true" : "false") - } - - static func contentType(value: ContentType) -> HeaderField { - return HeaderField(_field: "Content-Type", _value: value.rawValue) - } -} - - -enum RequestPath { - case CATEGORIES, - RECIPE_LIST(categoryName: String), - RECIPE_DETAIL(recipeId: Int), - NEW_RECIPE, - IMAGE(recipeId: Int, thumb: Bool), - CONFIG, - KEYWORDS - - case LOGINV2REQ, - CUSTOM(path: String), - NONE - - var stringValue: String { - switch self { - case .CATEGORIES: return "categories" - case .RECIPE_LIST(categoryName: let name): return "category/\(name)" - case .RECIPE_DETAIL(recipeId: let recipeId): return "recipes/\(recipeId)" - case .IMAGE(recipeId: let recipeId, thumb: let thumb): return "recipes/\(recipeId)/image?size=\(thumb ? "thumb" : "full")" - case .NEW_RECIPE: return "recipes" - case .CONFIG: return "config" - case .KEYWORDS: return "keywords" - - case .LOGINV2REQ: return "/index.php/login/v2" - case .CUSTOM(path: let path): return path - case .NONE: return "" - } - } -} - -struct RequestWrapper { - private let _method: RequestMethod - private let _path: RequestPath - private let _headerFields: [HeaderField] - private let _body: Data? - private let _authenticate: Bool = true - - private init( - method: RequestMethod, - path: RequestPath, - headerFields: [HeaderField] = [], - body: Data? = nil, - authenticate: Bool = true - ) { - self._method = method - self._path = path - self._headerFields = headerFields - self._body = body - } - - func getMethod() -> String { - return self._method.rawValue - } - - func getPath() -> String { - return self._path.stringValue - } - - func getHeaderFields() -> [HeaderField] { - return self._headerFields - } - - func getBody() -> Data? { - return _body - } - - func needsAuth() -> Bool { - return _authenticate - } -} - -extension RequestWrapper { - static func customRequest( - method: RequestMethod, - path: RequestPath, - headerFields: [HeaderField] = [], - body: Data? = nil, - authenticate: Bool = true - ) -> RequestWrapper { - let request = RequestWrapper( - method: method, - path: path, - headerFields: headerFields, - body: body, - authenticate: authenticate - ) - return request - } - - static func jsonGetRequest(path: RequestPath) -> RequestWrapper { - let headerFields = [ - HeaderField.ocsRequest(value: true), - HeaderField.accept(value: .JSON) - ] - let request = RequestWrapper( - method: .GET, - path: path, - headerFields: headerFields, - authenticate: true - ) - return request - } - - static func imageRequest(path: RequestPath) -> RequestWrapper { - let headerFields = [ - HeaderField.ocsRequest(value: true), - HeaderField.accept(value: .IMAGE) - ] - let request = RequestWrapper( - method: .GET, - path: path, - headerFields: headerFields, - authenticate: true - ) - return request - } -} - - diff --git a/Nextcloud Cookbook iOS Client/Network/NetworkUtils.swift b/Nextcloud Cookbook iOS Client/Network/NetworkUtils.swift new file mode 100644 index 0000000..4d0258f --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Network/NetworkUtils.swift @@ -0,0 +1,50 @@ +// +// NetworkRequests.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 13.09.23. +// + +import Foundation + +enum RequestMethod: String { + case GET = "GET", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE" +} + +enum ContentType: String { + case JSON = "application/json", + IMAGE = "image/jpeg", + FORM = "application/x-www-form-urlencoded" +} + +struct HeaderField { + private let _field: String + private let _value: String + + func getField() -> String { + return _field + } + + func getValue() -> String { + return _value + } + + static func accept(value: ContentType) -> HeaderField { + return HeaderField(_field: "accept", _value: value.rawValue) + } + + static func ocsRequest(value: Bool) -> HeaderField { + return HeaderField(_field: "OCS-APIRequest", _value: value ? "true" : "false") + } + + static func contentType(value: ContentType) -> HeaderField { + return HeaderField(_field: "Content-Type", _value: value.rawValue) + } +} + +struct RecipeImportRequest: Codable { + let url: String +} diff --git a/Nextcloud Cookbook iOS Client/Alerts.swift b/Nextcloud Cookbook iOS Client/Util/Alerts.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Alerts.swift rename to Nextcloud Cookbook iOS Client/Util/Alerts.swift diff --git a/Nextcloud Cookbook iOS Client/Data/DurationComponents.swift b/Nextcloud Cookbook iOS Client/Util/DurationComponents.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/Data/DurationComponents.swift rename to Nextcloud Cookbook iOS Client/Util/DurationComponents.swift diff --git a/Nextcloud Cookbook iOS Client/SupportedLanguage.swift b/Nextcloud Cookbook iOS Client/Util/SupportedLanguage.swift similarity index 100% rename from Nextcloud Cookbook iOS Client/SupportedLanguage.swift rename to Nextcloud Cookbook iOS Client/Util/SupportedLanguage.swift diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift deleted file mode 100644 index b99601f..0000000 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeDetailView.swift +++ /dev/null @@ -1,553 +0,0 @@ -// -// RecipeDetailView.swift -// Nextcloud Cookbook iOS Client -// -// Created by Vincent Meilinger on 15.09.23. -// - -import Foundation -import SwiftUI - - -struct RecipeDetailView: View { - @ObservedObject var viewModel: AppState - @State var recipe: Recipe - @State var recipeDetail: RecipeDetail? - @State var recipeImage: UIImage? - @State var showTitle: Bool = false - @State var isDownloaded: Bool? = nil - @State private var presentEditView: Bool = false - @State private var presentNutritionPopover: Bool = false - @State private var presentKeywordPopover: Bool = false - @State private var presentShareSheet: Bool = false - @State private var sharedURL: URL? = nil - - var body: some View { - ScrollView(showsIndicators: false) { - VStack(alignment: .leading) { - ZStack { - if let recipeImage = recipeImage { - Image(uiImage: recipeImage) - .resizable() - .scaledToFill() - .frame(maxHeight: 300) - .clipped() - } - }.animation(.easeInOut, value: recipeImage) - - if let recipeDetail = recipeDetail { - LazyVStack (alignment: .leading) { - HStack { - Text(recipeDetail.name) - .font(.title) - .bold() - .padding() - .onDisappear { - showTitle = true - } - .onAppear { - showTitle = false - } - - if let isDownloaded = isDownloaded { - Spacer() - Image(systemName: isDownloaded ? "checkmark.circle" : "icloud.and.arrow.down") - .foregroundColor(.secondary) - .padding() - } - } - - if recipeDetail.description != "" { - Text(recipeDetail.description) - .padding([.bottom, .horizontal]) - } - - Divider() - - RecipeDurationSection(viewModel: viewModel, recipeDetail: recipeDetail) - - LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { - if(!recipeDetail.recipeIngredient.isEmpty) { - RecipeIngredientSection(recipeDetail: recipeDetail) - } - if(!recipeDetail.recipeInstructions.isEmpty) { - RecipeInstructionSection(recipeDetail: recipeDetail) - } - if(!recipeDetail.tool.isEmpty) { - RecipeToolSection(recipeDetail: recipeDetail) - } - RecipeNutritionSection(recipeDetail: recipeDetail) - RecipeKeywordSection(recipeDetail: recipeDetail) - MoreInformationSection(recipeDetail: recipeDetail) - } - - }.padding(.horizontal, 5) - - } - } - } - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(showTitle ? recipe.name : "") - .toolbar { - if recipeDetail != nil { - Menu { - Button { - presentEditView = true - } label: { - HStack { - Text("Edit") - Image(systemName: "pencil") - } - } - - Button { - print("Sharing recipe ...") - self.presentShareSheet = true - } label: { - Text("Share recipe") - Image(systemName: "square.and.arrow.up") - } - } label: { - Image(systemName: "ellipsis.circle") - } - } - } - .sheet(isPresented: $presentEditView) { - if let recipeDetail = recipeDetail { - RecipeEditView( - viewModel: - RecipeEditViewModel( - mainViewModel: viewModel, - recipeDetail: recipeDetail, - uploadNew: false - ), - isPresented: $presentEditView - ) - } - } - .sheet(isPresented: $presentShareSheet) { - if let recipeDetail = recipeDetail { - ShareView(recipeDetail: recipeDetail, - recipeImage: recipeImage, - presentShareSheet: $presentShareSheet) - } - } - - .task { - recipeDetail = await viewModel.getRecipe( - id: recipe.recipe_id, - fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer - ) - recipeImage = await viewModel.getImage( - id: recipe.recipe_id, - size: .FULL, - fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer - ) - if recipe.storedLocally == nil { - recipe.storedLocally = viewModel.recipeDetailExists(recipeId: recipe.recipe_id) - } - self.isDownloaded = recipe.storedLocally - } - .refreshable { - recipeDetail = await viewModel.getRecipe( - id: recipe.recipe_id, - fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer - ) - recipeImage = await viewModel.getImage( - id: recipe.recipe_id, - size: .FULL, - fetchMode: UserSettings.shared.storeImages ? .preferServer : .onlyServer - ) - } - .onAppear { - if UserSettings.shared.keepScreenAwake { - UIApplication.shared.isIdleTimerDisabled = true - } - } - .onDisappear { - UIApplication.shared.isIdleTimerDisabled = false - } - } -} - -fileprivate struct ShareView: View { - @State var recipeDetail: RecipeDetail - @State var recipeImage: UIImage? - @Binding var presentShareSheet: Bool - - @State var exporter = RecipeExporter() - @State var sharedURL: URL? = nil - - var body: some View { - VStack(alignment: .leading) { - if let url = sharedURL { - ShareLink(item: url, subject: Text("PDF Document")) { - Image(systemName: "doc") - Text("Share as PDF") - } - .foregroundStyle(.primary) - .bold() - .padding() - } - - ShareLink(item: exporter.createText(recipe: recipeDetail), subject: Text("Recipe")) { - Image(systemName: "ellipsis.message") - Text("Share as text") - } - .foregroundStyle(.primary) - .bold() - .padding() - - /*ShareLink(item: exporter.createJson(recipe: recipeDetail), subject: Text("Recipe")) { - Image(systemName: "doc.badge.gearshape") - Text("Share as JSON") - } - .foregroundStyle(.primary) - .bold() - .padding() - */ - } - .task { - self.sharedURL = exporter.createPDF(recipe: recipeDetail, image: recipeImage) - } - - } -} - - -fileprivate struct RecipeDurationSection: View { - @ObservedObject var viewModel: AppState - @State var recipeDetail: RecipeDetail - - var body: some View { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 250), alignment: .leading)]) { - if let prepTime = recipeDetail.prepTime, let time = DurationComponents.ptToText(prepTime) { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Preparation")) - Spacer() - } - Text(time) - .lineLimit(1) - }.padding() - } - /* - if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) { - TimerView(timer: viewModel.getTimer(forRecipe: recipeDetail.id, duration: DurationComponents.fromPTString(cookTime))) - .padding() - } - */ - - if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Cooking")) - Spacer() - } - Text(time) - .lineLimit(1) - }.padding() - } - - if let totalTime = recipeDetail.totalTime, let time = DurationComponents.ptToText(totalTime) { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Total time")) - Spacer() - } - Text(time) - .lineLimit(1) - }.padding() - } - } - } -} - - - -fileprivate struct RecipeNutritionSection: View { - @State var recipeDetail: RecipeDetail - - var body: some View { - HStack() { - CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandNutritionSection) { - Group { - if let nutritionList = recipeDetail.getNutritionList() { - RecipeListSection(list: nutritionList) - } else { - Text(LocalizedStringKey("No nutritional information.")) - } - } - } title: { - HStack { - if let servingSize = recipeDetail.nutrition["servingSize"] { - SecondaryLabel(text: "Nutrition (\(servingSize))") - } else { - SecondaryLabel(text: LocalizedStringKey("Nutrition")) - } - Spacer() - } - } - .padding() - } - } -} - - - -fileprivate struct RecipeKeywordSection: View { - @State var recipeDetail: RecipeDetail - - var body: some View { - CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandKeywordSection) { - Group { - if let keywords = getKeywords() { - RecipeListSection(list: keywords) - } else { - Text(LocalizedStringKey("No keywords.")) - } - } - } title: { - HStack { - SecondaryLabel(text: LocalizedStringKey("Keywords")) - Spacer() - } - } - .padding() - } - - func getKeywords() -> [String]? { - let keywords = recipeDetail.keywords.components(separatedBy: ",") - return keywords.isEmpty ? nil : keywords - } -} - - -fileprivate struct MoreInformationSection: View { - let recipeDetail: RecipeDetail - - var body: some View { - CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandInfoSection) { - VStack(alignment: .leading) { - Text("Created: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateCreated) ?? "")") - Text("Last modified: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateModified) ?? "")") - if recipeDetail.url != "", let url = URL(string: recipeDetail.url) { - HStack() { - Text("URL:") - Link(destination: url) { - Text(recipeDetail.url) - } - } - } - } - .font(.caption) - .foregroundStyle(Color.secondary) - } title: { - HStack { - SecondaryLabel(text: "More information") - Spacer() - } - } - .padding() - } -} - - -fileprivate struct RecipeIngredientSection: View { - @EnvironmentObject var groceryList: GroceryList - @State var recipeDetail: RecipeDetail - - var body: some View { - VStack(alignment: .leading) { - HStack { - if recipeDetail.recipeYield == 0 { - SecondaryLabel(text: LocalizedStringKey("Ingredients")) - } else if recipeDetail.recipeYield == 1 { - SecondaryLabel(text: LocalizedStringKey("Ingredients per serving")) - } else { - SecondaryLabel(text: LocalizedStringKey("Ingredients for \(recipeDetail.recipeYield) servings")) - } - Spacer() - Button { - withAnimation { - if groceryList.containsRecipe(recipeDetail.id) { - groceryList.deleteGroceryRecipe(recipeDetail.id) - } else { - groceryList.addItems(recipeDetail.recipeIngredient, toRecipe: recipeDetail.id, recipeName: recipeDetail.name) - } - } - } label: { - if #available(iOS 17.0, *) { - Image(systemName: "storefront") - } else { - Image(systemName: "heart.text.square") - } - } - } - - ForEach(recipeDetail.recipeIngredient, id: \.self) { ingredient in - IngredientListItem(ingredient: ingredient, recipeId: recipeDetail.id) { - groceryList.addItem(ingredient, toRecipe: recipeDetail.id, recipeName: recipeDetail.name) - } - .padding(4) - - } - }.padding() - } -} - - -fileprivate struct RecipeToolSection: View { - @State var recipeDetail: RecipeDetail - - var body: some View { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: "Tools") - Spacer() - } - RecipeListSection(list: recipeDetail.tool) - }.padding() - } -} - - -fileprivate struct IngredientListItem: View { - @EnvironmentObject var groceryList: GroceryList - @State var ingredient: String - @State var recipeId: String - let addToGroceryListAction: () -> Void - @State var isSelected: Bool = false - - // Drag animation - @State private var dragOffset: CGFloat = 0 - @State private var animationStartOffset: CGFloat = 0 - let maxDragDistance = 50.0 - - var body: some View { - HStack(alignment: .top) { - if groceryList.containsItem(at: recipeId, item: ingredient) { - if #available(iOS 17.0, *) { - Image(systemName: "storefront") - .foregroundStyle(Color.green) - } else { - Image(systemName: "heart.text.square") - .foregroundStyle(Color.green) - } - - } else if isSelected { - Image(systemName: "checkmark.circle") - } else { - Image(systemName: "circle") - } - - Text("\(ingredient)") - .multilineTextAlignment(.leading) - .lineLimit(5) - Spacer() - } - .foregroundStyle(isSelected ? Color.secondary : Color.primary) - .onTapGesture { - isSelected.toggle() - } - .offset(x: dragOffset, y: 0) - .animation(.easeInOut, value: isSelected) - - .gesture( - DragGesture() - .onChanged { gesture in - // Update drag offset as the user drags - if animationStartOffset == 0 { - animationStartOffset = gesture.translation.width - } - let dragAmount = gesture.translation.width - let offset = min(dragAmount, maxDragDistance + pow(dragAmount - maxDragDistance, 0.7)) - animationStartOffset - self.dragOffset = max(0, offset) - } - .onEnded { gesture in - withAnimation { - if dragOffset > maxDragDistance * 0.3 { // Swipe threshold - if groceryList.containsItem(at: recipeId, item: ingredient) { - groceryList.deleteItem(ingredient, fromRecipe: recipeId) - } else { - addToGroceryListAction() - } - - } - // Animate back to original position - - self.dragOffset = 0 - self.animationStartOffset = 0 - } - } - ) - } -} - - - -fileprivate struct RecipeListSection: View { - @State var list: [String] - - var body: some View { - VStack(alignment: .leading) { - ForEach(list, id: \.self) { item in - HStack(alignment: .top) { - Text("\u{2022}") - Text("\(item)") - .multilineTextAlignment(.leading) - } - .padding(4) - } - } - } -} - - -fileprivate struct RecipeInstructionSection: View { - @State var recipeDetail: RecipeDetail - var body: some View { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Instructions")) - Spacer() - } - ForEach(0.. Binding { + Binding( + get: { viewModel.recipeDetail.nutrition[key, default: ""] }, + set: { viewModel.recipeDetail.nutrition[key] = $0 } + ) + } +} + + +// MARK: - Keyword Section + +fileprivate struct RecipeKeywordSection: View { + @ObservedObject var viewModel: RecipeView.ViewModel + @State var keywords: [String] = [] + + var body: some View { + CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandKeywordSection) { + Group { + if !keywords.isEmpty || viewModel.editMode { + //RecipeListSection(list: keywords) + EditableStringList(items: $keywords, editMode: $viewModel.editMode, titleKey: "Keyword", lineLimit: 0...1, axis: .horizontal) { + RecipeListSection(list: keywords) + } + } else { + Text(LocalizedStringKey("No keywords.")) + } + } + .onAppear { + self.keywords = viewModel.recipeDetail.keywords.components(separatedBy: ",") + } + .onDisappear { + viewModel.recipeDetail.keywords = keywords.joined(separator: ",") + } + } title: { + HStack { + SecondaryLabel(text: LocalizedStringKey("Keywords")) + Spacer() + } + } + .padding() + } +} + + +// MARK: - More Information Section + +fileprivate struct MoreInformationSection: View { + let recipeDetail: RecipeDetail + + var body: some View { + CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandInfoSection) { + VStack(alignment: .leading) { + Text("Created: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateCreated) ?? "")") + Text("Last modified: \(Date.convertISOStringToLocalString(isoDateString: recipeDetail.dateModified) ?? "")") + if recipeDetail.url != "", let url = URL(string: recipeDetail.url) { + HStack() { + Text("URL:") + Link(destination: url) { + Text(recipeDetail.url) + } + } + } + } + .font(.caption) + .foregroundStyle(Color.secondary) + } title: { + HStack { + SecondaryLabel(text: "More information") + Spacer() + } + } + .padding() + } +} + +fileprivate struct RecipeListSection: View { + @State var list: [String] + + var body: some View { + VStack(alignment: .leading) { + ForEach(list, id: \.self) { item in + HStack(alignment: .top) { + Text("\u{2022}") + Text("\(item)") + .multilineTextAlignment(.leading) + } + .padding(4) + } + } + } +} + +fileprivate struct SecondaryLabel: View { + let text: LocalizedStringKey + var body: some View { + Text(text) + .foregroundColor(.secondary) + .font(.headline) + .padding(.vertical, 5) + } +} + + + + + +// MARK: - Ingredients Section + +fileprivate struct RecipeIngredientSection: View { + @EnvironmentObject var groceryList: GroceryList + @ObservedObject var viewModel: RecipeView.ViewModel + + var body: some View { + VStack(alignment: .leading) { + HStack { + if viewModel.recipeDetail.recipeYield == 0 { + SecondaryLabel(text: LocalizedStringKey("Ingredients")) + } else if viewModel.recipeDetail.recipeYield == 1 { + SecondaryLabel(text: LocalizedStringKey("Ingredients per serving")) + } else { + SecondaryLabel(text: LocalizedStringKey("Ingredients for \(viewModel.recipeDetail.recipeYield) servings")) + } + Spacer() + Button { + withAnimation { + if groceryList.containsRecipe(viewModel.recipeDetail.id) { + groceryList.deleteGroceryRecipe(viewModel.recipeDetail.id) + } else { + groceryList.addItems( + viewModel.recipeDetail.recipeIngredient, + toRecipe: viewModel.recipeDetail.id, + recipeName: viewModel.recipeDetail.name + ) + } + } + } label: { + if #available(iOS 17.0, *) { + Image(systemName: "storefront") + } else { + Image(systemName: "heart.text.square") + } + } + } + + EditableStringList(items: $viewModel.recipeDetail.recipeIngredient, editMode: $viewModel.editMode, titleKey: "Ingredient", lineLimit: 0...1, axis: .horizontal) { + ForEach(0.. Void + @State var isSelected: Bool = false + + // Drag animation + @State private var dragOffset: CGFloat = 0 + @State private var animationStartOffset: CGFloat = 0 + let maxDragDistance = 50.0 + + var body: some View { + HStack(alignment: .top) { + if groceryList.containsItem(at: recipeId, item: ingredient) { + if #available(iOS 17.0, *) { + Image(systemName: "storefront") + .foregroundStyle(Color.green) + } else { + Image(systemName: "heart.text.square") + .foregroundStyle(Color.green) + } + + } else if isSelected { + Image(systemName: "checkmark.circle") + } else { + Image(systemName: "circle") + } + + Text("\(ingredient)") + .multilineTextAlignment(.leading) + .lineLimit(5) + Spacer() + } + .foregroundStyle(isSelected ? Color.secondary : Color.primary) + .onTapGesture { + isSelected.toggle() + } + .offset(x: dragOffset, y: 0) + .animation(.easeInOut, value: isSelected) + + .gesture( + DragGesture() + .onChanged { gesture in + // Update drag offset as the user drags + if animationStartOffset == 0 { + animationStartOffset = gesture.translation.width + } + let dragAmount = gesture.translation.width + let offset = min(dragAmount, maxDragDistance + pow(dragAmount - maxDragDistance, 0.7)) - animationStartOffset + self.dragOffset = max(0, offset) + } + .onEnded { gesture in + withAnimation { + if dragOffset > maxDragDistance * 0.3 { // Swipe threshold + if groceryList.containsItem(at: recipeId, item: ingredient) { + groceryList.deleteItem(ingredient, fromRecipe: recipeId) + } else { + addToGroceryListAction() + } + + } + // Animate back to original position + + self.dragOffset = 0 + self.animationStartOffset = 0 + } + } + ) + } +} + + +// MARK: - Instructions Section + +fileprivate struct RecipeInstructionSection: View { + @ObservedObject var viewModel: RecipeView.ViewModel + + var body: some View { + VStack(alignment: .leading) { + HStack { + SecondaryLabel(text: LocalizedStringKey("Instructions")) + Spacer() + } + EditableStringList(items: $viewModel.recipeDetail.recipeInstructions, editMode: $viewModel.editMode, titleKey: "Instruction", lineLimit: 0...15, axis: .vertical) { + ForEach(0.. = 0...1 + @State var axis: Axis = .horizontal + + var body: some View { + if editMode { + TextField(titleKey, text: $text, axis: axis) + .textFieldStyle(.roundedBorder) + .lineLimit(lineLimit) + } else { + Text(text) + } + } +} + + +fileprivate struct EditableStringList: View { + @Binding var items: [String] + @Binding var editMode: Bool + @State var titleKey: LocalizedStringKey = "" + @State var lineLimit: ClosedRange = 0...50 + @State var axis: Axis = .vertical + + @State var editableItems: [ReorderableItem] = [] + + var content: () -> Content + + var body: some View { + if editMode { + VStack { + ReorderableForEach(items: $editableItems, defaultItem: ReorderableItem(item: "")) { ix, item in + TextField("", text: $editableItems[ix].item, axis: axis) + .textFieldStyle(.roundedBorder) + .lineLimit(lineLimit) + } + } + .onAppear { + editableItems = ReorderableItem.list(items: items) + } + .onDisappear { + items = ReorderableItem.items(editableItems) + } + } else { + content() + } + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/ShareView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/ShareView.swift new file mode 100644 index 0000000..655f69b --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/ShareView.swift @@ -0,0 +1,54 @@ +// +// ShareView.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 17.02.24. +// + +import Foundation +import SwiftUI + + +struct ShareView: View { + @State var recipeDetail: RecipeDetail + @State var recipeImage: UIImage? + @Binding var presentShareSheet: Bool + + @State var exporter = RecipeExporter() + @State var sharedURL: URL? = nil + + var body: some View { + VStack(alignment: .leading) { + if let url = sharedURL { + ShareLink(item: url, subject: Text("PDF Document")) { + Image(systemName: "doc") + Text("Share as PDF") + } + .foregroundStyle(.primary) + .bold() + .padding() + } + + ShareLink(item: exporter.createText(recipe: recipeDetail), subject: Text("Recipe")) { + Image(systemName: "ellipsis.message") + Text("Share as text") + } + .foregroundStyle(.primary) + .bold() + .padding() + + /*ShareLink(item: exporter.createJson(recipe: recipeDetail), subject: Text("Recipe")) { + Image(systemName: "doc.badge.gearshape") + Text("Share as JSON") + } + .foregroundStyle(.primary) + .bold() + .padding() + */ + } + .task { + self.sharedURL = exporter.createPDF(recipe: recipeDetail, image: recipeImage) + } + + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift new file mode 100644 index 0000000..c9ac4e7 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift @@ -0,0 +1,118 @@ +// +// ReorderableForEach.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 15.02.24. +// + +import Foundation +import SwiftUI +import UniformTypeIdentifiers + + +struct ReorderableForEach: View { + @Binding var items: [ReorderableItem] + var defaultItem: ReorderableItem + var content: (Int, Item) -> Content + + @State var draggedItemId: UUID? = nil + @State var allowDeletion: Bool = false + + var body: some View { + VStack { + ForEach(Array(zip(items.indices, items)), id: \.1.id) { ix, item in + HStack { + if allowDeletion { + Button { + items.remove(at: ix) + } label: { + Image(systemName: "minus.circle.fill") + .foregroundColor(.red) + .padding(5) + .bold() + }.buttonStyle(.plain) + } + HStack { + content(ix, item.item) + Image(systemName: "line.3.horizontal") + .padding(5) + } + .padding(5) + .background( + RoundedRectangle(cornerRadius: 10) + .foregroundStyle(.background) + .ignoresSafeArea() + ) + } + .onDrag { + self.draggedItemId = item.id + return NSItemProvider(item: nil, typeIdentifier: item.id.uuidString) + } preview: { + EmptyView() + } + .onDrop(of: [.plainText], delegate: DropViewDelegate(targetId: item.id, sourceId: $draggedItemId, items: $items)) + } + HStack { + Button { + allowDeletion.toggle() + } label: { + Text(allowDeletion ? "Disable deletion" : "Enable deletion") + .bold() + .padding(.vertical, 3) + .padding(.horizontal) + } + .tint(Color.red) + Spacer() + Button { + items.append(defaultItem) + } label: { + Image(systemName: "plus") + .bold() + .padding(.vertical, 3) + .padding(.horizontal) + } + .buttonStyle(.borderedProminent) + } + }.animation(.default, value: allowDeletion) + } +} + + +struct ReorderableItem: Identifiable { + let id = UUID() + var item: Item + + static func list(items: [Item]) -> [ReorderableItem] { + items.map({ item in ReorderableItem(item: item) }) + } + + static func items(_ reorderableItems: [ReorderableItem]) -> [Item] { + reorderableItems.map { $0.item } + } +} + + +struct DropViewDelegate: DropDelegate { + let targetId: UUID + @Binding var sourceId : UUID? + @Binding var items: [ReorderableItem] + + func performDrop(info: DropInfo) -> Bool { + return true + } + + func dropEntered(info: DropInfo) { + guard let sourceId = self.sourceId else { + return + } + + if sourceId != targetId { + guard let sourceIndex = items.firstIndex(where: { $0.id == sourceId }), + let targetIndex = items.firstIndex(where: { $0.id == targetId }) + else { return } + withAnimation(.default) { + self.items.move(fromOffsets: IndexSet(integer: sourceIndex), toOffset: targetIndex > sourceIndex ? targetIndex + 1 : targetIndex) + } + } + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift index 76bebfa..5c7f6a2 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift @@ -53,7 +53,7 @@ struct RecipeTabView: View { } detail: { NavigationStack { if let category = viewModel.selectedCategory { - CategoryDetailView( + RecipeListView( categoryName: category.name, viewModel: mainViewModel, showEditView: $viewModel.presentEditView diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift index 2f26d9a..3ed91d1 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift @@ -35,7 +35,7 @@ struct SearchTabView: View { } } .navigationDestination(for: Recipe.self) { recipe in - RecipeDetailView(viewModel: mainViewModel, recipe: recipe) + RecipeView(appState: mainViewModel, viewModel: RecipeView.ViewModel(recipe: recipe)) } .searchable(text: $viewModel.searchText, prompt: "Search recipes/keywords") } diff --git a/Nextcloud-Cookbook-iOS-Client-Info.plist b/Nextcloud-Cookbook-iOS-Client-Info.plist new file mode 100644 index 0000000..bca21ac --- /dev/null +++ b/Nextcloud-Cookbook-iOS-Client-Info.plist @@ -0,0 +1,17 @@ + + + + + UTImportedTypeDeclarations + + + UTTypeIcons + + UTTypeIdentifier + com.cookbook-client.uuid + UTTypeTagSpecification + + + + +