Added category 'All' recipes

This commit is contained in:
Vicnet
2023-10-22 20:28:51 +02:00
parent 05c30a2cff
commit 8f32946e27
17 changed files with 574 additions and 243 deletions

View 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]
}
}
}

View File

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

View File

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

View File

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

View File

@@ -15,4 +15,7 @@ extension Color {
public static var backgroundHighlight: Color {
return Color("backgroundHighlight")
}
public static var background: Color {
return Color(UIColor.systemBackground)
}
}

View File

@@ -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" : {

View File

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

View File

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

View 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]
}

View File

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

View File

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

View File

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

View File

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

View File

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