Updated README.md with a roadmap of upcoming features

This commit is contained in:
VincentMeilinger
2024-03-10 18:10:36 +01:00
parent 31a796e8a4
commit da30c11e50
5 changed files with 24 additions and 373 deletions

View File

@@ -26,7 +26,6 @@
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171CC2AB501B100064C43 /* SettingsView.swift */; }; A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171CC2AB501B100064C43 /* SettingsView.swift */; };
A703226A2ABAF49800D7C4ED /* JSONCoderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70322692ABAF49800D7C4ED /* JSONCoderExtension.swift */; }; A703226A2ABAF49800D7C4ED /* JSONCoderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70322692ABAF49800D7C4ED /* JSONCoderExtension.swift */; };
A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */; }; A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */; };
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */; };
A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = A74D33BD2AF82AAE00D06555 /* SwiftSoup */; }; A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = A74D33BD2AF82AAE00D06555 /* SwiftSoup */; };
A74D33C32AFCD1C300D06555 /* RecipeScraper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74D33C22AFCD1C300D06555 /* RecipeScraper.swift */; }; A74D33C32AFCD1C300D06555 /* RecipeScraper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74D33C22AFCD1C300D06555 /* RecipeScraper.swift */; };
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; }; A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; };
@@ -42,7 +41,6 @@
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; }; A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; };
A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */; }; A7CD3FD22B2C546A00D764AD /* CollapsibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */; };
A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */; }; A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */; };
A7F3F8EA2ACC221C0076C227 /* CategoryPickerViewOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */; };
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 */; };
@@ -110,7 +108,6 @@
A70171CC2AB501B100064C43 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; A70171CC2AB501B100064C43 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A70322692ABAF49800D7C4ED /* JSONCoderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCoderExtension.swift; sourceTree = "<group>"; }; A70322692ABAF49800D7C4ED /* JSONCoderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCoderExtension.swift; sourceTree = "<group>"; };
A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; }; A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeEditView.swift; sourceTree = "<group>"; };
A74D33C22AFCD1C300D06555 /* RecipeScraper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeScraper.swift; sourceTree = "<group>"; }; A74D33C22AFCD1C300D06555 /* RecipeScraper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeScraper.swift; sourceTree = "<group>"; };
A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; }; A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; };
A76B8A702AE002AE00096CEC /* Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; }; A76B8A702AE002AE00096CEC /* Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; };
@@ -125,7 +122,6 @@
A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; }; A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleView.swift; sourceTree = "<group>"; }; A7CD3FD12B2C546A00D764AD /* CollapsibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleView.swift; sourceTree = "<group>"; };
A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeKeywordSection.swift; sourceTree = "<group>"; }; A7F3F8E72ACBFC760076C227 /* RecipeKeywordSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeKeywordSection.swift; sourceTree = "<group>"; };
A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerViewOld.swift; sourceTree = "<group>"; };
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>"; };
@@ -276,7 +272,6 @@
A977D0DC2B6002DA009783A9 /* Tabs */, A977D0DC2B6002DA009783A9 /* Tabs */,
A7FB0D782B25C65200A3469E /* Onboarding */, A7FB0D782B25C65200A3469E /* Onboarding */,
A9C3BE502B630E3900562C79 /* Recipes */, A9C3BE502B630E3900562C79 /* Recipes */,
A9C3BE512B630E8300562C79 /* RecipeEditing */,
A9C3BE522B630F1300562C79 /* ReusableViews */, A9C3BE522B630F1300562C79 /* ReusableViews */,
); );
path = Views; path = Views;
@@ -396,15 +391,6 @@
path = Recipes; path = Recipes;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A9C3BE512B630E8300562C79 /* RecipeEditing */ = {
isa = PBXGroup;
children = (
A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */,
A7F3F8E92ACC221C0076C227 /* CategoryPickerViewOld.swift */,
);
path = RecipeEditing;
sourceTree = "<group>";
};
A9C3BE522B630F1300562C79 /* ReusableViews */ = { A9C3BE522B630F1300562C79 /* ReusableViews */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -586,7 +572,6 @@
A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */, A79AA8E22AFF8C14007D25F2 /* RecipeEditViewModel.swift in Sources */,
A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */, A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */,
A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */, A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */,
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */,
A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */, A977D0E22B60034E009783A9 /* GroceryListTabView.swift in Sources */,
A70171B12AB211DF00064C43 /* NetworkError.swift in Sources */, A70171B12AB211DF00064C43 /* NetworkError.swift in Sources */,
A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */, A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */,
@@ -608,7 +593,6 @@
A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */, A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */,
A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */, A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */,
A70171C02AB498A900064C43 /* RecipeView.swift in Sources */, A70171C02AB498A900064C43 /* RecipeView.swift in Sources */,
A7F3F8EA2ACC221C0076C227 /* CategoryPickerViewOld.swift in Sources */,
A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */, A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */,
A975061B2B920F9F00E86029 /* RecipeNutritionSection.swift in Sources */, A975061B2B920F9F00E86029 /* RecipeNutritionSection.swift in Sources */,
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */, A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */,
@@ -809,7 +793,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.8.3; MARKETING_VERSION = 1.9.1;
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client"; PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
@@ -853,7 +837,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.8.3; MARKETING_VERSION = 1.9.1;
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client"; PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;

View File

@@ -1,64 +0,0 @@
//
// CategoryPickerView.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 03.10.23.
//
import Foundation
import SwiftUI
/*
struct CategoryPickerViewOld: View {
@State var title: String
@State var searchSuggestions: [String]
@Binding var selection: String
@State var searchText: String = ""
var body: some View {
VStack {
TextField(title, text: $searchText)
.textFieldStyle(.roundedBorder)
.padding()
List {
if searchText != "" {
HStack {
if selection.contains(searchText) {
Image(systemName: "checkmark.circle.fill")
}
Text(searchText)
Spacer()
}
.padding()
.onTapGesture {
selection = searchText
}
}
ForEach(suggestionsFiltered(), id: \.self) { suggestion in
HStack {
if selection.contains(suggestion) {
Image(systemName: "checkmark.circle.fill")
}
Text(suggestion)
}
.padding()
.onTapGesture {
selection = suggestion
}
}
}
Spacer()
}
.navigationTitle(title)
}
func suggestionsFiltered() -> [String] {
guard searchText != "" else { return searchSuggestions }
return searchSuggestions.filter { suggestion in
suggestion.lowercased().contains(searchText.lowercased())
}
}
}
*/

View File

@@ -1,282 +0,0 @@
//
// RecipeEditView.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 29.09.23.
//
import Foundation
import SwiftUI
import PhotosUI
/*
struct RecipeEditView: View {
@ObservedObject var viewModel: RecipeEditViewModel
@Binding var isPresented: Bool
@State var presentAlert = false
@State var alertType: UserAlert = RecipeAlert.GENERIC
@State var alertAction: @MainActor () async -> () = { }
var body: some View {
NavigationStack {
VStack {
HStack {
Button() {
isPresented = false
} label: {
Text("Cancel")
.bold()
}
if !viewModel.uploadNew {
Menu {
Button {
print("Delete recipe.")
alertType = RecipeAlert.CONFIRM_DELETE
alertAction = {
if let res = await viewModel.deleteRecipe() {
alertType = res
alertAction = { }
presentAlert = true
} else {
self.dismissEditView()
}
}
presentAlert = true
} label: {
Image(systemName: "trash")
.foregroundStyle(.red)
Text("Delete recipe")
.foregroundStyle(.red)
}
} label: {
Image(systemName: "ellipsis.circle")
.font(.title3)
.padding()
}
}
Spacer()
Button() {
Task {
if viewModel.uploadNew {
if let res = await viewModel.uploadNewRecipe() {
alertType = res
presentAlert = true
} else {
dismissEditView()
}
} else {
if let res = await viewModel.uploadEditedRecipe() {
alertType = res
presentAlert = true
} else {
dismissEditView()
}
}
}
} label: {
Text("Upload")
.bold()
}
}.padding()
HStack {
Text(viewModel.recipe.name == "" ? String(localized: "New recipe") : viewModel.recipe.name)
.font(.title)
.bold()
.padding()
Spacer()
}
Form {
if viewModel.showImportSection {
Section {
TextField(LocalizedStringKey("URL (e.g. example.com/recipe)"), text: $viewModel.importURL)
Button {
Task {
if let res = await viewModel.importRecipe() {
alertType = RecipeAlert.CUSTOM(
title: res.localizedTitle,
description: res.localizedDescription
)
alertAction = { }
presentAlert = true
}
}
} label: {
Text(LocalizedStringKey("Import"))
}
} header: {
Text(LocalizedStringKey("Import Recipe"))
} footer: {
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."))
}
} else {
Section {
Button() {
withAnimation{
viewModel.showImportSection = true
}
} label: {
Text("Import recipe from a website")
}
}
}
TextField("Title", text: $viewModel.recipe.name)
Section {
TextEditor(text: $viewModel.recipe.description)
} header: {
Text("Description")
}
Section() {
NavigationLink(viewModel.recipe.recipeCategory == "" ? "Category" : "Category: \(viewModel.recipe.recipeCategory)") {
CategoryPickerViewOld(
title: "Category",
searchSuggestions: viewModel.mainViewModel.categories.map({ category in
category.name == "*" ? "Other" : category.name
}),
selection: $viewModel.recipe.recipeCategory)
}
NavigationLink("Keywords") {
KeywordPickerView(
title: "Keywords",
searchSuggestions: viewModel.keywordSuggestions,
selection: $viewModel.keywords
)
}
} footer: {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(viewModel.keywords, id: \.self) { keyword in
Text(keyword)
}
}
}
}
Section() {
Picker("Servings:", selection: $viewModel.recipe.recipeYield) {
ForEach(0..<99, id: \.self) { i in
Text("\(i)").tag(i)
}
}
.pickerStyle(.menu)
DurationPicker(title: LocalizedStringKey("Preparation duration:"), duration: viewModel.prepDuration)
DurationPicker(title: LocalizedStringKey("Cooking duration:"), duration: viewModel.cookDuration)
DurationPicker(title: LocalizedStringKey("Total duration:"), duration: viewModel.totalDuration)
}
EditableListSection(title: LocalizedStringKey("Ingredients"), items: $viewModel.recipe.recipeIngredient)
EditableListSection(title: LocalizedStringKey("Tools"), items: $viewModel.recipe.tool)
EditableListSection(title: LocalizedStringKey("Instructions"), items: $viewModel.recipe.recipeInstructions)
}
}
}
.task {
viewModel.keywordSuggestions = await viewModel.mainViewModel.getKeywords(fetchMode: .preferServer)
}
.onAppear {
viewModel.prepareView()
}
.alert(alertType.localizedTitle, isPresented: $presentAlert) {
ForEach(alertType.alertButtons) { buttonType in
if buttonType == .OK {
Button(AlertButton.OK.rawValue, role: .cancel) {
Task {
await alertAction()
}
}
} else if buttonType == .CANCEL {
Button(AlertButton.CANCEL.rawValue, role: .cancel) { }
} else if buttonType == .DELETE {
Button(AlertButton.DELETE.rawValue, role: .destructive) {
Task {
await alertAction()
}
}
}
}
} message: {
Text(alertType.localizedDescription)
}
}
func dismissEditView() {
Task {
await viewModel.mainViewModel.getCategories()
await viewModel.mainViewModel.getCategory(named: viewModel.recipe.recipeCategory, fetchMode: .preferServer)
await viewModel.mainViewModel.updateRecipeDetails(in: viewModel.recipe.recipeCategory)
}
self.isPresented = false
}
}
fileprivate struct EditableListSection: View {
@State var title: LocalizedStringKey
@Binding var items: [String]
var body: some View {
Section() {
List {
ForEach(items.indices, id: \.self) { ix in
HStack(alignment: .top) {
Text("\(ix+1).")
.padding(.vertical, 10)
TextEditor(text: $items[ix])
.multilineTextAlignment(.leading)
.textFieldStyle(.plain)
.padding(.vertical, 1)
}
}
.onMove { indexSet, offset in
items.move(fromOffsets: indexSet, toOffset: offset)
}
.onDelete { indexSet in
items.remove(atOffsets: indexSet)
}
}
HStack {
Spacer()
Text("Add")
Button() {
items.append("")
} label: {
Image(systemName: "plus.circle.fill")
}
}
} header: {
HStack {
Text(title)
Spacer()
EditButton()
}
}
}
}
fileprivate struct DurationPicker: View {
@State var title: LocalizedStringKey
@ObservedObject var duration: DurationComponents
var body: some View {
HStack {
Text(title)
}
.frame(maxHeight: 40)
.clipped()
}
}
*/

View File

@@ -7,9 +7,8 @@ A Nextcloud Cookbook native iOS/iPadOS/MacOS client, built using Swift and Swift
See [here](https://github.com/nextcloud/cookbook) for the corresponding Nextcloud server application. See [here](https://github.com/nextcloud/cookbook) for the corresponding Nextcloud server application.
You can download the app from the AppStore: You can download the app from the AppStore:
[<img src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-us" alt="Download on the App Store" height="80" width="160">](https://apps.apple.com/de/app/cookbook-client/id6467141985)
[<img src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-us" alt="Download on the App Store" height="80" width="160">](https://apps.apple.com/de/app/cookbook-client/id6467141985)
## Features ## Features
@@ -27,16 +26,30 @@ You can download the app from the AppStore:
- [x] Share recipes (by name and keyword) - [x] Share recipes (by name and keyword)
- [x] Import recipes - [x] Import recipes
- [x] Keep display awake when viewing recipes - [x] Keep display awake when viewing recipes
- [ ] Cooking timer for recipes
- [x] Ingredient shopping list - [x] Ingredient shopping list
- [ ] Add code documentation
**Planned Features** ## Roadmap
- Calculate the required amount of a recipe ingredient depending on the number of servings
- Fuzzy search for recipe titles/keywords - [ ] **Version 1.9**: Enhancements to recipe editing for better intuitiveness; user interface design improvements for recipe viewing.
- Search for recipes based on left-over ingredients
- [ ] **Version 1.10**: Recipe ingredient calculator: Enables calculation of ingredient quantities based on a specifiable yield number.
- [ ] **Version 1.11**: Decoupling of internal recipe representation from the Nextcloud Cookbook recipe representation. This change provides increased flexibility for API updates and enables the introduction of features not currently supported by the Cookbook API, such as uploading images.
- [ ] **Version 1.12 and beyond** (Ideas for the future; integration not guaranteed!):
- Fuzzy search for recipe names and keywords.
- In-app timer for the cook time specified in a recipe.
- Search for recipes based on left-over ingredients.
- An option to use the app without a Nextcloud account.
- An option to specify the recipe folder in the Files app, to enable the app to work on the recipe files directly.
**If you would like to suggest new features/improvements or report bugs, please open an Issue!**
## Screenshots ## Screenshots
The following screenshots might not be up to date, since there can always be minor user interface changes. The following screenshots might not be up to date, since there can always be minor user interface changes.