diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj index c714523..cc81935 100644 --- a/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj +++ b/Nextcloud Cookbook iOS Client.xcodeproj/project.pbxproj @@ -42,7 +42,7 @@ A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; }; A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */; }; A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; }; - A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; }; + A7F3F8EA2ACC221C0076C227 /* CategoryPickerViewOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */; }; 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 */; }; @@ -53,6 +53,9 @@ 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 */; }; + A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */; }; + A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */; }; + A9BBB3902B91BE31002DA7FF /* ObservableRecipeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */; }; A9CA6CEF2B4C086100F78AB5 /* RecipeExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */; }; A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */ = {isa = PBXBuildFile; productRef = A9CA6CF52B4C63F200F78AB5 /* TPPDF */; }; A9D89AB02B4FE97800F49D92 /* TimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D89AAF2B4FE97800F49D92 /* TimerView.swift */; }; @@ -115,7 +118,7 @@ A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleView.swift; sourceTree = ""; }; A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordPickerView.swift; sourceTree = ""; }; - A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = ""; }; + A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerViewOld.swift; sourceTree = ""; }; 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 = ""; }; @@ -125,6 +128,9 @@ 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 = ""; }; + 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 /* ObservableRecipeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableRecipeDetail.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; }; @@ -269,6 +275,7 @@ A70171CA2AB4CD1700064C43 /* UserSettings.swift */, A70171C52AB4C43A00064C43 /* DataModels.swift */, A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */, + A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */, ); path = Data; sourceTree = ""; @@ -352,6 +359,7 @@ A70171BD2AB4987900064C43 /* RecipeListView.swift */, A70171C12AB498C600064C43 /* RecipeCardView.swift */, A70171BF2AB498A900064C43 /* RecipeView.swift */, + A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */, A9D89AAF2B4FE97800F49D92 /* TimerView.swift */, A97B4D342B80B82A00EC1A88 /* ShareView.swift */, ); @@ -362,8 +370,7 @@ isa = PBXGroup; children = ( A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */, - A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */, - A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */, + A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */, ); path = RecipeEditing; sourceTree = ""; @@ -371,8 +378,10 @@ A9C3BE522B630F1300562C79 /* ReusableViews */ = { isa = PBXGroup; children = ( + A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */, A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */, A95364662B7E89F1001018B0 /* ReorderableForEach.swift */, + A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */, ); path = ReusableViews; sourceTree = ""; @@ -538,8 +547,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */, A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */, A9D89AB02B4FE97800F49D92 /* TimerView.swift in Sources */, + A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */, A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */, A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */, A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */, @@ -554,6 +565,7 @@ A787B0782B2B1E6400C2DF1B /* DateExtension.swift in Sources */, A79AA8E92B062DD1007D25F2 /* CookbookApiV1.swift in Sources */, A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */, + A9BBB3902B91BE31002DA7FF /* ObservableRecipeDetail.swift in Sources */, A70171B42AB2122900064C43 /* NetworkUtils.swift in Sources */, A97B4D322B80B3E900EC1A88 /* RecipeModels.swift in Sources */, A70171BE2AB4987900064C43 /* RecipeListView.swift in Sources */, @@ -562,7 +574,7 @@ A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */, A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */, A70171C02AB498A900064C43 /* RecipeView.swift in Sources */, - A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */, + A7F3F8EA2ACC221C0076C227 /* CategoryPickerViewOld.swift in Sources */, A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */, A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */, A9CA6CEF2B4C086100F78AB5 /* RecipeExporter.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 1a36d67..cad8a5f 100644 Binary files a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate and b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Nextcloud Cookbook iOS Client/AppState.swift b/Nextcloud Cookbook iOS Client/AppState.swift index 0333e13..7a52f46 100644 --- a/Nextcloud Cookbook iOS Client/AppState.swift +++ b/Nextcloud Cookbook iOS Client/AppState.swift @@ -18,6 +18,7 @@ import UIKit var recipeImages: [Int: [String: UIImage]] = [:] var imagesNeedUpdate: [Int: [String: Bool]] = [:] var lastUpdates: [String: Date] = [:] + var allKeywords: [RecipeKeyword] = [] private let dataStore: DataStore diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/Contents.json b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/Contents.json deleted file mode 100644 index 3f7f444..0000000 --- a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "filename" : "cookbook-icon-20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "cookbook-icon-20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "cookbook-icon-29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "cookbook-icon-29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "cookbook-icon-40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "cookbook-icon-40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "cookbook-icon-60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "cookbook-icon-60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "cookbook-icon-20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "cookbook-icon-20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "cookbook-icon-29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "cookbook-icon-29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "cookbook-icon-40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "cookbook-icon-40@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "cookbook-icon-76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "cookbook-icon-76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "cookbook-icon-83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "cookbook-icon-1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-1024.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-1024.png deleted file mode 100644 index c78e0c2..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-1024.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20.png deleted file mode 100644 index 5d4e6a2..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@2x.png deleted file mode 100644 index cf17cef..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@3x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@3x.png deleted file mode 100644 index d84d18f..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-20@3x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29.png deleted file mode 100644 index fd2e3a8..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@2x.png deleted file mode 100644 index d40d5fe..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@3x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@3x.png deleted file mode 100644 index a2d472f..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-29@3x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40.png deleted file mode 100644 index cf17cef..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@2x.png deleted file mode 100644 index c0d9db9..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@3x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@3x.png deleted file mode 100644 index 3b61b69..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-40@3x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@2x.png deleted file mode 100644 index 3b61b69..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@3x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@3x.png deleted file mode 100644 index 7357fda..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-60@3x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76.png deleted file mode 100644 index dd4d389..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76@2x.png deleted file mode 100644 index b879aae..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-76@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-83.5@2x.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-83.5@2x.png deleted file mode 100644 index b5f070c..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/AppIcon_old.appiconset/cookbook-icon-83.5@2x.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Contents.json b/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Contents.json index e6140ae..720f745 100644 --- a/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Contents.json +++ b/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "cookbook-icon.png", + "filename" : "Hintergrund-1024.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Hintergrund-1024.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Hintergrund-1024.png new file mode 100644 index 0000000..9dd3e8d Binary files /dev/null and b/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/Hintergrund-1024.png differ diff --git a/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/cookbook-icon.png b/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/cookbook-icon.png deleted file mode 100644 index 69fb117..0000000 Binary files a/Nextcloud Cookbook iOS Client/Assets.xcassets/cookbook-icon.imageset/cookbook-icon.png and /dev/null differ diff --git a/Nextcloud Cookbook iOS Client/Data/DataModels.swift b/Nextcloud Cookbook iOS Client/Data/DataModels.swift index a9157b4..2a20a1f 100644 --- a/Nextcloud Cookbook iOS Client/Data/DataModels.swift +++ b/Nextcloud Cookbook iOS Client/Data/DataModels.swift @@ -18,16 +18,12 @@ struct Category: Codable { } } - extension Category: Identifiable, Hashable { var id: String { name } } - - - // MARK: - Login flow struct LoginV2Request: Codable { diff --git a/Nextcloud Cookbook iOS Client/Data/ObservableRecipeDetail.swift b/Nextcloud Cookbook iOS Client/Data/ObservableRecipeDetail.swift new file mode 100644 index 0000000..48bb229 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Data/ObservableRecipeDetail.swift @@ -0,0 +1,88 @@ +// +// ObservableRecipeDetail.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 01.03.24. +// + +import Foundation +import SwiftUI + +class ObservableRecipeDetail: ObservableObject { + var id: String + @Published var name: String + @Published var keywords: [String] + @Published var imageUrl: String + @Published var prepTime: DurationComponents + @Published var cookTime: DurationComponents + @Published var totalTime: DurationComponents + @Published var description: String + @Published var url: String + @Published var recipeYield: Int + @Published var recipeCategory: String + @Published var tool: [ReorderableItem] + @Published var recipeIngredient: [ReorderableItem] + @Published var recipeInstructions: [ReorderableItem] + @Published var nutrition: [String:String] + + init() { + id = "" + name = String(localized: "New Recipe") + keywords = [] + imageUrl = "" + prepTime = DurationComponents() + cookTime = DurationComponents() + totalTime = DurationComponents() + description = "" + url = "" + recipeYield = 0 + recipeCategory = "" + tool = [] + recipeIngredient = [] + recipeInstructions = [] + nutrition = [:] + } + + init(_ recipeDetail: RecipeDetail) { + id = recipeDetail.id + name = recipeDetail.name + keywords = recipeDetail.keywords.components(separatedBy: ",") + imageUrl = recipeDetail.imageUrl + prepTime = DurationComponents.fromPTString(recipeDetail.prepTime ?? "") + cookTime = DurationComponents.fromPTString(recipeDetail.cookTime ?? "") + totalTime = DurationComponents.fromPTString(recipeDetail.totalTime ?? "") + description = recipeDetail.description + url = recipeDetail.url + recipeYield = recipeDetail.recipeYield + recipeCategory = recipeDetail.recipeCategory + tool = ReorderableItem.list(items: recipeDetail.tool) + recipeIngredient = ReorderableItem.list(items: recipeDetail.recipeIngredient) + recipeInstructions = ReorderableItem.list(items: recipeDetail.recipeInstructions) + nutrition = recipeDetail.nutrition + } + + func toRecipeDetail() -> RecipeDetail { + return RecipeDetail( + name: self.name, + keywords: self.keywords.joined(separator: ","), + dateCreated: "", + dateModified: "", + imageUrl: self.imageUrl, + id: self.id, + prepTime: self.prepTime.toPTString(), + cookTime: self.cookTime.toPTString(), + totalTime: self.totalTime.toPTString(), + description: self.description, + url: self.url, + recipeYield: self.recipeYield, + recipeCategory: self.recipeCategory, + tool: ReorderableItem.items(self.tool), + recipeIngredient: ReorderableItem.items(self.recipeIngredient), + recipeInstructions: ReorderableItem.items(self.recipeInstructions), + nutrition: self.nutrition + ) + } +} + + + diff --git a/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift b/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift index 4d4e68b..de51043 100644 --- a/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift +++ b/Nextcloud Cookbook iOS Client/Data/RecipeModels.swift @@ -146,7 +146,8 @@ struct RecipeKeyword: Codable { enum Nutrition: CaseIterable { - case calories, + case servingSize, + calories, carbohydrateContent, cholesterolContent, fatContent, @@ -158,35 +159,39 @@ enum Nutrition: CaseIterable { sodiumContent, sugarContent - var localizedDescription: LocalizedStringKey { + var localizedDescription: String { switch self { + case .servingSize: + return NSLocalizedString("Serving size", comment: "Serving size") case .calories: - "Calories" + return NSLocalizedString("Calories", comment: "Calories") case .carbohydrateContent: - "Carbohydrate content" + return NSLocalizedString("Carbohydrate content", comment: "Carbohydrate content") case .cholesterolContent: - "Cholesterol content" + return NSLocalizedString("Cholesterol content", comment: "Cholesterol content") case .fatContent: - "Fat content" + return NSLocalizedString("Fat content", comment: "Fat content") case .saturatedFatContent: - "Saturated fat content" + return NSLocalizedString("Saturated fat content", comment: "Saturated fat content") case .unsaturatedFatContent: - "Unsaturated fat content" + return NSLocalizedString("Unsaturated fat content", comment: "Unsaturated fat content") case .transFatContent: - "Trans fat content" + return NSLocalizedString("Trans fat content", comment: "Trans fat content") case .fiberContent: - "Fiber content" + return NSLocalizedString("Fiber content", comment: "Fiber content") case .proteinContent: - "Protein content" + return NSLocalizedString("Protein content", comment: "Protein content") case .sodiumContent: - "Sodium content" + return NSLocalizedString("Sodium content", comment: "Sodium content") case .sugarContent: - "Sugar content" + return NSLocalizedString("Sugar content", comment: "Sugar content") } } var dictKey: String { switch self { + case .servingSize: + "servingSize" case .calories: "calories" case .carbohydrateContent: diff --git a/Nextcloud Cookbook iOS Client/Localizable.xcstrings b/Nextcloud Cookbook iOS Client/Localizable.xcstrings index 6400302..5098800 100644 --- a/Nextcloud Cookbook iOS Client/Localizable.xcstrings +++ b/Nextcloud Cookbook iOS Client/Localizable.xcstrings @@ -111,6 +111,16 @@ } } }, + "%@: %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@: %2$@" + } + } + } + }, "%lld" : { "localizations" : { "de" : { @@ -155,6 +165,16 @@ } } }, + "%lld h %lld min" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld h %2$lld min" + } + } + } + }, "%lld h, %lld min" : { "localizations" : { "de" : { @@ -559,7 +579,7 @@ } }, "Calories" : { - + "comment" : "Calories" }, "Cancel" : { "localizations" : { @@ -584,7 +604,7 @@ } }, "Carbohydrate content" : { - + "comment" : "Carbohydrate content" }, "Category" : { "localizations" : { @@ -631,7 +651,7 @@ } }, "Cholesterol content" : { - + "comment" : "Cholesterol content" }, "Configure what is stored on your device." : { "localizations" : { @@ -1152,6 +1172,9 @@ } } } + }, + "Edit keywords" : { + }, "Enable deletion" : { @@ -1267,10 +1290,10 @@ } }, "Fat content" : { - + "comment" : "Fat content" }, "Fiber content" : { - + "comment" : "Fiber content" }, "General" : { "localizations" : { @@ -1636,9 +1659,6 @@ } } } - }, - "Keyword" : { - }, "Keywords" : { "localizations" : { @@ -1971,6 +1991,9 @@ } } } + }, + "New Recipe" : { + }, "Nextcloud Login" : { "localizations" : { @@ -2347,7 +2370,7 @@ } }, "Protein content" : { - + "comment" : "Protein content" }, "Recipe" : { "localizations" : { @@ -2370,6 +2393,9 @@ } } } + }, + "Recipe Name" : { + }, "Recipes" : { "localizations" : { @@ -2438,7 +2464,7 @@ } }, "Saturated fat content" : { - + "comment" : "Saturated fat content" }, "Search" : { "localizations" : { @@ -2527,6 +2553,9 @@ } } } + }, + "Select Item" : { + }, "Selected keywords:" : { "localizations" : { @@ -2550,6 +2579,9 @@ } } }, + "Serving size" : { + "comment" : "Serving size" + }, "Servings:" : { "localizations" : { "de" : { @@ -2683,7 +2715,7 @@ } }, "Sodium content" : { - + "comment" : "Sodium content" }, "Store recipe images locally" : { "localizations" : { @@ -2752,7 +2784,7 @@ } }, "Sugar content" : { - + "comment" : "Sugar content" }, "Support" : { "localizations" : { @@ -3090,7 +3122,7 @@ } }, "Trans fat content" : { - + "comment" : "Trans fat content" }, "Unable to complete action." : { "localizations" : { @@ -3181,7 +3213,7 @@ } }, "Unsaturated fat content" : { - + "comment" : "Unsaturated fat content" }, "Upload" : { "localizations" : { diff --git a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift index f831488..430b325 100644 --- a/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift +++ b/Nextcloud Cookbook iOS Client/Nextcloud_Cookbook_iOS_ClientApp.swift @@ -11,7 +11,6 @@ import SwiftUI @main struct Nextcloud_Cookbook_iOS_ClientApp: App { - @StateObject var mainViewModel = AppState() @AppStorage("onboarding") var onboarding = true @AppStorage("language") var language = Locale.current.language.languageCode?.identifier ?? "en" @@ -21,7 +20,7 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App { if onboarding { OnboardingView() } else { - MainView(viewModel: mainViewModel) + MainView() } } .transition(.slide) diff --git a/Nextcloud Cookbook iOS Client/Util/DurationComponents.swift b/Nextcloud Cookbook iOS Client/Util/DurationComponents.swift index edd017a..d991708 100644 --- a/Nextcloud Cookbook iOS Client/Util/DurationComponents.swift +++ b/Nextcloud Cookbook iOS Client/Util/DurationComponents.swift @@ -58,6 +58,21 @@ class DurationComponents: ObservableObject { } } + var displayString: String { + let intHour = Int(hourComponent) ?? 0 + let intMinute = Int(minuteComponent) ?? 0 + + if intHour != 0 && intMinute != 0 { + return "\(intHour) h \(intMinute) min" + } else if intHour == 0 && intMinute != 0 { + return "\(intMinute) min" + } else if intHour != 0 && intMinute == 0 { + return "\(intHour) h" + } else { + return "-" + } + } + static func fromPTString(_ PTRepresentation: String) -> DurationComponents { let duration = DurationComponents() let hourRegex = /([0-9]{1,2})H/ @@ -86,21 +101,6 @@ class DurationComponents: ObservableObject { return "PT\(hourComponent)H\(minuteComponent)M00S" } - func toText() -> LocalizedStringKey { - let intHour = Int(hourComponent) ?? 0 - let intMinute = Int(minuteComponent) ?? 0 - - if intHour != 0 && intMinute != 0 { - return "\(intHour) h, \(intMinute) min" - } else if intHour == 0 && intMinute != 0 { - return "\(intMinute) min" - } else if intHour != 0 && intMinute == 0 { - return "\(intHour) h" - } else { - return "-" - } - } - func toTimerText() -> String { var timeString = "" if hourComponent != "00" { @@ -152,4 +152,5 @@ class DurationComponents: ObservableObject { return nil } } + } diff --git a/Nextcloud Cookbook iOS Client/Views/MainView.swift b/Nextcloud Cookbook iOS Client/Views/MainView.swift index 039c37b..2c35775 100644 --- a/Nextcloud Cookbook iOS Client/Views/MainView.swift +++ b/Nextcloud Cookbook iOS Client/Views/MainView.swift @@ -9,7 +9,7 @@ import SwiftUI import SimilaritySearchKit struct MainView: View { - @StateObject var viewModel = AppState() + @StateObject var appState = AppState() @StateObject var groceryList = GroceryList() // Tab ViewModels @@ -24,7 +24,7 @@ struct MainView: View { TabView { RecipeTabView() .environmentObject(recipeViewModel) - .environmentObject(viewModel) + .environmentObject(appState) .environmentObject(groceryList) .tabItem { Label("Recipes", systemImage: "book.closed.fill") @@ -33,7 +33,7 @@ struct MainView: View { SearchTabView() .environmentObject(searchViewModel) - .environmentObject(viewModel) + .environmentObject(appState) .environmentObject(groceryList) .tabItem { Label("Search", systemImage: "magnifyingglass") @@ -53,12 +53,12 @@ struct MainView: View { } .task { recipeViewModel.presentLoadingIndicator = true - await viewModel.getCategories() - await viewModel.updateAllRecipeDetails() + await appState.getCategories() + await appState.updateAllRecipeDetails() // Open detail view for default category if UserSettings.shared.defaultCategory != "" { - if let cat = viewModel.categories.first(where: { c in + if let cat = appState.categories.first(where: { c in if c.name == UserSettings.shared.defaultCategory { return true } diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerViewOld.swift similarity index 97% rename from Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerView.swift rename to Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerViewOld.swift index 551600d..27bc070 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerView.swift +++ b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/CategoryPickerViewOld.swift @@ -10,7 +10,7 @@ import SwiftUI -struct CategoryPickerView: View { +struct CategoryPickerViewOld: View { @State var title: String @State var searchSuggestions: [String] @Binding var selection: String diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift index a90455e..d91c6e2 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift +++ b/Nextcloud Cookbook iOS Client/Views/RecipeEditing/RecipeEditView.swift @@ -132,7 +132,7 @@ struct RecipeEditView: View { Section() { NavigationLink(viewModel.recipe.recipeCategory == "" ? "Category" : "Category: \(viewModel.recipe.recipeCategory)") { - CategoryPickerView( + CategoryPickerViewOld( title: "Category", searchSuggestions: viewModel.mainViewModel.categories.map({ category in category.name == "*" ? "Other" : category.name diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/KeywordPickerView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/KeywordPickerView.swift similarity index 93% rename from Nextcloud Cookbook iOS Client/Views/RecipeEditing/KeywordPickerView.swift rename to Nextcloud Cookbook iOS Client/Views/Recipes/KeywordPickerView.swift index 2a7b6e7..1f708d0 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeEditing/KeywordPickerView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/KeywordPickerView.swift @@ -11,6 +11,7 @@ import SwiftUI struct KeywordPickerView: View { + @Environment(\.presentationMode) var presentationMode @State var title: String @State var searchSuggestions: [RecipeKeyword] @Binding var selection: [String] @@ -20,6 +21,14 @@ struct KeywordPickerView: View { var body: some View { VStack(alignment: .leading) { + HStack { + Spacer() + Button { + presentationMode.wrappedValue.dismiss() + } label: { + Text("Done") + }.padding() + } TextField(title, text: $searchText) .textFieldStyle(.roundedBorder) .padding() diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift index abefdd1..924a91a 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeCardView.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI struct RecipeCardView: View { - @State var viewModel: AppState + @EnvironmentObject var appState: AppState @State var recipe: Recipe @State var recipeThumb: UIImage? @State var isDownloaded: Bool? = nil @@ -50,18 +50,18 @@ struct RecipeCardView: View { .clipShape(RoundedRectangle(cornerRadius: 17)) .padding(.horizontal) .task { - recipeThumb = await viewModel.getImage( + recipeThumb = await appState.getImage( id: recipe.recipe_id, size: .THUMB, fetchMode: UserSettings.shared.storeThumb ? .preferLocal : .onlyServer ) if recipe.storedLocally == nil { - recipe.storedLocally = viewModel.recipeDetailExists(recipeId: recipe.recipe_id) + recipe.storedLocally = appState.recipeDetailExists(recipeId: recipe.recipe_id) } isDownloaded = recipe.storedLocally } .refreshable { - recipeThumb = await viewModel.getImage( + recipeThumb = await appState.getImage( id: recipe.recipe_id, size: .THUMB, fetchMode: UserSettings.shared.storeThumb ? .preferServer : .onlyServer diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift index 452f86e..7487218 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift @@ -11,9 +11,9 @@ import SwiftUI struct RecipeListView: View { + @EnvironmentObject var appState: AppState @State var categoryName: String @State var searchText: String = "" - @ObservedObject var viewModel: AppState @Binding var showEditView: Bool @State var selectedRecipe: Recipe? = nil @State var presentRecipeView: Bool = false @@ -23,7 +23,7 @@ struct RecipeListView: View { LazyVStack { ForEach(recipesFiltered(), id: \.recipe_id) { recipe in NavigationLink(value: recipe) { - RecipeCardView(viewModel: viewModel, recipe: recipe) + RecipeCardView(recipe: recipe) .shadow(radius: 2) } @@ -36,7 +36,7 @@ struct RecipeListView: View { } } .navigationDestination(for: Recipe.self) { recipe in - RecipeView(appState: viewModel, viewModel: RecipeView.ViewModel(recipe: recipe)) + RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe)) } .navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName) .toolbar { @@ -51,13 +51,13 @@ struct RecipeListView: View { } .searchable(text: $searchText, prompt: "Search recipes/keywords") .task { - await viewModel.getCategory( + await appState.getCategory( named: categoryName, fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer ) } .refreshable { - await viewModel.getCategory( + await appState.getCategory( named: categoryName, fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer ) @@ -65,7 +65,7 @@ struct RecipeListView: View { } func recipesFiltered() -> [Recipe] { - guard let recipes = viewModel.recipes[categoryName] else { return [] } + guard let recipes = appState.recipes[categoryName] else { return [] } guard searchText != "" else { return recipes } return recipes.filter { recipe in recipe.name.lowercased().contains(searchText.lowercased()) || // check name for occurence of search term diff --git a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift index 44ff2b5..22e7789 100644 --- a/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeView.swift @@ -10,71 +10,85 @@ import SwiftUI struct RecipeView: View { - @ObservedObject var appState: AppState + @Environment(\.presentationMode) var presentationMode + @EnvironmentObject var appState: AppState @StateObject var viewModel: ViewModel + @State var imageHeight: CGFloat = 350 + + private enum CoordinateSpaces { + case scrollView + } var body: some View { ScrollView(showsIndicators: false) { - VStack(alignment: .leading) { - ZStack { + VStack(spacing: 0) { + ParallaxHeader( + coordinateSpace: CoordinateSpaces.scrollView, + defaultHeight: imageHeight + ) { if let recipeImage = viewModel.recipeImage { Image(uiImage: recipeImage) .resizable() .scaledToFill() - .frame(maxHeight: 300) - .clipped() } - }.animation(.easeInOut, value: viewModel.recipeImage) + } - - LazyVStack (alignment: .leading) { + VStack(alignment: .leading) { HStack { - EditableText(text: $viewModel.recipeDetail.name, editMode: $viewModel.editMode) + EditableText(text: $viewModel.observableRecipeDetail.name, editMode: $viewModel.editMode, titleKey: "Recipe Name") .font(.title) .bold() - .padding() - .onDisappear { - viewModel.showTitle = true - } - .onAppear { - viewModel.showTitle = false - } + + Spacer() if let isDownloaded = viewModel.isDownloaded { - Spacer() Image(systemName: isDownloaded ? "checkmark.circle" : "icloud.and.arrow.down") .foregroundColor(.secondary) - .padding() } - } + }.padding([.top, .horizontal]) - if viewModel.recipeDetail.description != "" || viewModel.editMode { - EditableText(text: $viewModel.recipeDetail.description, editMode: $viewModel.editMode, lineLimit: 0...10, axis: .vertical) + if viewModel.observableRecipeDetail.description != "" || viewModel.editMode { + EditableText(text: $viewModel.observableRecipeDetail.description, editMode: $viewModel.editMode, titleKey: "Description", lineLimit: 0...5, axis: .vertical) .padding([.bottom, .horizontal]) } + + // Recipe Body Section + RecipeDurationSection(viewModel: viewModel) + Divider() - RecipeDurationSection(viewModel: appState, recipeDetail: viewModel.recipeDetail) + if viewModel.editMode { + RecipeMetadataSection(viewModel: viewModel) + } LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { - if(!viewModel.recipeDetail.recipeIngredient.isEmpty || viewModel.editMode) { + if(!viewModel.observableRecipeDetail.recipeIngredient.isEmpty || viewModel.editMode) { RecipeIngredientSection(viewModel: viewModel) + .background(RoundedRectangle(cornerRadius: 20).foregroundStyle(.ultraThinMaterial)) + .padding(5) } - if(!viewModel.recipeDetail.recipeInstructions.isEmpty || viewModel.editMode) { + if(!viewModel.observableRecipeDetail.recipeInstructions.isEmpty || viewModel.editMode) { RecipeInstructionSection(viewModel: viewModel) + .background(RoundedRectangle(cornerRadius: 20).foregroundStyle(.ultraThinMaterial)) + .padding(5) } - if(!viewModel.recipeDetail.tool.isEmpty || viewModel.editMode) { + if(!viewModel.observableRecipeDetail.tool.isEmpty || viewModel.editMode) { RecipeToolSection(viewModel: viewModel) } RecipeNutritionSection(viewModel: viewModel) - RecipeKeywordSection(viewModel: viewModel) - MoreInformationSection(recipeDetail: viewModel.recipeDetail) + if !viewModel.editMode { + RecipeKeywordSection(viewModel: viewModel) + } + MoreInformationSection(viewModel: viewModel) } - - }.padding(.horizontal, 5) + } + .padding(.horizontal, 5) + .background(Rectangle().foregroundStyle(.background).shadow(radius: 5).mask(Rectangle().padding(.top, -20))) } } + .coordinateSpace(name: CoordinateSpaces.scrollView) + .ignoresSafeArea(.container, edges: .top) .navigationBarTitleDisplayMode(.inline) .navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "") .toolbar { @@ -87,7 +101,11 @@ struct RecipeView: View { ToolbarItem(placement: .topBarTrailing) { Button("Done") { // TODO: POST edited recipe - viewModel.editMode = false + if viewModel.newRecipe { + presentationMode.wrappedValue.dismiss() + } else { + viewModel.editMode = false + } } } } else { @@ -116,36 +134,44 @@ struct RecipeView: View { } } .sheet(isPresented: $viewModel.presentShareSheet) { - ShareView(recipeDetail: viewModel.recipeDetail, + ShareView(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), recipeImage: viewModel.recipeImage, presentShareSheet: $viewModel.presentShareSheet) } .task { - viewModel.recipeDetail = await appState.getRecipe( - id: viewModel.recipe.recipe_id, - fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer - ) ?? RecipeDetail.error - viewModel.recipeImage = await appState.getImage( - id: viewModel.recipe.recipe_id, - size: .FULL, - fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer - ) - if viewModel.recipe.storedLocally == nil { - viewModel.recipe.storedLocally = appState.recipeDetailExists(recipeId: viewModel.recipe.recipe_id) + // Load recipe detail + if !viewModel.newRecipe { + // For existing recipes, load the recipeDetail and image + let recipeDetail = await appState.getRecipe( + id: viewModel.recipe.recipe_id, + fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer + ) ?? RecipeDetail.error + viewModel.setupView(recipeDetail: recipeDetail) + + // Show download badge + if viewModel.recipe.storedLocally == nil { + viewModel.recipe.storedLocally = appState.recipeDetailExists(recipeId: viewModel.recipe.recipe_id) + } + viewModel.isDownloaded = viewModel.recipe.storedLocally + + // Load recipe image + viewModel.recipeImage = await appState.getImage( + id: viewModel.recipe.recipe_id, + size: .FULL, + fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer + ) + if let image = viewModel.recipeImage { + imageHeight = image.size.height < 350 ? image.size.height : 350 + } else { + imageHeight = 100 + } + } else { + // Prepare view for a new recipe + viewModel.setupView(recipeDetail: RecipeDetail()) + viewModel.editMode = true + viewModel.isDownloaded = false } - viewModel.isDownloaded = viewModel.recipe.storedLocally - } - .refreshable { - viewModel.recipeDetail = await appState.getRecipe( - id: viewModel.recipe.recipe_id, - fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer - ) ?? RecipeDetail.error - viewModel.recipeImage = await appState.getImage( - id: viewModel.recipe.recipe_id, - size: .FULL, - fetchMode: UserSettings.shared.storeImages ? .preferServer : .onlyServer - ) } .onAppear { if UserSettings.shared.keepScreenAwake { @@ -155,21 +181,29 @@ struct RecipeView: View { .onDisappear { 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 + }) + } + } + } } // MARK: - RecipeView ViewModel class ViewModel: ObservableObject { + @Published var observableRecipeDetail: ObservableRecipeDetail = ObservableRecipeDetail() @Published var recipeDetail: RecipeDetail = RecipeDetail.error @Published var recipeImage: UIImage? = nil @Published var editMode: Bool = false @Published var presentShareSheet: Bool = false @Published var showTitle: Bool = false @Published var isDownloaded: Bool? = nil - - @Published var keywords: [String] = [] - @Published var nutrition: [String] = [] + var newRecipe: Bool = false var recipe: Recipe var sharedURL: URL? = nil @@ -179,64 +213,154 @@ struct RecipeView: View { self.recipe = recipe } - func setupView(recipeDetail: RecipeDetail) { - self.keywords = recipeDetail.keywords.components(separatedBy: ",") + init() { + self.newRecipe = true + self.recipe = Recipe( + name: String(localized: "New Recipe"), + keywords: "", + dateCreated: "", + dateModified: "", + imageUrl: "", + imagePlaceholderUrl: "", + recipe_id: 0) } + + func setupView(recipeDetail: RecipeDetail) { + self.recipeDetail = recipeDetail + self.observableRecipeDetail = ObservableRecipeDetail(recipeDetail) + } + } } -// MARK: - Duration Section +// MARK: - Recipe Metadata Section -fileprivate struct RecipeDurationSection: View { - @ObservedObject var viewModel: AppState - @State var recipeDetail: RecipeDetail +struct RecipeMetadataSection: View { + @EnvironmentObject var appState: AppState + @ObservedObject var viewModel: RecipeView.ViewModel + + @State var categories: [String] = [] + @State var keywords: [RecipeKeyword] = [] + @State var presentKeywordPopover: Bool = false 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() - } - */ + VStack(alignment: .leading) { + CategoryPickerView(items: $categories, input: $viewModel.observableRecipeDetail.recipeCategory, titleKey: "Category") - if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Cooking")) - Spacer() - } - Text(time) - .lineLimit(1) - }.padding() - } + SecondaryLabel(text: "Keywords") + .padding() - if let totalTime = recipeDetail.totalTime, let time = DurationComponents.ptToText(totalTime) { - VStack(alignment: .leading) { - HStack { - SecondaryLabel(text: LocalizedStringKey("Total time")) - Spacer() + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(viewModel.observableRecipeDetail.keywords, id: \.self) { keyword in + Text(keyword) } - Text(time) - .lineLimit(1) - }.padding() + } + }.padding(.horizontal) + + Button { + presentKeywordPopover.toggle() + } label: { + Text("Edit keywords") + Image(systemName: "chevron.right") } + .padding(.horizontal) + + } + .task { + categories = appState.categories.map({ category in category.name }) + } + .sheet(isPresented: $presentKeywordPopover) { + KeywordPickerView(title: "Keywords", searchSuggestions: appState.allKeywords, selection: $viewModel.observableRecipeDetail.keywords) } } } + +struct CategoryPickerView: View { + @Binding var items: [String] + @Binding var input: String + @State private var pickerChoice: String = "" + + var titleKey: LocalizedStringKey + + var body: some View { + VStack(alignment: .leading) { + SecondaryLabel(text: "Category") + .padding([.top, .horizontal]) + HStack { + TextField(titleKey, text: $input) + .lineLimit(1) + .textFieldStyle(.roundedBorder) + .padding() + .onSubmit { + pickerChoice = "" + } + + Picker("Select Item", selection: $pickerChoice) { + Text("").tag("") + ForEach(items, id: \.self) { item in + Text(item) + } + } + .pickerStyle(.menu) + .padding() + .onChange(of: pickerChoice) { newValue in + if pickerChoice != "" { + input = newValue + } + } + } + } + .onAppear { + pickerChoice = input + } + } +} + + + +// MARK: - Duration Section + +fileprivate struct RecipeDurationSection: View { + @EnvironmentObject var appState: AppState + @ObservedObject var viewModel: RecipeView.ViewModel + + var body: some View { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) { + DurationView(time: viewModel.observableRecipeDetail.prepTime.displayString, title: LocalizedStringKey("Preparation")) + DurationView(time: viewModel.observableRecipeDetail.cookTime.displayString, title: LocalizedStringKey("Cooking")) + DurationView(time: viewModel.observableRecipeDetail.totalTime.displayString, title: LocalizedStringKey("Total time")) + } + + } +} + +struct DurationView: View { + @State var time: String + @State var title: LocalizedStringKey + + var body: some View { + VStack(alignment: .leading) { + HStack { + SecondaryLabel(text: title) + Spacer() + } + HStack { + Image(systemName: "clock") + .foregroundStyle(.secondary) + Text(time) + .lineLimit(1) + } + } + .padding() + } +} + + + // MARK: - Nutrition Section fileprivate struct RecipeNutritionSection: View { @@ -244,7 +368,7 @@ fileprivate struct RecipeNutritionSection: View { var body: some View { CollapsibleView(titleColor: .secondary, isCollapsed: !UserSettings.shared.expandNutritionSection) { - Group { + VStack(alignment: .leading) { if viewModel.editMode { ForEach(Nutrition.allCases, id: \.self) { nutrition in HStack { @@ -254,29 +378,25 @@ fileprivate struct RecipeNutritionSection: View { .lineLimit(1) } } - } else { - if !viewModel.recipeDetail.nutrition.isEmpty { - VStack(alignment: .leading) { - ForEach(Nutrition.allCases, id: \.self) { nutrition in - if let value = viewModel.recipeDetail.nutrition[nutrition.dictKey] { - HStack(alignment: .top) { - Text(nutrition.localizedDescription) - Text(":") - Text(value) - .multilineTextAlignment(.leading) - } - .padding(4) + } else if !nutritionEmpty() { + VStack(alignment: .leading) { + ForEach(Nutrition.allCases, id: \.self) { nutrition in + if let value = viewModel.observableRecipeDetail.nutrition[nutrition.dictKey], nutrition.dictKey != Nutrition.servingSize.dictKey { + HStack(alignment: .top) { + Text("\(nutrition.localizedDescription): \(value)") + .multilineTextAlignment(.leading) } + .padding(4) } } - } else { - Text(LocalizedStringKey("No nutritional information.")) } + } else { + Text(LocalizedStringKey("No nutritional information.")) } } } title: { HStack { - if let servingSize = viewModel.recipeDetail.nutrition["servingSize"] { + if let servingSize = viewModel.observableRecipeDetail.nutrition["servingSize"] { SecondaryLabel(text: "Nutrition (\(servingSize))") } else { SecondaryLabel(text: LocalizedStringKey("Nutrition")) @@ -289,10 +409,19 @@ fileprivate struct RecipeNutritionSection: View { func binding(for key: String) -> Binding { Binding( - get: { viewModel.recipeDetail.nutrition[key, default: ""] }, - set: { viewModel.recipeDetail.nutrition[key] = $0 } + get: { viewModel.observableRecipeDetail.nutrition[key, default: ""] }, + set: { viewModel.observableRecipeDetail.nutrition[key] = $0 } ) } + + func nutritionEmpty() -> Bool { + for nutrition in Nutrition.allCases { + if let value = viewModel.observableRecipeDetail.nutrition[nutrition.dictKey] { + return false + } + } + return true + } } @@ -300,26 +429,17 @@ fileprivate struct RecipeNutritionSection: View { fileprivate struct RecipeKeywordSection: View { @ObservedObject var viewModel: RecipeView.ViewModel - @State var keywords: [String] = [] + let columns: [GridItem] = [ GridItem(.flexible(minimum: 50, maximum: 200), spacing: 5) ] 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) - } + if !viewModel.observableRecipeDetail.keywords.isEmpty && !viewModel.editMode { + RecipeListSection(list: viewModel.observableRecipeDetail.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")) @@ -334,18 +454,18 @@ fileprivate struct RecipeKeywordSection: View { // MARK: - More Information Section fileprivate struct MoreInformationSection: View { - let recipeDetail: RecipeDetail + @ObservedObject var viewModel: RecipeView.ViewModel 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) { + Text("Created: \(Date.convertISOStringToLocalString(isoDateString: viewModel.recipeDetail.dateCreated) ?? "")") + Text("Last modified: \(Date.convertISOStringToLocalString(isoDateString: viewModel.recipeDetail.dateModified) ?? "")") + if viewModel.observableRecipeDetail.url != "", let url = URL(string: viewModel.observableRecipeDetail.url) { HStack() { Text("URL:") Link(destination: url) { - Text(recipeDetail.url) + Text(viewModel.observableRecipeDetail.url) } } } @@ -402,23 +522,23 @@ fileprivate struct RecipeIngredientSection: View { var body: some View { VStack(alignment: .leading) { HStack { - if viewModel.recipeDetail.recipeYield == 0 { + if viewModel.observableRecipeDetail.recipeYield == 0 { SecondaryLabel(text: LocalizedStringKey("Ingredients")) - } else if viewModel.recipeDetail.recipeYield == 1 { + } else if viewModel.observableRecipeDetail.recipeYield == 1 { SecondaryLabel(text: LocalizedStringKey("Ingredients per serving")) } else { - SecondaryLabel(text: LocalizedStringKey("Ingredients for \(viewModel.recipeDetail.recipeYield) servings")) + SecondaryLabel(text: LocalizedStringKey("Ingredients for \(viewModel.observableRecipeDetail.recipeYield) servings")) } Spacer() Button { withAnimation { - if groceryList.containsRecipe(viewModel.recipeDetail.id) { - groceryList.deleteGroceryRecipe(viewModel.recipeDetail.id) + if groceryList.containsRecipe(viewModel.observableRecipeDetail.id) { + groceryList.deleteGroceryRecipe(viewModel.observableRecipeDetail.id) } else { groceryList.addItems( - viewModel.recipeDetail.recipeIngredient, - toRecipe: viewModel.recipeDetail.id, - recipeName: viewModel.recipeDetail.name + ReorderableItem.items(viewModel.observableRecipeDetail.recipeIngredient), + toRecipe: viewModel.observableRecipeDetail.id, + recipeName: viewModel.observableRecipeDetail.name ) } } @@ -431,13 +551,13 @@ fileprivate struct RecipeIngredientSection: View { } } - EditableStringList(items: $viewModel.recipeDetail.recipeIngredient, editMode: $viewModel.editMode, titleKey: "Ingredient", lineLimit: 0...1, axis: .horizontal) { - ForEach(0.. @State var recipeId: String let addToGroceryListAction: () -> Void @State var isSelected: Bool = false @@ -461,7 +581,7 @@ fileprivate struct IngredientListItem: View { var body: some View { HStack(alignment: .top) { - if groceryList.containsItem(at: recipeId, item: ingredient) { + if groceryList.containsItem(at: recipeId, item: ingredient.item) { if #available(iOS 17.0, *) { Image(systemName: "storefront") .foregroundStyle(Color.green) @@ -476,7 +596,7 @@ fileprivate struct IngredientListItem: View { Image(systemName: "circle") } - Text("\(ingredient)") + Text("\(ingredient.item)") .multilineTextAlignment(.leading) .lineLimit(5) Spacer() @@ -502,8 +622,8 @@ fileprivate struct IngredientListItem: View { .onEnded { gesture in withAnimation { if dragOffset > maxDragDistance * 0.3 { // Swipe threshold - if groceryList.containsItem(at: recipeId, item: ingredient) { - groceryList.deleteItem(ingredient, fromRecipe: recipeId) + if groceryList.containsItem(at: recipeId, item: ingredient.item) { + groceryList.deleteItem(ingredient.item, fromRecipe: recipeId) } else { addToGroceryListAction() } @@ -531,9 +651,9 @@ fileprivate struct RecipeInstructionSection: View { SecondaryLabel(text: LocalizedStringKey("Instructions")) Spacer() } - EditableStringList(items: $viewModel.recipeDetail.recipeInstructions, editMode: $viewModel.editMode, titleKey: "Instruction", lineLimit: 0...15, axis: .vertical) { - ForEach(0.. @State var index: Int @State var isSelected: Bool = false @@ -549,7 +669,7 @@ fileprivate struct RecipeInstructionListItem: View { HStack(alignment: .top) { Text("\(index)") .monospaced() - Text(instruction) + Text(instruction.item) }.padding(4) .foregroundStyle(isSelected ? Color.secondary : Color.primary) .onTapGesture { @@ -571,8 +691,8 @@ fileprivate struct RecipeToolSection: View { SecondaryLabel(text: "Tools") Spacer() } - EditableStringList(items: $viewModel.recipeDetail.tool, editMode: $viewModel.editMode, titleKey: "Tool", lineLimit: 0...1, axis: .horizontal) { - RecipeListSection(list: viewModel.recipeDetail.tool) + EditableStringList(items: $viewModel.observableRecipeDetail.tool, editMode: $viewModel.editMode, titleKey: "Tool", lineLimit: 0...1, axis: .horizontal) { + RecipeListSection(list: ReorderableItem.items(viewModel.observableRecipeDetail.tool)) } }.padding() } @@ -601,31 +721,24 @@ fileprivate struct EditableText: View { fileprivate struct EditableStringList: View { - @Binding var items: [String] + @Binding var items: [ReorderableItem] @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) + ReorderableForEach(items: $items, defaultItem: ReorderableItem(item: "")) { ix, item in + TextField("", text: $items[ix].item, axis: axis) .textFieldStyle(.roundedBorder) .lineLimit(lineLimit) } } - .onAppear { - editableItems = ReorderableItem.list(items: items) - } - .onDisappear { - items = ReorderableItem.items(editableItems) - } + .transition(.slide) } else { content() } diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/BottomClipper.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/BottomClipper.swift new file mode 100644 index 0000000..16cd14b --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/BottomClipper.swift @@ -0,0 +1,17 @@ +// +// BottomClipper.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 27.02.24. +// + +import Foundation +import SwiftUI + +struct BottomClipper: Shape { + let bottom: CGFloat + + func path(in rect: CGRect) -> Path { + Rectangle().path(in: CGRect(x: 0, y: rect.size.height - bottom, width: rect.size.width, height: bottom)) + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/ParallaxHeaderView.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ParallaxHeaderView.swift new file mode 100644 index 0000000..ccd14b1 --- /dev/null +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ParallaxHeaderView.swift @@ -0,0 +1,59 @@ +// +// ParallaxHeaderView.swift +// Nextcloud Cookbook iOS Client +// +// Created by Vincent Meilinger on 26.02.24. +// + +import Foundation +import SwiftUI + + +struct ParallaxHeader: View { + let content: () -> Content + let coordinateSpace: Space + let defaultHeight: CGFloat + + init( + coordinateSpace: Space, + defaultHeight: CGFloat, + @ViewBuilder _ content: @escaping () -> Content + ) { + self.content = content + self.coordinateSpace = coordinateSpace + self.defaultHeight = defaultHeight + } + + var body: some View { + GeometryReader { proxy in + let offset = offset(for: proxy) + let heightModifier = heightModifier(for: proxy) + let blurRadius = min( + heightModifier / 20, + max(10, heightModifier / 20) + ) + content() + .edgesIgnoringSafeArea(.horizontal) + .frame( + width: proxy.size.width, + height: proxy.size.height + heightModifier + ) + .offset(y: offset) + .blur(radius: blurRadius) + }.frame(height: defaultHeight) + } + + + private func offset(for proxy: GeometryProxy) -> CGFloat { + let frame = proxy.frame(in: .named(coordinateSpace)) + if frame.minY < 0 { + return -frame.minY * 0.8 + } + return -frame.minY + } + + private func heightModifier(for proxy: GeometryProxy) -> CGFloat { + let frame = proxy.frame(in: .named(coordinateSpace)) + return max(0, frame.minY) + } +} diff --git a/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift index c9ac4e7..4daa8fd 100644 --- a/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift +++ b/Nextcloud Cookbook iOS Client/Views/ReusableViews/ReorderableForEach.swift @@ -58,8 +58,6 @@ struct ReorderableForEach: View { } label: { Text(allowDeletion ? "Disable deletion" : "Enable deletion") .bold() - .padding(.vertical, 3) - .padding(.horizontal) } .tint(Color.red) Spacer() @@ -68,11 +66,11 @@ struct ReorderableForEach: View { } label: { Image(systemName: "plus") .bold() - .padding(.vertical, 3) + .padding(.vertical, 2) .padding(.horizontal) } .buttonStyle(.borderedProminent) - } + }.padding(.top, 3) }.animation(.default, value: allowDeletion) } } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift index 5c7f6a2..2784ced 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/RecipeTabView.swift @@ -50,12 +50,14 @@ struct RecipeTabView: View { .navigationDestination(isPresented: $viewModel.presentSettingsView) { SettingsView() } + .navigationDestination(isPresented: $viewModel.presentEditView) { + RecipeView(viewModel: RecipeView.ViewModel()) + } } detail: { NavigationStack { if let category = viewModel.selectedCategory { RecipeListView( categoryName: category.name, - viewModel: mainViewModel, showEditView: $viewModel.presentEditView ) .id(category.id) // Workaround: This is needed to update the detail view when the selection changes @@ -63,7 +65,8 @@ struct RecipeTabView: View { } } .tint(.nextcloudBlue) - .sheet(isPresented: $viewModel.presentEditView) { + + /*.sheet(isPresented: $viewModel.presentEditView) { RecipeEditView( viewModel: RecipeEditViewModel( @@ -72,7 +75,7 @@ struct RecipeTabView: View { ), isPresented: $viewModel.presentEditView ) - } + }*/ .task { viewModel.serverConnection = await mainViewModel.checkServerConnection() } diff --git a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift index 3ed91d1..0da525b 100644 --- a/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift +++ b/Nextcloud Cookbook iOS Client/Views/Tabs/SearchTabView.swift @@ -11,7 +11,7 @@ import SimilaritySearchKit struct SearchTabView: View { @EnvironmentObject var viewModel: SearchTabView.ViewModel - @EnvironmentObject var mainViewModel: AppState + @EnvironmentObject var appState: AppState var body: some View { NavigationStack { @@ -27,7 +27,7 @@ struct SearchTabView: View { LazyVStack { ForEach(viewModel.recipesFiltered(), id: \.recipe_id) { recipe in NavigationLink(value: recipe) { - RecipeCardView(viewModel: mainViewModel, recipe: recipe) + RecipeCardView(recipe: recipe) .shadow(radius: 2) } .buttonStyle(.plain) @@ -35,7 +35,7 @@ struct SearchTabView: View { } } .navigationDestination(for: Recipe.self) { recipe in - RecipeView(appState: mainViewModel, viewModel: RecipeView.ViewModel(recipe: recipe)) + RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe)) } .searchable(text: $viewModel.searchText, prompt: "Search recipes/keywords") } @@ -43,11 +43,11 @@ struct SearchTabView: View { } .task { if viewModel.allRecipes.isEmpty { - viewModel.allRecipes = await mainViewModel.getRecipes() + viewModel.allRecipes = await appState.getRecipes() } } .refreshable { - viewModel.allRecipes = await mainViewModel.getRecipes() + viewModel.allRecipes = await appState.getRecipes() } } diff --git a/Nextcloud-Cookbook-iOS-Client-Info.plist b/Nextcloud-Cookbook-iOS-Client-Info.plist index bca21ac..0c67376 100644 --- a/Nextcloud-Cookbook-iOS-Client-Info.plist +++ b/Nextcloud-Cookbook-iOS-Client-Info.plist @@ -1,17 +1,5 @@ - - UTImportedTypeDeclarations - - - UTTypeIcons - - UTTypeIdentifier - com.cookbook-client.uuid - UTTypeTagSpecification - - - - +