Recipe edit UI polish
This commit is contained in:
@@ -46,7 +46,6 @@
|
|||||||
A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D792B25C66600A3469E /* OnboardingView.swift */; };
|
A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D792B25C66600A3469E /* OnboardingView.swift */; };
|
||||||
A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */; };
|
A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */; };
|
||||||
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */; };
|
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */; };
|
||||||
A95364672B7E89F1001018B0 /* ReorderableForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95364662B7E89F1001018B0 /* ReorderableForEach.swift */; };
|
|
||||||
A97506132B920D9F00E86029 /* RecipeDurationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506122B920D9F00E86029 /* RecipeDurationSection.swift */; };
|
A97506132B920D9F00E86029 /* RecipeDurationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506122B920D9F00E86029 /* RecipeDurationSection.swift */; };
|
||||||
A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506142B920DF200E86029 /* RecipeGenericViews.swift */; };
|
A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506142B920DF200E86029 /* RecipeGenericViews.swift */; };
|
||||||
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506182B920EC200E86029 /* RecipeIngredientSection.swift */; };
|
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506182B920EC200E86029 /* RecipeIngredientSection.swift */; };
|
||||||
@@ -59,7 +58,7 @@
|
|||||||
A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977D0E12B60034E009783A9 /* GroceryListTabView.swift */; };
|
A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977D0E12B60034E009783A9 /* GroceryListTabView.swift */; };
|
||||||
A97B4D322B80B3E900EC1A88 /* RecipeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */; };
|
A97B4D322B80B3E900EC1A88 /* RecipeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */; };
|
||||||
A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D342B80B82A00EC1A88 /* ShareView.swift */; };
|
A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97B4D342B80B82A00EC1A88 /* ShareView.swift */; };
|
||||||
A99DC7BC2B6411A7000118AA /* SimilaritySearchKit in Frameworks */ = {isa = PBXBuildFile; productRef = A99DC7BB2B6411A7000118AA /* SimilaritySearchKit */; };
|
A9A43AE12B963150003D95CA /* SwipeActions in Frameworks */ = {isa = PBXBuildFile; productRef = A9A43AE02B963150003D95CA /* SwipeActions */; };
|
||||||
A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */; };
|
A9BBB38C2B8D3B0C002DA7FF /* ParallaxHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */; };
|
||||||
A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */; };
|
A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */; };
|
||||||
A9BBB3902B91BE31002DA7FF /* ObservableRecipeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */; };
|
A9BBB3902B91BE31002DA7FF /* ObservableRecipeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */; };
|
||||||
@@ -129,7 +128,6 @@
|
|||||||
A7FB0D792B25C66600A3469E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
A7FB0D792B25C66600A3469E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||||
A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenLoginView.swift; sourceTree = "<group>"; };
|
A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenLoginView.swift; sourceTree = "<group>"; };
|
||||||
A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2LoginView.swift; sourceTree = "<group>"; };
|
A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2LoginView.swift; sourceTree = "<group>"; };
|
||||||
A95364662B7E89F1001018B0 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
|
|
||||||
A97506122B920D9F00E86029 /* RecipeDurationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDurationSection.swift; sourceTree = "<group>"; };
|
A97506122B920D9F00E86029 /* RecipeDurationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDurationSection.swift; sourceTree = "<group>"; };
|
||||||
A97506142B920DF200E86029 /* RecipeGenericViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeGenericViews.swift; sourceTree = "<group>"; };
|
A97506142B920DF200E86029 /* RecipeGenericViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeGenericViews.swift; sourceTree = "<group>"; };
|
||||||
A97506182B920EC200E86029 /* RecipeIngredientSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeIngredientSection.swift; sourceTree = "<group>"; };
|
A97506182B920EC200E86029 /* RecipeIngredientSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeIngredientSection.swift; sourceTree = "<group>"; };
|
||||||
@@ -157,7 +155,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */,
|
A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */,
|
||||||
A99DC7BC2B6411A7000118AA /* SimilaritySearchKit in Frameworks */,
|
A9A43AE12B963150003D95CA /* SwipeActions in Frameworks */,
|
||||||
A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */,
|
A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -409,7 +407,6 @@
|
|||||||
children = (
|
children = (
|
||||||
A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */,
|
A9BBB38B2B8D3B0C002DA7FF /* ParallaxHeaderView.swift */,
|
||||||
A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */,
|
A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */,
|
||||||
A95364662B7E89F1001018B0 /* ReorderableForEach.swift */,
|
|
||||||
A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */,
|
A9BBB38D2B8E44B3002DA7FF /* BottomClipper.swift */,
|
||||||
);
|
);
|
||||||
path = ReusableViews;
|
path = ReusableViews;
|
||||||
@@ -450,7 +447,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
A74D33BD2AF82AAE00D06555 /* SwiftSoup */,
|
A74D33BD2AF82AAE00D06555 /* SwiftSoup */,
|
||||||
A9CA6CF52B4C63F200F78AB5 /* TPPDF */,
|
A9CA6CF52B4C63F200F78AB5 /* TPPDF */,
|
||||||
A99DC7BB2B6411A7000118AA /* SimilaritySearchKit */,
|
A9A43AE02B963150003D95CA /* SwipeActions */,
|
||||||
);
|
);
|
||||||
productName = "Nextcloud Cookbook iOS Client";
|
productName = "Nextcloud Cookbook iOS Client";
|
||||||
productReference = A701717E2AA8E71900064C43 /* Nextcloud Cookbook iOS Client.app */;
|
productReference = A701717E2AA8E71900064C43 /* Nextcloud Cookbook iOS Client.app */;
|
||||||
@@ -530,7 +527,7 @@
|
|||||||
packageReferences = (
|
packageReferences = (
|
||||||
A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||||
A9CA6CF42B4C63F200F78AB5 /* XCRemoteSwiftPackageReference "TPPDF" */,
|
A9CA6CF42B4C63F200F78AB5 /* XCRemoteSwiftPackageReference "TPPDF" */,
|
||||||
A99DC7BA2B6411A7000118AA /* XCRemoteSwiftPackageReference "similarity-search-kit" */,
|
A9A43ADF2B963150003D95CA /* XCRemoteSwiftPackageReference "SwipeActions" */,
|
||||||
);
|
);
|
||||||
productRefGroup = A701717F2AA8E71900064C43 /* Products */;
|
productRefGroup = A701717F2AA8E71900064C43 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -594,7 +591,6 @@
|
|||||||
A70171C42AB4A31200064C43 /* DataStore.swift in Sources */,
|
A70171C42AB4A31200064C43 /* DataStore.swift in Sources */,
|
||||||
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */,
|
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */,
|
||||||
A975061D2B920FCC00E86029 /* RecipeInstructionSection.swift in Sources */,
|
A975061D2B920FCC00E86029 /* RecipeInstructionSection.swift in Sources */,
|
||||||
A95364672B7E89F1001018B0 /* ReorderableForEach.swift in Sources */,
|
|
||||||
A787B0782B2B1E6400C2DF1B /* DateExtension.swift in Sources */,
|
A787B0782B2B1E6400C2DF1B /* DateExtension.swift in Sources */,
|
||||||
A79AA8E92B062DD1007D25F2 /* CookbookApiV1.swift in Sources */,
|
A79AA8E92B062DD1007D25F2 /* CookbookApiV1.swift in Sources */,
|
||||||
A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */,
|
A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */,
|
||||||
@@ -1009,12 +1005,12 @@
|
|||||||
minimumVersion = 2.6.1;
|
minimumVersion = 2.6.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
A99DC7BA2B6411A7000118AA /* XCRemoteSwiftPackageReference "similarity-search-kit" */ = {
|
A9A43ADF2B963150003D95CA /* XCRemoteSwiftPackageReference "SwipeActions" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/ZachNagengast/similarity-search-kit";
|
repositoryURL = "https://github.com/aheze/SwipeActions";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 0.0.13;
|
minimumVersion = 1.1.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
A9CA6CF42B4C63F200F78AB5 /* XCRemoteSwiftPackageReference "TPPDF" */ = {
|
A9CA6CF42B4C63F200F78AB5 /* XCRemoteSwiftPackageReference "TPPDF" */ = {
|
||||||
@@ -1033,10 +1029,10 @@
|
|||||||
package = A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
package = A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||||
productName = SwiftSoup;
|
productName = SwiftSoup;
|
||||||
};
|
};
|
||||||
A99DC7BB2B6411A7000118AA /* SimilaritySearchKit */ = {
|
A9A43AE02B963150003D95CA /* SwipeActions */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = A99DC7BA2B6411A7000118AA /* XCRemoteSwiftPackageReference "similarity-search-kit" */;
|
package = A9A43ADF2B963150003D95CA /* XCRemoteSwiftPackageReference "SwipeActions" */;
|
||||||
productName = SimilaritySearchKit;
|
productName = SwipeActions;
|
||||||
};
|
};
|
||||||
A9CA6CF52B4C63F200F78AB5 /* TPPDF */ = {
|
A9CA6CF52B4C63F200F78AB5 /* TPPDF */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict/>
|
||||||
|
</plist>
|
||||||
@@ -1,14 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
|
||||||
"identity" : "similarity-search-kit",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/ZachNagengast/similarity-search-kit",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "ddc8e458d0e826b2fe5dbce6f6eac96a8935e8eb",
|
|
||||||
"version" : "0.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "swiftsoup",
|
"identity" : "swiftsoup",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -18,6 +9,15 @@
|
|||||||
"version" : "2.6.1"
|
"version" : "2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swipeactions",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/aheze/SwipeActions",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "41e6f6dce02d8cfa164f8c5461a41340850ca3ab",
|
||||||
|
"version" : "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "tppdf",
|
"identity" : "tppdf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>BuildLocationStyle</key>
|
||||||
|
<string>UseAppPreferences</string>
|
||||||
|
<key>CustomBuildLocationType</key>
|
||||||
|
<string>RelativeToDerivedData</string>
|
||||||
|
<key>DerivedDataLocationStyle</key>
|
||||||
|
<string>Default</string>
|
||||||
|
<key>ShowSharedSchemesAutomaticallyEnabled</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -89,26 +89,5 @@ class DataStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimilarityIndex loading and saving
|
|
||||||
import SimilaritySearchKit
|
|
||||||
extension DataStore {
|
|
||||||
func loadIndex() async -> [IndexItem]? {
|
|
||||||
do {
|
|
||||||
let indexItems = try await SimilarityIndex().loadIndex(fromDirectory: Self.fileURL(appending: "similarity_index"))
|
|
||||||
return indexItems
|
|
||||||
} catch {
|
|
||||||
print("Unable to load SimilarityIndex")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveIndex(_ index: SimilarityIndex) {
|
|
||||||
do {
|
|
||||||
try index.saveIndex(toDirectory: Self.fileURL(appending: "similarity_index"))
|
|
||||||
} catch {
|
|
||||||
print("Unable to save SimilarityIndex")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ class ObservableRecipeDetail: ObservableObject {
|
|||||||
@Published var url: String
|
@Published var url: String
|
||||||
@Published var recipeYield: Int
|
@Published var recipeYield: Int
|
||||||
@Published var recipeCategory: String
|
@Published var recipeCategory: String
|
||||||
@Published var tool: [ReorderableItem<String>]
|
@Published var tool: [String]
|
||||||
@Published var recipeIngredient: [ReorderableItem<String>]
|
@Published var recipeIngredient: [String]
|
||||||
@Published var recipeInstructions: [ReorderableItem<String>]
|
@Published var recipeInstructions: [String]
|
||||||
@Published var nutrition: [String:String]
|
@Published var nutrition: [String:String]
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -55,9 +55,9 @@ class ObservableRecipeDetail: ObservableObject {
|
|||||||
url = recipeDetail.url
|
url = recipeDetail.url
|
||||||
recipeYield = recipeDetail.recipeYield
|
recipeYield = recipeDetail.recipeYield
|
||||||
recipeCategory = recipeDetail.recipeCategory
|
recipeCategory = recipeDetail.recipeCategory
|
||||||
tool = ReorderableItem.list(items: recipeDetail.tool)
|
tool = recipeDetail.tool
|
||||||
recipeIngredient = ReorderableItem.list(items: recipeDetail.recipeIngredient)
|
recipeIngredient = recipeDetail.recipeIngredient
|
||||||
recipeInstructions = ReorderableItem.list(items: recipeDetail.recipeInstructions)
|
recipeInstructions = recipeDetail.recipeInstructions
|
||||||
nutrition = recipeDetail.nutrition
|
nutrition = recipeDetail.nutrition
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +76,9 @@ class ObservableRecipeDetail: ObservableObject {
|
|||||||
url: self.url,
|
url: self.url,
|
||||||
recipeYield: self.recipeYield,
|
recipeYield: self.recipeYield,
|
||||||
recipeCategory: self.recipeCategory,
|
recipeCategory: self.recipeCategory,
|
||||||
tool: ReorderableItem.items(self.tool),
|
tool: self.tool,
|
||||||
recipeIngredient: ReorderableItem.items(self.recipeIngredient),
|
recipeIngredient: self.recipeIngredient,
|
||||||
recipeInstructions: ReorderableItem.items(self.recipeInstructions),
|
recipeInstructions: self.recipeInstructions,
|
||||||
nutrition: self.nutrition
|
nutrition: self.nutrition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ extension Color {
|
|||||||
public static var nextcloudBlue: Color {
|
public static var nextcloudBlue: Color {
|
||||||
return Color("ncblue")
|
return Color("ncblue")
|
||||||
}
|
}
|
||||||
|
public static var nextcloudDarkBlue: Color {
|
||||||
|
return Color("ncdarkblue")
|
||||||
|
}
|
||||||
public static var backgroundHighlight: Color {
|
public static var backgroundHighlight: Color {
|
||||||
return Color("backgroundHighlight")
|
return Color("backgroundHighlight")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"%lld." : {
|
"%lld." : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -407,6 +408,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Add" : {
|
"Add" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -427,6 +429,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Add cooking steps for fellow chefs to follow." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Add groceries to this list by either using the button next to an ingredient list in a recipe, or by swiping right on individual ingredients of a recipe." : {
|
"Add groceries to this list by either using the button next to an ingredient list in a recipe, or by swiping right on individual ingredients of a recipe." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -634,6 +639,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Category: %@" : {
|
"Category: %@" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -816,6 +822,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Cooking duration:" : {
|
"Cooking duration:" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -1056,9 +1063,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Disable deletion" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Done" : {
|
"Done" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1180,9 +1184,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Enable deletion" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Error" : {
|
"Error" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1525,6 +1526,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Import recipe from a website" : {
|
"Import recipe from a website" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -1755,6 +1757,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"List your tools here. 🍴" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Log out" : {
|
"Log out" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1982,6 +1987,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"New recipe" : {
|
"New recipe" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2359,6 +2365,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Preparation duration:" : {
|
"Preparation duration:" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2564,9 +2571,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Select Item" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Select Keywords" : {
|
"Select Keywords" : {
|
||||||
|
|
||||||
@@ -2600,6 +2604,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"Servings:" : {
|
"Servings:" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2733,6 +2738,9 @@
|
|||||||
},
|
},
|
||||||
"Sodium content" : {
|
"Sodium content" : {
|
||||||
"comment" : "Sodium content"
|
"comment" : "Sodium content"
|
||||||
|
},
|
||||||
|
"Start by adding your first ingredient! 🥬" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Store recipe images locally" : {
|
"Store recipe images locally" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -3026,6 +3034,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Title" : {
|
"Title" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -3071,11 +3080,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Total" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Total duration:" : {
|
"Total duration:" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -3236,6 +3243,7 @@
|
|||||||
"comment" : "Unsaturated fat content"
|
"comment" : "Unsaturated fat content"
|
||||||
},
|
},
|
||||||
"Upload" : {
|
"Upload" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
|||||||
@@ -137,4 +137,10 @@ class DurationComponents: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func + (lhs: DurationComponents, rhs: DurationComponents) -> DurationComponents {
|
||||||
|
let totalSeconds = lhs.toSeconds() + rhs.toSeconds()
|
||||||
|
let result = DurationComponents()
|
||||||
|
result.fromSeconds(Int(totalSeconds))
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SimilaritySearchKit
|
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@StateObject var appState = AppState()
|
@StateObject var appState = AppState()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
struct CategoryPickerViewOld: View {
|
struct CategoryPickerViewOld: View {
|
||||||
@State var title: String
|
@State var title: String
|
||||||
@State var searchSuggestions: [String]
|
@State var searchSuggestions: [String]
|
||||||
@@ -61,3 +61,4 @@ struct CategoryPickerViewOld: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwiftUI
|
|||||||
import PhotosUI
|
import PhotosUI
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
struct RecipeEditView: View {
|
struct RecipeEditView: View {
|
||||||
@ObservedObject var viewModel: RecipeEditViewModel
|
@ObservedObject var viewModel: RecipeEditViewModel
|
||||||
@Binding var isPresented: Bool
|
@Binding var isPresented: Bool
|
||||||
@@ -279,4 +279,4 @@ fileprivate struct DurationPicker: View {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ struct RecipeView: View {
|
|||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(maxHeight: imageHeight + 200)
|
.frame(maxHeight: imageHeight + 200)
|
||||||
.clipped()
|
.clipped()
|
||||||
|
} else {
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 400)
|
||||||
|
.foregroundStyle(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [.white, .nextcloudBlue]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +51,11 @@ struct RecipeView: View {
|
|||||||
if viewModel.editMode {
|
if viewModel.editMode {
|
||||||
RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe)
|
RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.editMode {
|
||||||
|
RecipeMetadataSection(viewModel: viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
EditableText(text: $viewModel.observableRecipeDetail.name, editMode: $viewModel.editMode, titleKey: "Recipe Name")
|
EditableText(text: $viewModel.observableRecipeDetail.name, editMode: $viewModel.editMode, titleKey: "Recipe Name")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
@@ -66,10 +81,6 @@ struct RecipeView: View {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
if viewModel.editMode {
|
|
||||||
RecipeMetadataSection(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
||||||
if(!viewModel.observableRecipeDetail.recipeIngredient.isEmpty || viewModel.editMode) {
|
if(!viewModel.observableRecipeDetail.recipeIngredient.isEmpty || viewModel.editMode) {
|
||||||
RecipeIngredientSection(viewModel: viewModel)
|
RecipeIngredientSection(viewModel: viewModel)
|
||||||
@@ -83,15 +94,14 @@ struct RecipeView: View {
|
|||||||
RecipeNutritionSection(viewModel: viewModel)
|
RecipeNutritionSection(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
|
||||||
if !viewModel.editMode {
|
if !viewModel.editMode {
|
||||||
|
Divider()
|
||||||
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
|
||||||
RecipeKeywordSection(viewModel: viewModel)
|
RecipeKeywordSection(viewModel: viewModel)
|
||||||
}
|
|
||||||
MoreInformationSection(viewModel: viewModel)
|
MoreInformationSection(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.padding(.horizontal, 5)
|
.padding(.horizontal, 5)
|
||||||
.background(Rectangle().foregroundStyle(.background).shadow(radius: 5).mask(Rectangle().padding(.top, -20)))
|
.background(Rectangle().foregroundStyle(.background).shadow(radius: 5).mask(Rectangle().padding(.top, -20)))
|
||||||
}
|
}
|
||||||
@@ -99,6 +109,7 @@ struct RecipeView: View {
|
|||||||
.coordinateSpace(name: CoordinateSpaces.scrollView)
|
.coordinateSpace(name: CoordinateSpaces.scrollView)
|
||||||
.ignoresSafeArea(.container, edges: .top)
|
.ignoresSafeArea(.container, edges: .top)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
//.toolbarTitleDisplayMode(.inline)
|
||||||
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
|
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if viewModel.editMode {
|
if viewModel.editMode {
|
||||||
@@ -112,7 +123,6 @@ struct RecipeView: View {
|
|||||||
// Upload Button
|
// Upload Button
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
// TODO: POST edited recipe
|
|
||||||
Task {
|
Task {
|
||||||
if viewModel.newRecipe {
|
if viewModel.newRecipe {
|
||||||
if let res = await uploadNewRecipe() {
|
if let res = await uploadNewRecipe() {
|
||||||
@@ -191,6 +201,7 @@ struct RecipeView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "ellipsis.circle")
|
Image(systemName: "ellipsis.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,6 +210,36 @@ struct RecipeView: View {
|
|||||||
recipeImage: viewModel.recipeImage,
|
recipeImage: viewModel.recipeImage,
|
||||||
presentShareSheet: $viewModel.presentShareSheet)
|
presentShareSheet: $viewModel.presentShareSheet)
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $viewModel.presentInstructionEditView) {
|
||||||
|
EditableListView(
|
||||||
|
isPresented: $viewModel.presentInstructionEditView,
|
||||||
|
items: $viewModel.observableRecipeDetail.recipeInstructions,
|
||||||
|
title: "Instructions",
|
||||||
|
emptyListText: "Add cooking steps for fellow chefs to follow.",
|
||||||
|
titleKey: "Instruction",
|
||||||
|
lineLimit: 0...10,
|
||||||
|
axis: .vertical)
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $viewModel.presentIngredientEditView) {
|
||||||
|
EditableListView(
|
||||||
|
isPresented: $viewModel.presentIngredientEditView,
|
||||||
|
items: $viewModel.observableRecipeDetail.recipeIngredient,
|
||||||
|
title: "Ingredients",
|
||||||
|
emptyListText: "Start by adding your first ingredient! 🥬",
|
||||||
|
titleKey: "Ingredient",
|
||||||
|
lineLimit: 0...1,
|
||||||
|
axis: .horizontal)
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $viewModel.presentToolEditView) {
|
||||||
|
EditableListView(
|
||||||
|
isPresented: $viewModel.presentToolEditView,
|
||||||
|
items: $viewModel.observableRecipeDetail.tool,
|
||||||
|
title: "Tools",
|
||||||
|
emptyListText: "List your tools here. 🍴",
|
||||||
|
titleKey: "Tool",
|
||||||
|
lineLimit: 0...1,
|
||||||
|
axis: .horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
.task {
|
.task {
|
||||||
// Load recipe detail
|
// Load recipe detail
|
||||||
@@ -282,10 +323,15 @@ struct RecipeView: View {
|
|||||||
@Published var recipeDetail: RecipeDetail = RecipeDetail.error
|
@Published var recipeDetail: RecipeDetail = RecipeDetail.error
|
||||||
@Published var recipeImage: UIImage? = nil
|
@Published var recipeImage: UIImage? = nil
|
||||||
@Published var editMode: Bool = false
|
@Published var editMode: Bool = false
|
||||||
@Published var presentShareSheet: Bool = false
|
|
||||||
@Published var showTitle: Bool = false
|
@Published var showTitle: Bool = false
|
||||||
@Published var isDownloaded: Bool? = nil
|
@Published var isDownloaded: Bool? = nil
|
||||||
@Published var importUrl: String = ""
|
@Published var importUrl: String = ""
|
||||||
|
|
||||||
|
@Published var presentShareSheet: Bool = false
|
||||||
|
@Published var presentInstructionEditView: Bool = false
|
||||||
|
@Published var presentIngredientEditView: Bool = false
|
||||||
|
@Published var presentToolEditView: Bool = false
|
||||||
|
|
||||||
var recipe: Recipe
|
var recipe: Recipe
|
||||||
var sharedURL: URL? = nil
|
var sharedURL: URL? = nil
|
||||||
var newRecipe: Bool = false
|
var newRecipe: Bool = false
|
||||||
@@ -408,9 +454,10 @@ fileprivate struct RecipeImportSection: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack {
|
|
||||||
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importUrl)
|
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importUrl)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.padding(.top, 5)
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
if let res = await importRecipe(viewModel.importUrl) {
|
if let res = await importRecipe(viewModel.importUrl) {
|
||||||
@@ -425,9 +472,10 @@ fileprivate struct RecipeImportSection: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Text(LocalizedStringKey("Import"))
|
Text(LocalizedStringKey("Import"))
|
||||||
}
|
}
|
||||||
}.padding(.top, 5)
|
.buttonStyle(.bordered)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Rectangle().foregroundStyle(Color.white.opacity(0.1)))
|
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,30 +15,21 @@ struct RecipeDurationSection: View {
|
|||||||
@State var presentPopover: Bool = false
|
@State var presentPopover: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !viewModel.editMode {
|
VStack(alignment: .leading) {
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) {
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) {
|
||||||
DurationView(time: viewModel.observableRecipeDetail.prepTime, title: LocalizedStringKey("Preparation"))
|
DurationView(time: viewModel.observableRecipeDetail.prepTime, title: LocalizedStringKey("Preparation"))
|
||||||
DurationView(time: viewModel.observableRecipeDetail.cookTime, title: LocalizedStringKey("Cooking"))
|
DurationView(time: viewModel.observableRecipeDetail.cookTime, title: LocalizedStringKey("Cooking"))
|
||||||
DurationView(time: viewModel.observableRecipeDetail.totalTime, title: LocalizedStringKey("Total time"))
|
DurationView(time: viewModel.observableRecipeDetail.totalTime, title: LocalizedStringKey("Total time"))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200, maximum: .infinity), alignment: .leading)]) {
|
|
||||||
Button {
|
Button {
|
||||||
presentPopover.toggle()
|
presentPopover.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
DurationView(time: viewModel.observableRecipeDetail.prepTime, title: LocalizedStringKey("Preparation"))
|
Text("Edit")
|
||||||
}
|
|
||||||
Button {
|
|
||||||
presentPopover.toggle()
|
|
||||||
} label: {
|
|
||||||
DurationView(time: viewModel.observableRecipeDetail.cookTime, title: LocalizedStringKey("Cooking"))
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
presentPopover.toggle()
|
|
||||||
} label: {
|
|
||||||
DurationView(time: viewModel.observableRecipeDetail.totalTime, title: LocalizedStringKey("Total time"))
|
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.padding(.top, 5)
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
.popover(isPresented: $presentPopover) {
|
.popover(isPresented: $presentPopover) {
|
||||||
EditableDurationView(
|
EditableDurationView(
|
||||||
prepTime: viewModel.observableRecipeDetail.prepTime,
|
prepTime: viewModel.observableRecipeDetail.prepTime,
|
||||||
@@ -47,9 +38,6 @@ struct RecipeDurationSection: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate struct DurationView: View {
|
fileprivate struct DurationView: View {
|
||||||
@@ -64,36 +52,64 @@ fileprivate struct DurationView: View {
|
|||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "clock")
|
Image(systemName: "clock")
|
||||||
|
.bold()
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text(time.displayString)
|
Text(time.displayString)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate struct EditableDurationView: View {
|
fileprivate struct EditableDurationView: View {
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@ObservedObject var prepTime: DurationComponents
|
@ObservedObject var prepTime: DurationComponents
|
||||||
@ObservedObject var cookTime: DurationComponents
|
@ObservedObject var cookTime: DurationComponents
|
||||||
@ObservedObject var totalTime: DurationComponents
|
@ObservedObject var totalTime: DurationComponents
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .center) {
|
||||||
HStack {
|
HStack {
|
||||||
SecondaryLabel(text: "Preparation")
|
SecondaryLabel(text: "Preparation")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
Button("Done") {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TimePickerView(selectedHour: $prepTime.hourComponent, selectedMinute: $prepTime.minuteComponent)
|
TimePickerView(selectedHour: $prepTime.hourComponent, selectedMinute: $prepTime.minuteComponent)
|
||||||
|
|
||||||
|
HStack {
|
||||||
SecondaryLabel(text: "Cooking")
|
SecondaryLabel(text: "Cooking")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
TimePickerView(selectedHour: $cookTime.hourComponent, selectedMinute: $cookTime.minuteComponent)
|
TimePickerView(selectedHour: $cookTime.hourComponent, selectedMinute: $cookTime.minuteComponent)
|
||||||
SecondaryLabel(text: "Total")
|
|
||||||
|
HStack {
|
||||||
|
SecondaryLabel(text: "Total time")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
TimePickerView(selectedHour: $totalTime.hourComponent, selectedMinute: $totalTime.minuteComponent)
|
TimePickerView(selectedHour: $totalTime.hourComponent, selectedMinute: $totalTime.minuteComponent)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.onChange(of: prepTime.hourComponent) { _ in updateTotalTime() }
|
||||||
|
.onChange(of: prepTime.minuteComponent) { _ in updateTotalTime() }
|
||||||
|
.onChange(of: cookTime.hourComponent) { _ in updateTotalTime() }
|
||||||
|
.onChange(of: cookTime.minuteComponent) { _ in updateTotalTime() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateTotalTime() {
|
||||||
|
var hourComponent = prepTime.hourComponent + cookTime.hourComponent
|
||||||
|
var minuteComponent = prepTime.minuteComponent + cookTime.minuteComponent
|
||||||
|
// Handle potential overflow from minutes to hours
|
||||||
|
if minuteComponent >= 60 {
|
||||||
|
hourComponent += minuteComponent / 60
|
||||||
|
minuteComponent %= 60
|
||||||
|
}
|
||||||
|
totalTime.hourComponent = hourComponent
|
||||||
|
totalTime.minuteComponent = minuteComponent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,27 +59,93 @@ struct EditableText: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct EditableStringList<Content: View>: View {
|
struct EditableListView: View {
|
||||||
@Binding var items: [ReorderableItem<String>]
|
@Binding var isPresented: Bool
|
||||||
@Binding var editMode: Bool
|
@Binding var items: [String]
|
||||||
|
@State var title: LocalizedStringKey
|
||||||
|
@State var emptyListText: LocalizedStringKey
|
||||||
@State var titleKey: LocalizedStringKey = ""
|
@State var titleKey: LocalizedStringKey = ""
|
||||||
@State var lineLimit: ClosedRange<Int> = 0...50
|
@State var lineLimit: ClosedRange<Int> = 0...50
|
||||||
@State var axis: Axis = .vertical
|
@State var axis: Axis = .vertical
|
||||||
|
|
||||||
var content: () -> Content
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if editMode {
|
NavigationView {
|
||||||
VStack {
|
ZStack {
|
||||||
ReorderableForEach(items: $items, defaultItem: ReorderableItem(item: "")) { ix, item in
|
List {
|
||||||
TextField("", text: $items[ix].item, axis: axis)
|
if items.isEmpty {
|
||||||
.textFieldStyle(.roundedBorder)
|
Text(emptyListText)
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach(items.indices, id: \.self) { ix in
|
||||||
|
TextField(titleKey, text: $items[ix], axis: axis)
|
||||||
.lineLimit(lineLimit)
|
.lineLimit(lineLimit)
|
||||||
}
|
}
|
||||||
|
.onDelete(perform: deleteItem)
|
||||||
|
.onMove(perform: moveItem)
|
||||||
}
|
}
|
||||||
.transition(.slide)
|
VStack {
|
||||||
} else {
|
Spacer()
|
||||||
content()
|
|
||||||
|
Button {
|
||||||
|
addItem()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.bold()
|
||||||
|
.padding()
|
||||||
|
.background(Circle().fill(Color.nextcloudBlue))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitle(title, displayMode: .inline)
|
||||||
|
.navigationBarItems(
|
||||||
|
trailing: Button(action: { isPresented = false }) {
|
||||||
|
Text("Done")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.environment(\.editMode, .constant(.active)) // Bind edit mode to your state variable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addItem() {
|
||||||
|
withAnimation {
|
||||||
|
items.append("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteItem(at offsets: IndexSet) {
|
||||||
|
withAnimation {
|
||||||
|
items.remove(atOffsets: offsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func moveItem(from source: IndexSet, to destination: Int) {
|
||||||
|
withAnimation {
|
||||||
|
items.move(fromOffsets: source, toOffset: destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct EditableListView_Previews: PreviewProvider {
|
||||||
|
// Sample keywords for preview
|
||||||
|
@State static var sampleList: [String] = [
|
||||||
|
/*"3 Eggs",
|
||||||
|
"1 kg Potatos",
|
||||||
|
"3 g Sugar",
|
||||||
|
"1 ml Milk",
|
||||||
|
"Salt, Pepper"*/
|
||||||
|
]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
Color.white
|
||||||
|
.sheet(isPresented: .constant(true), content: {
|
||||||
|
EditableListView(isPresented: .constant(true), items: $sampleList, title: "Ingredient", emptyListText: "Add cooking steps for fellow chefs to follow.")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct RecipeIngredientSection: View {
|
|||||||
groceryList.deleteGroceryRecipe(viewModel.observableRecipeDetail.id)
|
groceryList.deleteGroceryRecipe(viewModel.observableRecipeDetail.id)
|
||||||
} else {
|
} else {
|
||||||
groceryList.addItems(
|
groceryList.addItems(
|
||||||
ReorderableItem.items(viewModel.observableRecipeDetail.recipeIngredient),
|
viewModel.observableRecipeDetail.recipeIngredient,
|
||||||
toRecipe: viewModel.observableRecipeDetail.id,
|
toRecipe: viewModel.observableRecipeDetail.id,
|
||||||
recipeName: viewModel.observableRecipeDetail.name
|
recipeName: viewModel.observableRecipeDetail.name
|
||||||
)
|
)
|
||||||
@@ -46,17 +46,23 @@ struct RecipeIngredientSection: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableStringList(items: $viewModel.observableRecipeDetail.recipeIngredient, editMode: $viewModel.editMode, titleKey: "Ingredient", lineLimit: 0...1, axis: .horizontal) {
|
|
||||||
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
||||||
IngredientListItem(ingredient: viewModel.observableRecipeDetail.recipeIngredient[ix], recipeId: viewModel.observableRecipeDetail.id) {
|
IngredientListItem(ingredient: $viewModel.observableRecipeDetail.recipeIngredient[ix], recipeId: viewModel.observableRecipeDetail.id) {
|
||||||
groceryList.addItem(
|
groceryList.addItem(
|
||||||
viewModel.observableRecipeDetail.recipeIngredient[ix].item,
|
viewModel.observableRecipeDetail.recipeIngredient[ix],
|
||||||
toRecipe: viewModel.observableRecipeDetail.id,
|
toRecipe: viewModel.observableRecipeDetail.id,
|
||||||
recipeName: viewModel.observableRecipeDetail.name
|
recipeName: viewModel.observableRecipeDetail.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(4)
|
.padding(4)
|
||||||
}
|
}
|
||||||
|
if viewModel.editMode {
|
||||||
|
Button {
|
||||||
|
viewModel.presentIngredientEditView.toggle()
|
||||||
|
} label: {
|
||||||
|
Text("Edit")
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
}
|
}
|
||||||
}.padding()
|
}.padding()
|
||||||
}
|
}
|
||||||
@@ -66,7 +72,7 @@ struct RecipeIngredientSection: View {
|
|||||||
|
|
||||||
fileprivate struct IngredientListItem: View {
|
fileprivate struct IngredientListItem: View {
|
||||||
@EnvironmentObject var groceryList: GroceryList
|
@EnvironmentObject var groceryList: GroceryList
|
||||||
@State var ingredient: ReorderableItem<String>
|
@Binding var ingredient: String
|
||||||
@State var recipeId: String
|
@State var recipeId: String
|
||||||
let addToGroceryListAction: () -> Void
|
let addToGroceryListAction: () -> Void
|
||||||
@State var isSelected: Bool = false
|
@State var isSelected: Bool = false
|
||||||
@@ -78,7 +84,7 @@ fileprivate struct IngredientListItem: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
if groceryList.containsItem(at: recipeId, item: ingredient.item) {
|
if groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
Image(systemName: "storefront")
|
Image(systemName: "storefront")
|
||||||
.foregroundStyle(Color.green)
|
.foregroundStyle(Color.green)
|
||||||
@@ -93,7 +99,7 @@ fileprivate struct IngredientListItem: View {
|
|||||||
Image(systemName: "circle")
|
Image(systemName: "circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("\(ingredient.item)")
|
Text("\(ingredient)")
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.lineLimit(5)
|
.lineLimit(5)
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -119,15 +125,13 @@ fileprivate struct IngredientListItem: View {
|
|||||||
.onEnded { gesture in
|
.onEnded { gesture in
|
||||||
withAnimation {
|
withAnimation {
|
||||||
if dragOffset > maxDragDistance * 0.3 { // Swipe threshold
|
if dragOffset > maxDragDistance * 0.3 { // Swipe threshold
|
||||||
if groceryList.containsItem(at: recipeId, item: ingredient.item) {
|
if groceryList.containsItem(at: recipeId, item: ingredient) {
|
||||||
groceryList.deleteItem(ingredient.item, fromRecipe: recipeId)
|
groceryList.deleteItem(ingredient, fromRecipe: recipeId)
|
||||||
} else {
|
} else {
|
||||||
addToGroceryListAction()
|
addToGroceryListAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// Animate back to original position
|
// Animate back to original position
|
||||||
|
|
||||||
self.dragOffset = 0
|
self.dragOffset = 0
|
||||||
self.animationStartOffset = 0
|
self.animationStartOffset = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,27 @@ struct RecipeInstructionSection: View {
|
|||||||
SecondaryLabel(text: LocalizedStringKey("Instructions"))
|
SecondaryLabel(text: LocalizedStringKey("Instructions"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
EditableStringList(items: $viewModel.observableRecipeDetail.recipeInstructions, editMode: $viewModel.editMode, titleKey: "Instruction", lineLimit: 0...15, axis: .vertical) {
|
ForEach(viewModel.observableRecipeDetail.recipeInstructions.indices, id: \.self) { ix in
|
||||||
ForEach(0..<viewModel.observableRecipeDetail.recipeInstructions.count, id: \.self) { ix in
|
RecipeInstructionListItem(instruction: $viewModel.observableRecipeDetail.recipeInstructions[ix], index: ix+1)
|
||||||
RecipeInstructionListItem(instruction: viewModel.observableRecipeDetail.recipeInstructions[ix], index: ix+1)
|
}
|
||||||
|
if viewModel.editMode {
|
||||||
|
Button {
|
||||||
|
viewModel.presentInstructionEditView.toggle()
|
||||||
|
} label: {
|
||||||
|
Text("Edit")
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding()
|
.padding()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fileprivate struct RecipeInstructionListItem: View {
|
fileprivate struct RecipeInstructionListItem: View {
|
||||||
@State var instruction: ReorderableItem<String>
|
@Binding var instruction: String
|
||||||
@State var index: Int
|
@State var index: Int
|
||||||
@State var isSelected: Bool = false
|
@State var isSelected: Bool = false
|
||||||
|
|
||||||
@@ -39,7 +47,7 @@ fileprivate struct RecipeInstructionListItem: View {
|
|||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
Text("\(index)")
|
Text("\(index)")
|
||||||
.monospaced()
|
.monospaced()
|
||||||
Text(instruction.item)
|
Text(instruction)
|
||||||
}.padding(4)
|
}.padding(4)
|
||||||
.foregroundStyle(isSelected ? Color.secondary : Color.primary)
|
.foregroundStyle(isSelected ? Color.secondary : Color.primary)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ struct RecipeMetadataSection: View {
|
|||||||
appState.categories.map({ category in category.name })
|
appState.categories.map({ category in category.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@State var presentKeywordSheet: Bool = false
|
@State var presentKeywordSheet: Bool = false
|
||||||
@State var presentServingsPopover: Bool = false
|
@State var presentServingsPopover: Bool = false
|
||||||
@State var presentCategoryPopover: Bool = false
|
@State var presentCategoryPopover: Bool = false
|
||||||
@@ -27,7 +26,6 @@ struct RecipeMetadataSection: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
// Category
|
// Category
|
||||||
//CategoryPickerView(items: $categories, input: $viewModel.observableRecipeDetail.recipeCategory, titleKey: "Category")
|
|
||||||
SecondaryLabel(text: "Category")
|
SecondaryLabel(text: "Category")
|
||||||
HStack {
|
HStack {
|
||||||
TextField("Category", text: $viewModel.observableRecipeDetail.recipeCategory)
|
TextField("Category", text: $viewModel.observableRecipeDetail.recipeCategory)
|
||||||
@@ -42,6 +40,7 @@ struct RecipeMetadataSection: View {
|
|||||||
}
|
}
|
||||||
.pickerStyle(.menu)
|
.pickerStyle(.menu)
|
||||||
}
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
SecondaryLabel(text: "Keywords")
|
SecondaryLabel(text: "Keywords")
|
||||||
@@ -51,6 +50,8 @@ struct RecipeMetadataSection: View {
|
|||||||
HStack {
|
HStack {
|
||||||
ForEach(viewModel.observableRecipeDetail.keywords, id: \.self) { keyword in
|
ForEach(viewModel.observableRecipeDetail.keywords, id: \.self) { keyword in
|
||||||
Text(keyword)
|
Text(keyword)
|
||||||
|
.padding(5)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.primary.opacity(0.1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,7 @@ struct RecipeMetadataSection: View {
|
|||||||
Text("Select Keywords")
|
Text("Select Keywords")
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
}
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
// Servings / Yield
|
// Servings / Yield
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@@ -77,7 +79,8 @@ struct RecipeMetadataSection: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Rectangle().foregroundStyle(Color.white.opacity(0.1)))
|
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
||||||
|
.padding()
|
||||||
.sheet(isPresented: $presentKeywordSheet) {
|
.sheet(isPresented: $presentKeywordSheet) {
|
||||||
KeywordPickerView(title: "Keywords", searchSuggestions: appState.allKeywords, selection: $viewModel.observableRecipeDetail.keywords)
|
KeywordPickerView(title: "Keywords", searchSuggestions: appState.allKeywords, selection: $viewModel.observableRecipeDetail.keywords)
|
||||||
}
|
}
|
||||||
@@ -104,47 +107,6 @@ fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate 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: - RecipeView More Information Section
|
// MARK: - RecipeView More Information Section
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,19 @@ struct RecipeToolSection: View {
|
|||||||
SecondaryLabel(text: "Tools")
|
SecondaryLabel(text: "Tools")
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
EditableStringList(items: $viewModel.observableRecipeDetail.tool, editMode: $viewModel.editMode, titleKey: "Tool", lineLimit: 0...1, axis: .horizontal) {
|
|
||||||
RecipeListSection(list: ReorderableItem.items(viewModel.observableRecipeDetail.tool))
|
RecipeListSection(list: viewModel.observableRecipeDetail.tool)
|
||||||
|
|
||||||
|
if viewModel.editMode {
|
||||||
|
Button {
|
||||||
|
viewModel.presentToolEditView.toggle()
|
||||||
|
} label: {
|
||||||
|
Text("Edit")
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
}
|
}
|
||||||
}.padding()
|
}.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
//
|
|
||||||
// ReorderableForEach.swift
|
|
||||||
// Nextcloud Cookbook iOS Client
|
|
||||||
//
|
|
||||||
// Created by Vincent Meilinger on 15.02.24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
|
|
||||||
struct ReorderableForEach<Item: Any, Content: View>: View {
|
|
||||||
@Binding var items: [ReorderableItem<Item>]
|
|
||||||
var defaultItem: ReorderableItem<Item>
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
.tint(Color.red)
|
|
||||||
Spacer()
|
|
||||||
Button {
|
|
||||||
items.append(defaultItem)
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "plus")
|
|
||||||
.bold()
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
}.padding(.top, 3)
|
|
||||||
}.animation(.default, value: allowDeletion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ReorderableItem<Item: Any>: 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<Item: Any>: DropDelegate {
|
|
||||||
let targetId: UUID
|
|
||||||
@Binding var sourceId : UUID?
|
|
||||||
@Binding var items: [ReorderableItem<Item>]
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -65,17 +65,6 @@ struct RecipeTabView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tint(.nextcloudBlue)
|
.tint(.nextcloudBlue)
|
||||||
|
|
||||||
/*.sheet(isPresented: $viewModel.presentEditView) {
|
|
||||||
RecipeEditView(
|
|
||||||
viewModel:
|
|
||||||
RecipeEditViewModel(
|
|
||||||
mainViewModel: mainViewModel,
|
|
||||||
uploadNew: true
|
|
||||||
),
|
|
||||||
isPresented: $viewModel.presentEditView
|
|
||||||
)
|
|
||||||
}*/
|
|
||||||
.task {
|
.task {
|
||||||
viewModel.serverConnection = await mainViewModel.checkServerConnection()
|
viewModel.serverConnection = await mainViewModel.checkServerConnection()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SimilaritySearchKit
|
|
||||||
|
|
||||||
struct SearchTabView: View {
|
struct SearchTabView: View {
|
||||||
@EnvironmentObject var viewModel: SearchTabView.ViewModel
|
@EnvironmentObject var viewModel: SearchTabView.ViewModel
|
||||||
@@ -56,8 +55,7 @@ struct SearchTabView: View {
|
|||||||
@Published var searchText: String = ""
|
@Published var searchText: String = ""
|
||||||
@Published var searchMode: SearchMode = .name
|
@Published var searchMode: SearchMode = .name
|
||||||
|
|
||||||
var similarityIndex: SimilarityIndex? = nil
|
|
||||||
var similaritySearchResults: [SearchResult] = []
|
|
||||||
|
|
||||||
enum SearchMode: String, CaseIterable {
|
enum SearchMode: String, CaseIterable {
|
||||||
case name = "Name & Keywords", ingredient = "Ingredients"
|
case name = "Name & Keywords", ingredient = "Ingredients"
|
||||||
|
|||||||
Reference in New Issue
Block a user