Bug fixes and UI polish
This commit is contained in:
@@ -65,6 +65,7 @@
|
|||||||
A9CA6CEF2B4C086100F78AB5 /* RecipeExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */; };
|
A9CA6CEF2B4C086100F78AB5 /* RecipeExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */; };
|
||||||
A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */ = {isa = PBXBuildFile; productRef = A9CA6CF52B4C63F200F78AB5 /* TPPDF */; };
|
A9CA6CF62B4C63F200F78AB5 /* TPPDF in Frameworks */ = {isa = PBXBuildFile; productRef = A9CA6CF52B4C63F200F78AB5 /* TPPDF */; };
|
||||||
A9D89AB02B4FE97800F49D92 /* TimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D89AAF2B4FE97800F49D92 /* TimerView.swift */; };
|
A9D89AB02B4FE97800F49D92 /* TimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D89AAF2B4FE97800F49D92 /* TimerView.swift */; };
|
||||||
|
A9D8F9052B99F3E5009BACAE /* RecipeImportSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8F9042B99F3E4009BACAE /* RecipeImportSection.swift */; };
|
||||||
A9FA2AB62B5079B200A43702 /* alarm_sound_0.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */; };
|
A9FA2AB62B5079B200A43702 /* alarm_sound_0.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@
|
|||||||
A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableRecipeDetail.swift; sourceTree = "<group>"; };
|
A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableRecipeDetail.swift; sourceTree = "<group>"; };
|
||||||
A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeExporter.swift; sourceTree = "<group>"; };
|
A9CA6CEE2B4C086100F78AB5 /* RecipeExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeExporter.swift; sourceTree = "<group>"; };
|
||||||
A9D89AAF2B4FE97800F49D92 /* TimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerView.swift; sourceTree = "<group>"; };
|
A9D89AAF2B4FE97800F49D92 /* TimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerView.swift; sourceTree = "<group>"; };
|
||||||
|
A9D8F9042B99F3E4009BACAE /* RecipeImportSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeImportSection.swift; sourceTree = "<group>"; };
|
||||||
A9DA25D42B82096B0061FC2B /* Nextcloud-Cookbook-iOS-Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Nextcloud-Cookbook-iOS-Client-Info.plist"; sourceTree = SOURCE_ROOT; };
|
A9DA25D42B82096B0061FC2B /* Nextcloud-Cookbook-iOS-Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Nextcloud-Cookbook-iOS-Client-Info.plist"; sourceTree = SOURCE_ROOT; };
|
||||||
A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = alarm_sound_0.mp3; sourceTree = "<group>"; };
|
A9FA2AB52B5079B200A43702 /* alarm_sound_0.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = alarm_sound_0.mp3; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -356,6 +358,7 @@
|
|||||||
A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */,
|
A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */,
|
||||||
A97506142B920DF200E86029 /* RecipeGenericViews.swift */,
|
A97506142B920DF200E86029 /* RecipeGenericViews.swift */,
|
||||||
A97506202B92104700E86029 /* RecipeMetadataSection.swift */,
|
A97506202B92104700E86029 /* RecipeMetadataSection.swift */,
|
||||||
|
A9D8F9042B99F3E4009BACAE /* RecipeImportSection.swift */,
|
||||||
);
|
);
|
||||||
path = RecipeViewSections;
|
path = RecipeViewSections;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -573,6 +576,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A9D8F9052B99F3E5009BACAE /* RecipeImportSection.swift in Sources */,
|
||||||
A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */,
|
A9BBB38E2B8E44B3002DA7FF /* BottomClipper.swift in Sources */,
|
||||||
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */,
|
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */,
|
||||||
A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */,
|
A97B4D352B80B82A00EC1A88 /* ShareView.swift in Sources */,
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x29",
|
||||||
|
"green" : "0x1B",
|
||||||
|
"red" : "0x00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xFF",
|
||||||
|
"green" : "0xE2",
|
||||||
|
"red" : "0xAD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x29",
|
||||||
|
"green" : "0x1B",
|
||||||
|
"red" : "0x00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x52",
|
||||||
|
"green" : "0x35",
|
||||||
|
"red" : "0x00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xFF",
|
||||||
|
"green" : "0xF8",
|
||||||
|
"red" : "0xEB"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x52",
|
||||||
|
"green" : "0x35",
|
||||||
|
"red" : "0x00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,13 @@ class ObservableRecipeDetail: ObservableObject {
|
|||||||
nutrition: self.nutrition
|
nutrition: self.nutrition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ingredients(for servings: Int) -> [String] {
|
||||||
|
for ingredient in recipeIngredient {
|
||||||
|
// TODO: Parse ingredient strings, adjust them for yield
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,10 @@ extension Color {
|
|||||||
public static var background: Color {
|
public static var background: Color {
|
||||||
return Color(UIColor.systemBackground)
|
return Color(UIColor.systemBackground)
|
||||||
}
|
}
|
||||||
|
public static var ncGradientDark: Color {
|
||||||
|
return Color("ncgradientdarkblue")
|
||||||
|
}
|
||||||
|
public static var ncGradientLight: Color {
|
||||||
|
return Color("ncgradientlightblue")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@ class CookbookApiV1: CookbookApi {
|
|||||||
|
|
||||||
let (data, error) = await request.send()
|
let (data, error) = await request.send()
|
||||||
guard let data = data else { return (nil, error) }
|
guard let data = data else { return (nil, error) }
|
||||||
|
print("\n\nRECIPE: ", String(data: data, encoding: .utf8))
|
||||||
return (JSONDecoder.safeDecode(data), nil)
|
return (JSONDecoder.safeDecode(data), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct RecipeListView: View {
|
struct RecipeListView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var groceryList: GroceryList
|
||||||
@State var categoryName: String
|
@State var categoryName: String
|
||||||
@State var searchText: String = ""
|
@State var searchText: String = ""
|
||||||
@Binding var showEditView: Bool
|
@Binding var showEditView: Bool
|
||||||
@@ -37,6 +38,8 @@ struct RecipeListView: View {
|
|||||||
}
|
}
|
||||||
.navigationDestination(for: Recipe.self) { recipe in
|
.navigationDestination(for: Recipe.self) { recipe in
|
||||||
RecipeView(isPresented: .constant(true), viewModel: RecipeView.ViewModel(recipe: recipe))
|
RecipeView(isPresented: .constant(true), viewModel: RecipeView.ViewModel(recipe: recipe))
|
||||||
|
.environmentObject(appState)
|
||||||
|
.environmentObject(groceryList)
|
||||||
}
|
}
|
||||||
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
|
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ struct RecipeView: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@Binding var isPresented: Bool
|
@Binding var isPresented: Bool
|
||||||
@StateObject var viewModel: ViewModel
|
@StateObject var viewModel: ViewModel
|
||||||
@State var imageHeight: CGFloat = 350
|
var imageHeight: CGFloat {
|
||||||
|
if let image = viewModel.recipeImage {
|
||||||
|
return image.size.height < 350 ? image.size.height : 350
|
||||||
|
}
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
private enum CoordinateSpaces {
|
private enum CoordinateSpaces {
|
||||||
case scrollView
|
case scrollView
|
||||||
@@ -37,7 +42,7 @@ struct RecipeView: View {
|
|||||||
.frame(height: 400)
|
.frame(height: 400)
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
gradient: Gradient(colors: [.nextcloudBlue, .nextcloudDarkBlue]),
|
gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
@@ -107,6 +112,7 @@ struct RecipeView: View {
|
|||||||
.coordinateSpace(name: CoordinateSpaces.scrollView)
|
.coordinateSpace(name: CoordinateSpaces.scrollView)
|
||||||
.ignoresSafeArea(.container, edges: .top)
|
.ignoresSafeArea(.container, edges: .top)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar(.visible, for: .navigationBar)
|
||||||
//.toolbarTitleDisplayMode(.inline)
|
//.toolbarTitleDisplayMode(.inline)
|
||||||
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
|
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -170,9 +176,7 @@ struct RecipeView: View {
|
|||||||
size: .FULL,
|
size: .FULL,
|
||||||
fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer
|
fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer
|
||||||
)
|
)
|
||||||
if let image = viewModel.recipeImage {
|
|
||||||
imageHeight = image.size.height < 350 ? image.size.height : 350
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Prepare view for a new recipe
|
// Prepare view for a new recipe
|
||||||
viewModel.setupView(recipeDetail: RecipeDetail())
|
viewModel.setupView(recipeDetail: RecipeDetail())
|
||||||
@@ -278,6 +282,7 @@ struct RecipeView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extension RecipeView {
|
extension RecipeView {
|
||||||
func importRecipe(from url: String) async -> UserAlert? {
|
func importRecipe(from url: String) async -> UserAlert? {
|
||||||
let (scrapedRecipe, error) = await appState.importRecipe(url: url)
|
let (scrapedRecipe, error) = await appState.importRecipe(url: url)
|
||||||
@@ -300,8 +305,6 @@ extension RecipeView {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -446,42 +449,3 @@ struct RecipeViewToolBar: ToolbarContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Recipe Import Section
|
|
||||||
|
|
||||||
fileprivate struct RecipeImportSection: View {
|
|
||||||
@ObservedObject var viewModel: RecipeView.ViewModel
|
|
||||||
var importRecipe: (String) async -> UserAlert?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
SecondaryLabel(text: "Import Recipe")
|
|
||||||
|
|
||||||
Text(LocalizedStringKey("Paste the url of a recipe you would like to import in the above, and we will try to fill in the fields for you. This feature does not work with every website. If your favourite website is not supported, feel free to reach out for help. You can find the contact details in the app settings."))
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
|
|
||||||
|
|
||||||
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importUrl)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
.padding(.top, 5)
|
|
||||||
Button {
|
|
||||||
Task {
|
|
||||||
if let res = await importRecipe(viewModel.importUrl) {
|
|
||||||
viewModel.presentAlert(
|
|
||||||
RecipeAlert.CUSTOM(
|
|
||||||
title: res.localizedTitle,
|
|
||||||
description: res.localizedDescription
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Text(LocalizedStringKey("Import"))
|
|
||||||
}
|
|
||||||
.buttonStyle(.bordered)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// RecipeImportSection.swift
|
||||||
|
// Nextcloud Cookbook iOS Client
|
||||||
|
//
|
||||||
|
// Created by Vincent Meilinger on 07.03.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - RecipeView Import Section
|
||||||
|
|
||||||
|
struct RecipeImportSection: View {
|
||||||
|
@ObservedObject var viewModel: RecipeView.ViewModel
|
||||||
|
var importRecipe: (String) async -> UserAlert?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
SecondaryLabel(text: "Import Recipe")
|
||||||
|
|
||||||
|
Text(LocalizedStringKey("Paste the url of a recipe you would like to import in the above, and we will try to fill in the fields for you. This feature does not work with every website. If your favourite website is not supported, feel free to reach out for help. You can find the contact details in the app settings."))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
|
||||||
|
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importUrl)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.padding(.top, 5)
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
if let res = await importRecipe(viewModel.importUrl) {
|
||||||
|
viewModel.presentAlert(
|
||||||
|
RecipeAlert.CUSTOM(
|
||||||
|
title: res.localizedTitle,
|
||||||
|
description: res.localizedDescription
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text(LocalizedStringKey("Import"))
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
||||||
|
.padding(5)
|
||||||
|
.padding(.top, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ struct RecipeIngredientSection: View {
|
|||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart.text.square")
|
Image(systemName: "heart.text.square")
|
||||||
}
|
}
|
||||||
}
|
}.disabled(viewModel.editMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in
|
||||||
|
|||||||
@@ -70,17 +70,17 @@ struct RecipeMetadataSection: View {
|
|||||||
Button {
|
Button {
|
||||||
presentServingsPopover.toggle()
|
presentServingsPopover.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Text("\(viewModel.observableRecipeDetail.recipeYield) serving(s)")
|
Text("\(viewModel.observableRecipeDetail.recipeYield) Serving(s)")
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.popover(isPresented: $presentServingsPopover) {
|
.popover(isPresented: $presentServingsPopover) {
|
||||||
PickerPopoverView(value: $viewModel.observableRecipeDetail.recipeYield, items: 0..<99, titleKey: "Servings")
|
PickerPopoverView(isPresented: $presentServingsPopover, value: $viewModel.observableRecipeDetail.recipeYield, items: 0..<99, title: "Servings", titleKey: "Servings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
|
||||||
.padding()
|
.padding([.horizontal, .bottom], 5)
|
||||||
.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)
|
||||||
}
|
}
|
||||||
@@ -88,11 +88,24 @@ struct RecipeMetadataSection: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, Collection: Sequence>: View where Collection.Element == Item {
|
fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, Collection: Sequence>: View where Collection.Element == Item {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
@Binding var value: Item
|
@Binding var value: Item
|
||||||
@State var items: Collection
|
@State var items: Collection
|
||||||
|
var title: LocalizedStringKey
|
||||||
var titleKey: LocalizedStringKey = ""
|
var titleKey: LocalizedStringKey = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
SecondaryLabel(text: title)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
isPresented = false
|
||||||
|
} label: {
|
||||||
|
Text("Done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
HStack {
|
HStack {
|
||||||
Picker(selection: $value, label: Text(titleKey)) {
|
Picker(selection: $value, label: Text(titleKey)) {
|
||||||
ForEach(Array(items), id: \.self) { item in
|
ForEach(Array(items), id: \.self) { item in
|
||||||
@@ -103,6 +116,8 @@ fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, C
|
|||||||
.frame(width: 150, height: 150)
|
.frame(width: 150, height: 150)
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct ShareView: View {
|
|||||||
@State var sharedURL: URL? = nil
|
@State var sharedURL: URL? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if let url = sharedURL {
|
if let url = sharedURL {
|
||||||
ShareLink(item: url, subject: Text("PDF Document")) {
|
ShareLink(item: url, subject: Text("PDF Document")) {
|
||||||
@@ -46,9 +47,18 @@ struct ShareView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
Button("Done") {
|
||||||
|
presentShareSheet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.task {
|
.task {
|
||||||
self.sharedURL = exporter.createPDF(recipe: recipeDetail, image: recipeImage)
|
self.sharedURL = exporter.createPDF(recipe: recipeDetail, image: recipeImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import SwiftUI
|
|||||||
|
|
||||||
|
|
||||||
struct RecipeTabView: View {
|
struct RecipeTabView: View {
|
||||||
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var groceryList: GroceryList
|
||||||
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
|
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
|
||||||
@EnvironmentObject var mainViewModel: AppState
|
@EnvironmentObject var mainViewModel: AppState
|
||||||
|
|
||||||
@@ -49,9 +51,12 @@ struct RecipeTabView: View {
|
|||||||
}
|
}
|
||||||
.navigationDestination(isPresented: $viewModel.presentSettingsView) {
|
.navigationDestination(isPresented: $viewModel.presentSettingsView) {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
|
.environmentObject(appState)
|
||||||
}
|
}
|
||||||
.navigationDestination(isPresented: $viewModel.presentEditView) {
|
.navigationDestination(isPresented: $viewModel.presentEditView) {
|
||||||
RecipeView(isPresented: $viewModel.presentEditView, viewModel: RecipeView.ViewModel())
|
RecipeView(isPresented: $viewModel.presentEditView, viewModel: RecipeView.ViewModel())
|
||||||
|
.environmentObject(appState)
|
||||||
|
.environmentObject(groceryList)
|
||||||
}
|
}
|
||||||
} detail: {
|
} detail: {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -62,6 +67,7 @@ struct RecipeTabView: View {
|
|||||||
)
|
)
|
||||||
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
|
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tint(.nextcloudBlue)
|
.tint(.nextcloudBlue)
|
||||||
|
|||||||
Reference in New Issue
Block a user