Bug fixes and UI polish

This commit is contained in:
VincentMeilinger
2024-03-07 19:09:39 +01:00
parent 92decb773e
commit dbe626d595
15 changed files with 922 additions and 175 deletions

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -82,6 +82,13 @@ class ObservableRecipeDetail: ObservableObject {
nutrition: self.nutrition
)
}
func ingredients(for servings: Int) -> [String] {
for ingredient in recipeIngredient {
// TODO: Parse ingredient strings, adjust them for yield
}
return []
}
}

View File

@@ -21,4 +21,10 @@ extension Color {
public static var background: Color {
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

View File

@@ -49,6 +49,7 @@ class CookbookApiV1: CookbookApi {
let (data, error) = await request.send()
guard let data = data else { return (nil, error) }
print("\n\nRECIPE: ", String(data: data, encoding: .utf8))
return (JSONDecoder.safeDecode(data), nil)
}

View File

@@ -12,6 +12,7 @@ import SwiftUI
struct RecipeListView: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var groceryList: GroceryList
@State var categoryName: String
@State var searchText: String = ""
@Binding var showEditView: Bool
@@ -37,6 +38,8 @@ struct RecipeListView: View {
}
.navigationDestination(for: Recipe.self) { recipe in
RecipeView(isPresented: .constant(true), viewModel: RecipeView.ViewModel(recipe: recipe))
.environmentObject(appState)
.environmentObject(groceryList)
}
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
.toolbar {

View File

@@ -13,7 +13,12 @@ struct RecipeView: View {
@EnvironmentObject var appState: AppState
@Binding var isPresented: Bool
@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 {
case scrollView
@@ -37,7 +42,7 @@ struct RecipeView: View {
.frame(height: 400)
.foregroundStyle(
LinearGradient(
gradient: Gradient(colors: [.nextcloudBlue, .nextcloudDarkBlue]),
gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
@@ -107,6 +112,7 @@ struct RecipeView: View {
.coordinateSpace(name: CoordinateSpaces.scrollView)
.ignoresSafeArea(.container, edges: .top)
.navigationBarTitleDisplayMode(.inline)
.toolbar(.visible, for: .navigationBar)
//.toolbarTitleDisplayMode(.inline)
.navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
.toolbar {
@@ -170,9 +176,7 @@ struct RecipeView: View {
size: .FULL,
fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer
)
if let image = viewModel.recipeImage {
imageHeight = image.size.height < 350 ? image.size.height : 350
}
} else {
// Prepare view for a new recipe
viewModel.setupView(recipeDetail: RecipeDetail())
@@ -278,6 +282,7 @@ struct RecipeView: View {
}
extension RecipeView {
func importRecipe(from url: String) async -> UserAlert? {
let (scrapedRecipe, error) = await appState.importRecipe(url: url)
@@ -300,8 +305,6 @@ extension RecipeView {
}
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()
}
}

View File

@@ -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)
}
}

View File

@@ -43,7 +43,7 @@ struct RecipeIngredientSection: View {
} else {
Image(systemName: "heart.text.square")
}
}
}.disabled(viewModel.editMode)
}
ForEach(0..<viewModel.observableRecipeDetail.recipeIngredient.count, id: \.self) { ix in

View File

@@ -70,17 +70,17 @@ struct RecipeMetadataSection: View {
Button {
presentServingsPopover.toggle()
} label: {
Text("\(viewModel.observableRecipeDetail.recipeYield) serving(s)")
Text("\(viewModel.observableRecipeDetail.recipeYield) Serving(s)")
.lineLimit(1)
}
.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()
.background(RoundedRectangle(cornerRadius: 20).foregroundStyle(Color.white.opacity(0.1)))
.padding()
.padding([.horizontal, .bottom], 5)
.sheet(isPresented: $presentKeywordSheet) {
KeywordPickerView(title: "Keywords", searchSuggestions: appState.allKeywords, selection: $viewModel.observableRecipeDetail.keywords)
}
@@ -88,20 +88,35 @@ struct RecipeMetadataSection: View {
}
fileprivate struct PickerPopoverView<Item: Hashable & CustomStringConvertible, Collection: Sequence>: View where Collection.Element == Item {
@Binding var isPresented: Bool
@Binding var value: Item
@State var items: Collection
var title: LocalizedStringKey
var titleKey: LocalizedStringKey = ""
var body: some View {
HStack {
Picker(selection: $value, label: Text(titleKey)) {
ForEach(Array(items), id: \.self) { item in
Text(item.description).tag(item)
VStack {
HStack {
SecondaryLabel(text: title)
Spacer()
Button {
isPresented = false
} label: {
Text("Done")
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 150, height: 150)
.clipped()
Spacer()
HStack {
Picker(selection: $value, label: Text(titleKey)) {
ForEach(Array(items), id: \.self) { item in
Text(item.description).tag(item)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 150, height: 150)
.clipped()
}
Spacer()
}
.padding()
}

View File

@@ -18,37 +18,47 @@ struct ShareView: View {
@State var sharedURL: URL? = nil
var body: some View {
VStack(alignment: .leading) {
if let url = sharedURL {
ShareLink(item: url, subject: Text("PDF Document")) {
Image(systemName: "doc")
Text("Share as PDF")
NavigationStack {
VStack(alignment: .leading) {
if let url = sharedURL {
ShareLink(item: url, subject: Text("PDF Document")) {
Image(systemName: "doc")
Text("Share as PDF")
}
.foregroundStyle(.primary)
.bold()
.padding()
}
ShareLink(item: exporter.createText(recipe: recipeDetail), subject: Text("Recipe")) {
Image(systemName: "ellipsis.message")
Text("Share as text")
}
.foregroundStyle(.primary)
.bold()
.padding()
/*ShareLink(item: exporter.createJson(recipe: recipeDetail), subject: Text("Recipe")) {
Image(systemName: "doc.badge.gearshape")
Text("Share as JSON")
}
.foregroundStyle(.primary)
.bold()
.padding()
*/
}
ShareLink(item: exporter.createText(recipe: recipeDetail), subject: Text("Recipe")) {
Image(systemName: "ellipsis.message")
Text("Share as text")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
presentShareSheet = false
}
}
}
.foregroundStyle(.primary)
.bold()
.padding()
/*ShareLink(item: exporter.createJson(recipe: recipeDetail), subject: Text("Recipe")) {
Image(systemName: "doc.badge.gearshape")
Text("Share as JSON")
}
.foregroundStyle(.primary)
.bold()
.padding()
*/
}
.task {
self.sharedURL = exporter.createPDF(recipe: recipeDetail, image: recipeImage)
}
}
}

View File

@@ -10,6 +10,8 @@ import SwiftUI
struct RecipeTabView: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var groceryList: GroceryList
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
@EnvironmentObject var mainViewModel: AppState
@@ -49,9 +51,12 @@ struct RecipeTabView: View {
}
.navigationDestination(isPresented: $viewModel.presentSettingsView) {
SettingsView()
.environmentObject(appState)
}
.navigationDestination(isPresented: $viewModel.presentEditView) {
RecipeView(isPresented: $viewModel.presentEditView, viewModel: RecipeView.ViewModel())
.environmentObject(appState)
.environmentObject(groceryList)
}
} detail: {
NavigationStack {
@@ -62,6 +67,7 @@ struct RecipeTabView: View {
)
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
}
}
}
.tint(.nextcloudBlue)