Added category 'All' recipes
This commit is contained in:
@@ -25,13 +25,15 @@
|
||||
A70171C42AB4A31200064C43 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C32AB4A31200064C43 /* DataStore.swift */; };
|
||||
A70171C62AB4C43A00064C43 /* DataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C52AB4C43A00064C43 /* DataModels.swift */; };
|
||||
A70171C92AB4CBB400064C43 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171C82AB4CBB400064C43 /* OnboardingView.swift */; };
|
||||
A70171CB2AB4CD1700064C43 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171CA2AB4CD1700064C43 /* UserDefaults.swift */; };
|
||||
A70171CB2AB4CD1700064C43 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171CA2AB4CD1700064C43 /* UserSettings.swift */; };
|
||||
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70171CC2AB501B100064C43 /* SettingsView.swift */; };
|
||||
A703226A2ABAF49800D7C4ED /* JSONCoderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70322692ABAF49800D7C4ED /* JSONCoderExtension.swift */; };
|
||||
A703226D2ABAF90D00D7C4ED /* APIController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226C2ABAF90D00D7C4ED /* APIController.swift */; };
|
||||
A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */; };
|
||||
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */; };
|
||||
A70D7CA32AC74B3B00D53DBF /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */; };
|
||||
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; };
|
||||
A76B8A712AE002AE00096CEC /* AlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A702AE002AE00096CEC /* AlertHandler.swift */; };
|
||||
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; };
|
||||
A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; };
|
||||
A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; };
|
||||
@@ -77,13 +79,15 @@
|
||||
A70171C32AB4A31200064C43 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
|
||||
A70171C52AB4C43A00064C43 /* DataModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModels.swift; sourceTree = "<group>"; };
|
||||
A70171C82AB4CBB400064C43 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
A70171CA2AB4CD1700064C43 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
|
||||
A70171CA2AB4CD1700064C43 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.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>"; };
|
||||
A703226C2ABAF90D00D7C4ED /* APIController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIController.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>"; };
|
||||
A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
|
||||
A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; };
|
||||
A76B8A702AE002AE00096CEC /* AlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertHandler.swift; sourceTree = "<group>"; };
|
||||
A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordPickerView.swift; sourceTree = "<group>"; };
|
||||
A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = "<group>"; };
|
||||
@@ -138,13 +142,15 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A70171812AA8E71900064C43 /* Nextcloud_Cookbook_iOS_ClientApp.swift */,
|
||||
A76B8A702AE002AE00096CEC /* AlertHandler.swift */,
|
||||
A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */,
|
||||
A70171C72AB4C4A100064C43 /* Data */,
|
||||
A70171BA2AB4980100064C43 /* Views */,
|
||||
A70171B72AB2445700064C43 /* ViewModels */,
|
||||
A70171B22AB211F000064C43 /* Network */,
|
||||
A703226B2ABAF60D00D7C4ED /* Extensions */,
|
||||
A70171852AA8E71F00064C43 /* Assets.xcassets */,
|
||||
A7AEAE632AD5521400135378 /* Localizable.xcstrings */,
|
||||
A70171852AA8E71F00064C43 /* Assets.xcassets */,
|
||||
A70171872AA8E71F00064C43 /* Nextcloud_Cookbook_iOS_Client.entitlements */,
|
||||
A70171882AA8E71F00064C43 /* Preview Content */,
|
||||
);
|
||||
@@ -216,7 +222,7 @@
|
||||
children = (
|
||||
A70171C32AB4A31200064C43 /* DataStore.swift */,
|
||||
A70171C52AB4C43A00064C43 /* DataModels.swift */,
|
||||
A70171CA2AB4CD1700064C43 /* UserDefaults.swift */,
|
||||
A70171CA2AB4CD1700064C43 /* UserSettings.swift */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
@@ -368,6 +374,7 @@
|
||||
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */,
|
||||
A70D7CA32AC74B3B00D53DBF /* DateExtension.swift in Sources */,
|
||||
A70171B12AB211DF00064C43 /* CustomError.swift in Sources */,
|
||||
A76B8A712AE002AE00096CEC /* AlertHandler.swift in Sources */,
|
||||
A70171C42AB4A31200064C43 /* DataStore.swift in Sources */,
|
||||
A70171AF2AB2116B00064C43 /* NetworkHandler.swift in Sources */,
|
||||
A70171B42AB2122900064C43 /* NetworkRequests.swift in Sources */,
|
||||
@@ -380,11 +387,12 @@
|
||||
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */,
|
||||
A70171C22AB498C600064C43 /* RecipeCardView.swift in Sources */,
|
||||
A70171842AA8E71900064C43 /* MainView.swift in Sources */,
|
||||
A70171CB2AB4CD1700064C43 /* UserDefaults.swift in Sources */,
|
||||
A70171CB2AB4CD1700064C43 /* UserSettings.swift in Sources */,
|
||||
A703226A2ABAF49800D7C4ED /* JSONCoderExtension.swift in Sources */,
|
||||
A703226D2ABAF90D00D7C4ED /* APIController.swift in Sources */,
|
||||
A70171822AA8E71900064C43 /* Nextcloud_Cookbook_iOS_ClientApp.swift in Sources */,
|
||||
A70171AD2AA8EF4700064C43 /* MainViewModel.swift in Sources */,
|
||||
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */,
|
||||
A70171C92AB4CBB400064C43 /* OnboardingView.swift in Sources */,
|
||||
A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */,
|
||||
);
|
||||
@@ -562,7 +570,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
MARKETING_VERSION = 1.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
@@ -603,7 +611,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
MARKETING_VERSION = 1.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
||||
99
Nextcloud Cookbook iOS Client/AlertHandler.swift
Normal file
99
Nextcloud Cookbook iOS Client/AlertHandler.swift
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AlertHandler.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 18.10.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
|
||||
class AlertHandler: ObservableObject {
|
||||
@Published var presentAlert: Bool = false
|
||||
var alert: AlertType = .GENERIC
|
||||
var alertAction: () -> () = {}
|
||||
|
||||
func present(alert: AlertType, onConfirm: @escaping () -> () = {}) {
|
||||
self.alert = alert
|
||||
self.alertAction = onConfirm
|
||||
self.presentAlert = true
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
self.alertAction = {}
|
||||
self.alert = .GENERIC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum AlertButton: LocalizedStringKey, Identifiable {
|
||||
var id: Self {
|
||||
return self
|
||||
}
|
||||
|
||||
case OK = "Ok", DELETE = "Delete", CANCEL = "Cancel"
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum AlertType: Error {
|
||||
|
||||
case NO_TITLE,
|
||||
DUPLICATE,
|
||||
UPLOAD_ERROR,
|
||||
CONFIRM_DELETE,
|
||||
LOGIN_FAILED,
|
||||
GENERIC,
|
||||
CUSTOM(title: LocalizedStringKey, description: LocalizedStringKey)
|
||||
|
||||
var localizedDescription: LocalizedStringKey {
|
||||
switch self {
|
||||
case .NO_TITLE:
|
||||
return "Please enter a recipe name."
|
||||
case .DUPLICATE:
|
||||
return "A recipe with that name already exists."
|
||||
case .UPLOAD_ERROR:
|
||||
return "Unable to upload your recipe. Please check your internet connection."
|
||||
case .CONFIRM_DELETE:
|
||||
return "This action is not reversible!"
|
||||
case .LOGIN_FAILED:
|
||||
return "Please check your credentials and internet connection."
|
||||
case .CUSTOM(title: _, description: let description):
|
||||
return description
|
||||
default:
|
||||
return "An unknown error occured."
|
||||
}
|
||||
}
|
||||
|
||||
var localizedTitle: LocalizedStringKey {
|
||||
switch self {
|
||||
case .NO_TITLE:
|
||||
return "Missing recipe name."
|
||||
case .DUPLICATE:
|
||||
return "Duplicate recipe."
|
||||
case .UPLOAD_ERROR:
|
||||
return "Network error."
|
||||
case .CONFIRM_DELETE:
|
||||
return "Delete recipe?"
|
||||
case .LOGIN_FAILED:
|
||||
return "Login failed."
|
||||
case .CUSTOM(title: let title, description: _):
|
||||
return title
|
||||
default:
|
||||
return "Error."
|
||||
}
|
||||
}
|
||||
|
||||
var alertButtons: [AlertButton] {
|
||||
switch self {
|
||||
case .CONFIRM_DELETE:
|
||||
return [.CANCEL, .DELETE]
|
||||
default:
|
||||
return [.OK]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cookbook-category.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "cookbook-recipe.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// UserDefaults.swift
|
||||
// UserSettings.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 15.09.23.
|
||||
@@ -46,12 +46,19 @@ class UserSettings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var downloadRecipes: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(downloadRecipes, forKey: "downloadRecipes")
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
|
||||
self.token = UserDefaults.standard.object(forKey: "token") as? String ?? ""
|
||||
self.serverAddress = UserDefaults.standard.object(forKey: "serverAddress") as? String ?? ""
|
||||
self.onboarding = UserDefaults.standard.object(forKey: "onboarding") as? Bool ?? true
|
||||
self.defaultCategory = UserDefaults.standard.object(forKey: "defaultCategory") as? String ?? ""
|
||||
self.language = UserDefaults.standard.object(forKey: "language") as? String ?? SupportedLanguage.EN.rawValue
|
||||
self.language = UserDefaults.standard.object(forKey: "language") as? String ?? SupportedLanguage.DEVICE.rawValue
|
||||
self.downloadRecipes = UserDefaults.standard.object(forKey: "downloadRecipes") as? Bool ?? false
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,7 @@ extension Color {
|
||||
public static var backgroundHighlight: Color {
|
||||
return Color("backgroundHighlight")
|
||||
}
|
||||
public static var background: Color {
|
||||
return Color(UIColor.systemBackground)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"All" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Alle"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todas"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"An unknown error occured." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -257,18 +273,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cook time" : {
|
||||
"Connected to server." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kochen"
|
||||
"value" : "Verbindung mit dem Server hergestellt."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Duración de cocción"
|
||||
"value" : "Conexión al servidor establecida."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,6 +321,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cooking" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kochen"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cocción"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cooking duration:" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -545,6 +577,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"If 'Same as Device' is selected and your device language is not supported yet, this option will default to english." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wenn \"Systemsprache\" ausgewählt ist und Ihre Systemsprache noch nicht unterstützt wird, wird standardmäßig Englisch verwendet."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Si se selecciona 'Idioma del sistema' y el idioma de su dispositivo no es compatible aún, esta opción se establecerá por defecto en inglés."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"If you are interested in contributing to this project or simply wish to review its source code, we encourage you to visit the GitHub repository for this application." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -689,6 +737,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Login failed." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Login fehlgeschlagen."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inicio de sesión fallido"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Login Method" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -833,6 +897,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Please check your credentials and internet connection." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bitte überprüfen Sie Ihre Anmeldedaten oder Ihre Internetverbindung."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Por favor, comprueba tus credenciales de inicio de sesión y la conexión a Internet."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Please enter a recipe name." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -849,7 +929,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Prep time" : {
|
||||
"Preparation" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
@@ -860,7 +940,7 @@
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Duración de preparación"
|
||||
"value" : "Preparación"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -881,6 +961,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Same as Device" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Systemsprache"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Idioma del sistema"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Search recipe" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rezept suchen"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Buscar receta"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Search recipes" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -1121,6 +1233,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to connect to server." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Verbindung mit dem Server fehlgeschlagen."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No se puede conectar al servidor."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to upload your recipe. Please check your internet connection." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
||||
@@ -20,6 +20,7 @@ enum RequestPath {
|
||||
RECIPE_DETAIL(recipeId: Int),
|
||||
NEW_RECIPE,
|
||||
IMAGE(recipeId: Int, thumb: Bool),
|
||||
CONFIG,
|
||||
KEYWORDS
|
||||
|
||||
case LOGINV2REQ,
|
||||
@@ -33,6 +34,7 @@ enum RequestPath {
|
||||
case .RECIPE_DETAIL(recipeId: let recipeId): return "recipes/\(recipeId)"
|
||||
case .IMAGE(recipeId: let recipeId, thumb: let thumb): return "recipes/\(recipeId)/image?size=\(thumb ? "thumb" : "full")"
|
||||
case .NEW_RECIPE: return "recipes"
|
||||
case .CONFIG: return "config"
|
||||
case .KEYWORDS: return "keywords"
|
||||
|
||||
case .LOGINV2REQ: return "/index.php/login/v2"
|
||||
|
||||
@@ -11,6 +11,7 @@ import SwiftUI
|
||||
struct Nextcloud_Cookbook_iOS_ClientApp: App {
|
||||
@StateObject var userSettings = UserSettings()
|
||||
@StateObject var mainViewModel = MainViewModel()
|
||||
@StateObject var alertHandler = AlertHandler()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
@@ -25,8 +26,12 @@ struct Nextcloud_Cookbook_iOS_ClientApp: App {
|
||||
}
|
||||
}
|
||||
.transition(.slide)
|
||||
.environment(\.locale, .init(identifier: userSettings.language))
|
||||
.environment(
|
||||
\.locale,
|
||||
.init(identifier: userSettings.language ==
|
||||
SupportedLanguage.DEVICE.rawValue ? (Locale.current.language.languageCode?.identifier ?? "en") : userSettings.language)
|
||||
)
|
||||
.environmentObject(alertHandler)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
31
Nextcloud Cookbook iOS Client/SupportedLanguage.swift
Normal file
31
Nextcloud Cookbook iOS Client/SupportedLanguage.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// SupportedLanguage.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 18.10.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
enum SupportedLanguage: String, Codable {
|
||||
case DEVICE = "device",
|
||||
EN = "en",
|
||||
DE = "de",
|
||||
ES = "es"
|
||||
|
||||
func descriptor() -> String {
|
||||
switch self {
|
||||
case .DEVICE:
|
||||
return String(localized: "Same as Device")
|
||||
case .EN:
|
||||
return "English"
|
||||
case .DE:
|
||||
return "Deutsch"
|
||||
case .ES:
|
||||
return "Español"
|
||||
}
|
||||
}
|
||||
|
||||
static let allValues = [DEVICE, EN, DE, ES]
|
||||
}
|
||||
@@ -64,6 +64,19 @@ import SwiftUI
|
||||
|
||||
}
|
||||
|
||||
func getAllRecipes() async -> [Recipe] {
|
||||
var allRecipes: [Recipe] = []
|
||||
for category in categories {
|
||||
await loadRecipeList(categoryName: category.name)
|
||||
if let recipeArray = recipes[category.name] {
|
||||
allRecipes.append(contentsOf: recipeArray)
|
||||
}
|
||||
}
|
||||
return allRecipes.sorted(by: {
|
||||
$0.name < $1.name
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to load the recipe details from cache. If not found, try to load from store or the server.
|
||||
/// - Parameters
|
||||
/// - recipeId: The id of the recipe.
|
||||
@@ -204,6 +217,22 @@ import SwiftUI
|
||||
})
|
||||
recipeDetails.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
func checkServerConnection() async -> Bool {
|
||||
guard let apiController = apiController else { return false }
|
||||
let req = RequestWrapper.customRequest(
|
||||
method: .GET,
|
||||
path: .CONFIG,
|
||||
headerFields: [
|
||||
.ocsRequest(value: true),
|
||||
.accept(value: .JSON)
|
||||
]
|
||||
)
|
||||
if let error = await apiController.sendRequest(req) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,7 +254,7 @@ extension MainViewModel {
|
||||
}
|
||||
return data
|
||||
}
|
||||
}catch {
|
||||
} catch {
|
||||
print("An unknown error occurred.")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -14,104 +14,203 @@ struct MainView: View {
|
||||
|
||||
@State private var selectedCategory: Category? = nil
|
||||
@State private var showEditView: Bool = false
|
||||
@State private var showSearchView: Bool = false
|
||||
@State private var showSettingsView: Bool = false
|
||||
@State private var serverConnection: Bool = false
|
||||
|
||||
|
||||
var columns: [GridItem] = [GridItem(.adaptive(minimum: 150), spacing: 0)]
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List(viewModel.categories, selection: $selectedCategory) { category in
|
||||
if category.recipe_count != 0 {
|
||||
NavigationLink(value: category) {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "book.closed.fill")
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
.font(.system(size: 20, weight: .light, design: .serif))
|
||||
.italic()
|
||||
}.padding(7)
|
||||
List(selection: $selectedCategory) {
|
||||
// All recipes
|
||||
NavigationLink {
|
||||
RecipeSearchView(viewModel: viewModel)
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "book.closed.fill")
|
||||
Text("All")
|
||||
.font(.system(size: 20, weight: .light, design: .serif))
|
||||
.italic()
|
||||
}
|
||||
.padding(7)
|
||||
}
|
||||
|
||||
// Categories
|
||||
ForEach(viewModel.categories) { category in
|
||||
if category.recipe_count != 0 {
|
||||
NavigationLink(value: category) {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "book.closed.fill")
|
||||
Text(category.name == "*" ? "Other" : category.name)
|
||||
.font(.system(size: 20, weight: .light, design: .serif))
|
||||
.italic()
|
||||
}.padding(7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigationTitle("Cookbooks")
|
||||
.navigationDestination(isPresented: $showSettingsView) {
|
||||
SettingsView(userSettings: userSettings, viewModel: viewModel)
|
||||
}
|
||||
.navigationDestination(isPresented: $showSearchView) {
|
||||
RecipeSearchView(viewModel: viewModel)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Menu {
|
||||
Button {
|
||||
print("Downloading all recipes ...")
|
||||
Task {
|
||||
await viewModel.downloadAllRecipes()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Download all recipes")
|
||||
Image(systemName: "icloud.and.arrow.down")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
self.showSettingsView = true
|
||||
} label: {
|
||||
Text("Settings")
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
MainViewToolBar(
|
||||
viewModel: viewModel,
|
||||
showEditView: $showEditView,
|
||||
showSettingsView: $showSettingsView,
|
||||
serverConnection: $serverConnection
|
||||
)
|
||||
}
|
||||
} detail: {
|
||||
NavigationStack {
|
||||
if let category = selectedCategory {
|
||||
CategoryDetailView(
|
||||
categoryName: category.name,
|
||||
viewModel: viewModel,
|
||||
showEditView: $showEditView
|
||||
)
|
||||
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.sheet(isPresented: $showEditView) {
|
||||
RecipeEditView(viewModel: viewModel, isPresented: $showEditView)
|
||||
}
|
||||
.task {
|
||||
self.serverConnection = await viewModel.checkServerConnection()
|
||||
await viewModel.loadCategoryList()
|
||||
// Open detail view for default category
|
||||
if userSettings.defaultCategory != "" {
|
||||
if let cat = viewModel.categories.first(where: { c in
|
||||
if c.name == userSettings.defaultCategory {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
self.selectedCategory = cat
|
||||
}
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
self.serverConnection = await viewModel.checkServerConnection()
|
||||
await viewModel.loadCategoryList(needsUpdate: true)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
struct MainViewToolBar: ToolbarContent {
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@Binding var showEditView: Bool
|
||||
@Binding var showSettingsView: Bool
|
||||
@Binding var serverConnection: Bool
|
||||
@State private var presentPopover: Bool = false
|
||||
|
||||
var body: some ToolbarContent {
|
||||
// Top left menu toolbar item
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Menu {
|
||||
Button {
|
||||
print("Add new recipe")
|
||||
showEditView = true
|
||||
print("Downloading all recipes ...")
|
||||
Task {
|
||||
await viewModel.downloadAllRecipes()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
Text("Download all recipes")
|
||||
Image(systemName: "icloud.and.arrow.down")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
self.showSettingsView = true
|
||||
} label: {
|
||||
Text("Settings")
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
|
||||
// Server connection indicator
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
print("Check server connection")
|
||||
presentPopover = true
|
||||
} label: {
|
||||
if serverConnection {
|
||||
Image(systemName: "checkmark.icloud")
|
||||
} else {
|
||||
Image(systemName: "xmark.icloud")
|
||||
}
|
||||
}.popover(isPresented: $presentPopover) {
|
||||
Text(serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server."))
|
||||
.bold()
|
||||
.padding()
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new recipes
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
print("Add new recipe")
|
||||
showEditView = true
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct RecipeSearchView: View {
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@State var searchText: String = ""
|
||||
@State var allRecipes: [Recipe] = []
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack {
|
||||
ForEach(recipesFiltered(), id: \.recipe_id) { recipe in
|
||||
NavigationLink(value: recipe) {
|
||||
RecipeCardView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} detail: {
|
||||
NavigationStack {
|
||||
if let category = selectedCategory {
|
||||
CategoryDetailView(
|
||||
categoryName: category.name,
|
||||
viewModel: viewModel,
|
||||
showEditView: $showEditView
|
||||
)
|
||||
.id(category.id) // Workaround: This is needed to update the detail view when the selection changes
|
||||
.navigationDestination(for: Recipe.self) { recipe in
|
||||
RecipeDetailView(viewModel: viewModel, recipe: recipe)
|
||||
}
|
||||
.searchable(text: $searchText, prompt: "Search recipes")
|
||||
}
|
||||
}
|
||||
.tint(.nextcloudBlue)
|
||||
.sheet(isPresented: $showEditView) {
|
||||
RecipeEditView(viewModel: viewModel, isPresented: $showEditView)
|
||||
.navigationTitle("Search recipe")
|
||||
}
|
||||
.task {
|
||||
await viewModel.loadCategoryList()
|
||||
if userSettings.defaultCategory != "" {
|
||||
if let cat = viewModel.categories.first(where: { c in
|
||||
if c.name == userSettings.defaultCategory {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
self.selectedCategory = cat
|
||||
}
|
||||
}
|
||||
allRecipes = await viewModel.getAllRecipes()
|
||||
}
|
||||
.refreshable {
|
||||
await viewModel.loadCategoryList(needsUpdate: true)
|
||||
}
|
||||
|
||||
func recipesFiltered() -> [Recipe] {
|
||||
guard searchText != "" else { return allRecipes }
|
||||
return allRecipes.filter { recipe in
|
||||
recipe.name.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,24 @@ struct RecipeCardView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(uiImage: recipeThumb ?? UIImage(named: "cookbook-recipe")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
if let recipeThumb = recipeThumb {
|
||||
Image(uiImage: recipeThumb)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
.clipped()
|
||||
} else {
|
||||
ZStack {
|
||||
Image(systemName: "square.text.square")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundStyle(Color.white)
|
||||
.padding(10)
|
||||
|
||||
}
|
||||
.background(Color("ncblue"))
|
||||
.frame(width: 80, height: 80)
|
||||
.clipped()
|
||||
}
|
||||
Text(recipe.name)
|
||||
.font(.headline)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import PhotosUI
|
||||
|
||||
|
||||
struct RecipeEditView: View {
|
||||
@EnvironmentObject var alertHandler: AlertHandler
|
||||
@ObservedObject var viewModel: MainViewModel
|
||||
@State var recipe: RecipeDetail = RecipeDetail()
|
||||
@Binding var isPresented: Bool
|
||||
@@ -25,8 +26,6 @@ struct RecipeEditView: View {
|
||||
@State private var keywords: [String] = []
|
||||
@State private var keywordSuggestions: [String] = []
|
||||
|
||||
@State private var alertMessage: ErrorMessages = .GENERIC
|
||||
@State private var presentAlert: Bool = false
|
||||
@State private var waitingForUpload: Bool = false
|
||||
|
||||
var body: some View {
|
||||
@@ -43,8 +42,9 @@ struct RecipeEditView: View {
|
||||
Menu {
|
||||
Button {
|
||||
print("Delete recipe.")
|
||||
alertMessage = .CONFIRM_DELETE
|
||||
presentAlert = true
|
||||
alertHandler.present(alert: .CONFIRM_DELETE) {
|
||||
deleteRecipe()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.foregroundStyle(.red)
|
||||
@@ -152,18 +152,24 @@ struct RecipeEditView: View {
|
||||
keywords.append(keyword)
|
||||
}
|
||||
}
|
||||
.alert(alertMessage.localizedTitle, isPresented: $presentAlert) {
|
||||
switch alertMessage {
|
||||
case .CONFIRM_DELETE:
|
||||
Button("Cancel", role: .cancel) { }
|
||||
Button("Delete", role: .destructive) {
|
||||
deleteRecipe()
|
||||
.alert(alertHandler.alert.localizedTitle, isPresented: $alertHandler.presentAlert) {
|
||||
ForEach(alertHandler.alert.alertButtons) { buttonType in
|
||||
if buttonType == .OK {
|
||||
Button(AlertButton.OK.rawValue, role: .cancel) {
|
||||
alertHandler.alertAction()
|
||||
alertHandler.dismiss()
|
||||
}
|
||||
} else if buttonType == .CANCEL {
|
||||
Button(AlertButton.CANCEL.rawValue, role: .cancel) { }
|
||||
} else if buttonType == .DELETE {
|
||||
Button(AlertButton.DELETE.rawValue, role: .destructive) {
|
||||
alertHandler.alertAction()
|
||||
alertHandler.dismiss()
|
||||
}
|
||||
}
|
||||
default:
|
||||
Button("Ok", role: .cancel) { }
|
||||
}
|
||||
} message: {
|
||||
Text(alertMessage.localizedDescription)
|
||||
Text(alertHandler.alert.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +185,8 @@ struct RecipeEditView: View {
|
||||
|
||||
func recipeValid() -> Bool {
|
||||
// Check if the recipe has a name
|
||||
if recipe.name == "" {
|
||||
self.alertMessage = .NO_TITLE
|
||||
self.presentAlert = true
|
||||
if recipe.name.replacingOccurrences(of: " ", with: "") == "" {
|
||||
alertHandler.present(alert: .NO_TITLE)
|
||||
return false
|
||||
}
|
||||
// Check if the recipe has a unique name
|
||||
@@ -194,8 +199,7 @@ struct RecipeEditView: View {
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.lowercased()
|
||||
{
|
||||
self.alertMessage = .DUPLICATE
|
||||
self.presentAlert = true
|
||||
alertHandler.present(alert: .DUPLICATE)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -266,11 +270,7 @@ struct RecipeEditView: View {
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let error = try JSONDecoder().decode(ServerMessage.self, from: data)
|
||||
DispatchQueue.main.sync {
|
||||
alertMessage = .CUSTOM(title: "Error.", description: LocalizedStringKey(stringLiteral: error.msg))
|
||||
presentAlert = true
|
||||
return
|
||||
}
|
||||
// TODO: Better error handling (Show error to user!)
|
||||
} catch {
|
||||
|
||||
}
|
||||
@@ -410,46 +410,3 @@ fileprivate class Duration: ObservableObject {
|
||||
|
||||
|
||||
|
||||
fileprivate enum ErrorMessages: Error {
|
||||
|
||||
case NO_TITLE,
|
||||
DUPLICATE,
|
||||
UPLOAD_ERROR,
|
||||
CONFIRM_DELETE,
|
||||
GENERIC,
|
||||
CUSTOM(title: LocalizedStringKey, description: LocalizedStringKey)
|
||||
|
||||
var localizedDescription: LocalizedStringKey {
|
||||
switch self {
|
||||
case .NO_TITLE:
|
||||
return "Please enter a recipe name."
|
||||
case .DUPLICATE:
|
||||
return "A recipe with that name already exists."
|
||||
case .UPLOAD_ERROR:
|
||||
return "Unable to upload your recipe. Please check your internet connection."
|
||||
case .CONFIRM_DELETE:
|
||||
return "This action is not reversible!"
|
||||
case .CUSTOM(title: _, description: let description):
|
||||
return description
|
||||
default:
|
||||
return "An unknown error occured."
|
||||
}
|
||||
}
|
||||
|
||||
var localizedTitle: LocalizedStringKey {
|
||||
switch self {
|
||||
case .NO_TITLE:
|
||||
return "Missing recipe name."
|
||||
case .DUPLICATE:
|
||||
return "Duplicate recipe."
|
||||
case .UPLOAD_ERROR:
|
||||
return "Network error."
|
||||
case .CONFIRM_DELETE:
|
||||
return "Delete recipe?"
|
||||
case .CUSTOM(title: let title, description: _):
|
||||
return title
|
||||
default:
|
||||
return "Error."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,45 +8,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
fileprivate enum SettingsAlert {
|
||||
case LOG_OUT,
|
||||
DELETE_CACHE,
|
||||
NONE
|
||||
|
||||
func getTitle() -> String {
|
||||
switch self {
|
||||
case .LOG_OUT: return "Log out"
|
||||
case .DELETE_CACHE: return "Delete local data"
|
||||
default: return "Please confirm your action."
|
||||
}
|
||||
}
|
||||
|
||||
func getMessage() -> String {
|
||||
switch self {
|
||||
case .LOG_OUT: return "Are you sure that you want to log out of your account?"
|
||||
case .DELETE_CACHE: return "Are you sure that you want to delete the downloaded recipes? This action will not affect any recipes stored on your server."
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SupportedLanguage: String, Codable {
|
||||
case EN = "en",
|
||||
DE = "de",
|
||||
ES = "es"
|
||||
func descriptor() -> String {
|
||||
switch self {
|
||||
case .EN:
|
||||
return "English"
|
||||
case .DE:
|
||||
return "Deutsch"
|
||||
case .ES:
|
||||
return "Español"
|
||||
}
|
||||
}
|
||||
|
||||
static let allValues = [EN, DE, ES]
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@ObservedObject var userSettings: UserSettings
|
||||
@@ -58,23 +20,32 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
/*Toggle(isOn: $userSettings.downloadRecipes) {
|
||||
Text("Always download new recipes")
|
||||
}*/
|
||||
Picker("Select a default cookbook", selection: $userSettings.defaultCategory) {
|
||||
Text("None").tag("None")
|
||||
ForEach(viewModel.categories, id: \.name) { category in
|
||||
Text(category.name == "*" ? "Other" : category.name).tag(category)
|
||||
}
|
||||
}
|
||||
Picker("Language", selection: $userSettings.language) {
|
||||
ForEach(SupportedLanguage.allValues, id: \.self) { lang in
|
||||
Text(lang.descriptor()).tag(lang.rawValue)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("General")
|
||||
} footer: {
|
||||
Text("The selected cookbook will open on app launch by default.")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Language", selection: $userSettings.language) {
|
||||
ForEach(SupportedLanguage.allValues, id: \.self) { lang in
|
||||
Text(lang.descriptor()).tag(lang.rawValue)
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("If \'Same as Device\' is selected and your device language is not supported yet, this option will default to english.")
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
Link("Visit the GitHub page", destination: URL(string: "https://github.com/VincentMeilinger/Nextcloud-Cookbook-iOS")!)
|
||||
} header: {
|
||||
@@ -142,3 +113,24 @@ struct SettingsView: View {
|
||||
|
||||
|
||||
|
||||
fileprivate enum SettingsAlert {
|
||||
case LOG_OUT,
|
||||
DELETE_CACHE,
|
||||
NONE
|
||||
|
||||
func getTitle() -> String {
|
||||
switch self {
|
||||
case .LOG_OUT: return "Log out"
|
||||
case .DELETE_CACHE: return "Delete local data"
|
||||
default: return "Please confirm your action."
|
||||
}
|
||||
}
|
||||
|
||||
func getMessage() -> String {
|
||||
switch self {
|
||||
case .LOG_OUT: return "Are you sure that you want to log out of your account?"
|
||||
case .DELETE_CACHE: return "Are you sure that you want to delete the downloaded recipes? This action will not affect any recipes stored on your server."
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user