Code cleanup, translations, show empty categories

This commit is contained in:
VincentMeilinger
2024-03-26 18:13:24 +01:00
parent d201690332
commit 6ae9926c41
7 changed files with 289 additions and 402 deletions

View File

@@ -44,7 +44,6 @@
A7FB0D7A2B25C66600A3469E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D792B25C66600A3469E /* OnboardingView.swift */; };
A7FB0D7C2B25C68500A3469E /* TokenLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7B2B25C68500A3469E /* TokenLoginView.swift */; };
A7FB0D7E2B25C6A200A3469E /* V2LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */; };
A90C45F42B9F4DB6005D62B6 /* Units.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90C45F32B9F4DB6005D62B6 /* Units.swift */; };
A97506132B920D9F00E86029 /* RecipeDurationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506122B920D9F00E86029 /* RecipeDurationSection.swift */; };
A97506152B920DF200E86029 /* RecipeGenericViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506142B920DF200E86029 /* RecipeGenericViews.swift */; };
A97506192B920EC200E86029 /* RecipeIngredientSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97506182B920EC200E86029 /* RecipeIngredientSection.swift */; };
@@ -126,7 +125,6 @@
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>"; };
A7FB0D7D2B25C6A200A3469E /* V2LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2LoginView.swift; sourceTree = "<group>"; };
A90C45F32B9F4DB6005D62B6 /* Units.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Units.swift; sourceTree = "<group>"; };
A97506122B920D9F00E86029 /* RecipeDurationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDurationSection.swift; sourceTree = "<group>"; };
A97506142B920DF200E86029 /* RecipeGenericViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeGenericViews.swift; sourceTree = "<group>"; };
A97506182B920EC200E86029 /* RecipeIngredientSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeIngredientSection.swift; sourceTree = "<group>"; };
@@ -287,7 +285,6 @@
A70171C52AB4C43A00064C43 /* DataModels.swift */,
A97B4D312B80B3E900EC1A88 /* RecipeModels.swift */,
A9BBB38F2B91BE31002DA7FF /* ObservableRecipeDetail.swift */,
A90C45F32B9F4DB6005D62B6 /* Units.swift */,
);
path = Data;
sourceTree = "<group>";
@@ -596,7 +593,6 @@
A7F3F8E82ACBFC760076C227 /* RecipeKeywordSection.swift in Sources */,
A79AA8E02AFF80E3007D25F2 /* DurationComponents.swift in Sources */,
A70171C02AB498A900064C43 /* RecipeView.swift in Sources */,
A90C45F42B9F4DB6005D62B6 /* Units.swift in Sources */,
A79AA8E42B02A962007D25F2 /* CookbookApi.swift in Sources */,
A975061B2B920F9F00E86029 /* RecipeNutritionSection.swift in Sources */,
A70171CD2AB501B100064C43 /* SettingsView.swift in Sources */,

View File

@@ -126,13 +126,11 @@ class ObservableRecipeDetail: ObservableObject {
var attributedString = AttributedString(ingredient)
for (newSubstring, matchRange) in matches {
print(newSubstring, matchRange)
guard let range = Range(matchRange, in: attributedString) else { continue }
var attributedSubString = AttributedString(newSubstring)
//attributedSubString.foregroundColor = .ncTextHighlight
attributedSubString.font = .system(.body, weight: .bold)
attributedString.replaceSubrange(range, with: attributedSubString)
print("\n", attributedString)
}
return attributedString

View File

@@ -1,212 +0,0 @@
//
// Units.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 11.03.24.
//
import Foundation
import SwiftUI
// MARK: - Ingredient Units
enum MeasurementUnit {
// Volume Metric
case milliLiter, centiLiter, deciLiter, liter
// Volume Imperial
case teaspoon, tablespoon, cup, pint, quart, gallon, gill, fluidOunce // Please just use metric
// Weight Metric
case milliGram, gram, kilogram
// Weight Imperial
case ounce, pound
// Other
case pinch, dash, smidgen
var localizedDescription: [LocalizedStringKey] {
switch self {
case .milliLiter:
return ["milliliter", "millilitre", "ml", "cc"]
case .centiLiter:
return ["centiliter", "centilitre", "cl"]
case .deciLiter:
return ["deciliter", "decilitre", "dl"]
case .liter:
return ["liter", "litre", "l"]
case .teaspoon:
return ["teaspoon", "tsp"]
case .tablespoon:
return ["tablespoon", "tbsp"]
case .cup:
return ["cup", "c"]
case .pint:
return ["pint", "pt"]
case .quart:
return ["quart", "qt"]
case .gallon:
return ["gallon", "gal"]
case .gill:
return ["gill", "gi"]
case .fluidOunce:
return ["fluid ounce", "fl oz"]
case .milliGram:
return ["milligram", "mg"]
case .gram:
return ["gram", "g"]
case .kilogram:
return ["kilogram", "kg"]
case .ounce:
return ["ounce", "oz"]
case .pound:
return ["pound", "lb"]
case .pinch:
return ["pinch"]
case .dash:
return ["dash"]
case .smidgen:
return ["smidgen"]
}
}
static func convert(value: Double, from fromUnit: MeasurementUnit, to toUnit: MeasurementUnit) -> Double? {
let (baseValue, _) = MeasurementUnit.toBaseUnit(value: value, unit: fromUnit)
return MeasurementUnit.fromBaseUnit(value: baseValue, targetUnit: toUnit)
}
private static func baseUnit(of unit: MeasurementUnit) -> MeasurementUnit {
switch unit {
// Volume Metric (all converted to liters)
case .milliLiter, .centiLiter, .deciLiter, .liter, .teaspoon, .tablespoon, .cup, .pint, .quart, .gallon, .gill, .fluidOunce, .dash:
return .liter
// Weight (all converted to grams)
case .milliGram, .gram, .kilogram, .ounce, .pound, .pinch, .smidgen:
return .gram
}
}
private static func toBaseUnit(value: Double, unit: MeasurementUnit) -> (Double, MeasurementUnit) {
guard abs(value) >= Double(1e-10) else {
return (0, unit)
}
switch unit {
case .milliLiter:
return (value/1000, .liter)
case .centiLiter:
return (value/100, .liter)
case .deciLiter:
return (value/10, .liter)
case .liter:
return (value, .liter)
case .teaspoon:
return (value * 0.005, .liter)
case .tablespoon:
return (value * 0.015, .liter)
case .cup:
return (value * 0.25, .liter)
case .pint:
return (value * 0.5, .liter)
case .quart:
return (value * 0.946, .liter)
case .gallon:
return (value * 3.8, .liter)
case .gill:
return (value * 0.17, .liter)
case .fluidOunce:
return (value * 0.03, .liter)
case .milliGram:
return (value * 0.001, .gram)
case .gram:
return (value, .gram)
case .kilogram:
return (value * 1000, .gram)
case .ounce:
return (value * 30, .gram)
case .pound:
return (value * 450, .gram)
case .pinch:
return (value * 0.3, .gram)
case .dash:
return (value * 0.000625, .liter)
case .smidgen:
return (value * 0.15, .gram)
}
}
static private func fromBaseUnit(value: Double, targetUnit: MeasurementUnit) -> Double {
guard abs(value) >= Double(1e-10) else {
return 0
}
switch targetUnit {
case .milliLiter:
return value * 1000
case .centiLiter:
return value * 100
case .deciLiter:
return value * 10
case .liter:
return value
case .teaspoon:
return value / 0.005
case .tablespoon:
return value / 0.015
case .cup:
return value / 0.25
case .pint:
return value / 0.5
case .quart:
return value / 0.946
case .gallon:
return value / 3.8
case .gill:
return value / 0.17
case .fluidOunce:
return value / 0.03
case .milliGram:
return value * 1000
case .gram:
return value
case .kilogram:
return value / 1000
case .ounce:
return value / 30
case .pound:
return value / 450
case .pinch:
return value / 0.3
case .dash:
return value / 0.000625
case .smidgen:
return value / 0.15
}
}
}
enum TemperatureUnit {
case fahrenheit, celsius
var localizedDescription: [LocalizedStringKey] {
switch self {
case .fahrenheit:
["fahrenheit", "f"]
case .celsius:
["celsius", "c"]
}
}
static func celsiusToFahrenheit(_ celsius: Double) -> Double {
return celsius * 9.0 / 5.0 + 32.0
}
static func fahrenheitToCelsius(_ fahrenheit: Double) -> Double {
return (fahrenheit - 32.0) * 5.0 / 9.0
}
}

View File

@@ -91,7 +91,26 @@
}
},
"%.2f" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%.2f"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "%.2f"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
}
}
},
"%@" : {
"localizations" : {
@@ -664,9 +683,6 @@
}
}
}
},
"c" : {
},
"Calories" : {
"comment" : "Calories",
@@ -780,18 +796,6 @@
}
}
}
},
"cc" : {
},
"celsius" : {
},
"centiliter" : {
},
"centilitre" : {
},
"Cholesterol content" : {
"comment" : "Cholesterol content",
@@ -837,12 +841,28 @@
}
}
}
},
"cl" : {
},
"Comma (e.g. 1,42)" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Komma (z.B. 1,42)"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Coma (por ejemplo, 1,42)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Virgule (par exemple, 1,42)"
}
}
}
},
"Configure what is stored on your device." : {
"localizations" : {
@@ -1085,21 +1105,28 @@
}
}
}
},
"cup" : {
},
"dash" : {
},
"deciliter" : {
},
"decilitre" : {
},
"Decimal number format" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dezimalzahlenformat"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Formato de número decimal"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Format de nombre décimal"
}
}
}
},
"Delete" : {
"localizations" : {
@@ -1277,9 +1304,6 @@
}
}
}
},
"dl" : {
},
"Done" : {
"localizations" : {
@@ -1523,12 +1547,6 @@
}
}
}
},
"f" : {
},
"fahrenheit" : {
},
"Fat content" : {
"comment" : "Fat content",
@@ -1575,24 +1593,28 @@
}
}
}
},
"fl oz" : {
},
"fluid ounce" : {
},
"Fraction" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bruch"
}
},
"g" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fracción"
}
},
"gal" : {
},
"gallon" : {
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fraction"
}
}
}
},
"General" : {
"localizations" : {
@@ -1637,15 +1659,6 @@
}
}
}
},
"gi" : {
},
"gill" : {
},
"gram" : {
},
"Grocery List" : {
"localizations" : {
@@ -2000,15 +2013,6 @@
}
}
}
},
"kg" : {
},
"kilogram" : {
},
"l" : {
},
"Language" : {
"localizations" : {
@@ -2075,9 +2079,6 @@
}
}
}
},
"lb" : {
},
"List your tools here. 🍴" : {
"localizations" : {
@@ -2100,12 +2101,6 @@
}
}
}
},
"liter" : {
},
"litre" : {
},
"Log out" : {
"localizations" : {
@@ -2218,19 +2213,26 @@
}
},
"Marked ingredients could not be adjusted!" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Markierte Zutaten können nicht angepasst werden."
}
},
"mg" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "¡Los ingredientes marcados no pudieron ser ajustados!"
}
},
"milligram" : {
},
"milliliter" : {
},
"millilitre" : {
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Les ingrédients marqués n'ont pas pu être ajustés!"
}
}
}
},
"Minutes" : {
"localizations" : {
@@ -2323,10 +2325,26 @@
}
},
"Mixed fraction" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemischter Bruch"
}
},
"ml" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fracción mixta"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fraction mixte"
}
}
}
},
"More information" : {
"localizations" : {
@@ -2506,7 +2524,26 @@
}
},
"Number" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dezimalzahl"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Número"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nombre"
}
}
}
},
"Nutrition" : {
"localizations" : {
@@ -2617,12 +2654,6 @@
}
}
}
},
"ounce" : {
},
"oz" : {
},
"Parsing error" : {
"localizations" : {
@@ -2689,12 +2720,6 @@
}
}
}
},
"pinch" : {
},
"pint" : {
},
"Please check the entered URL." : {
"localizations" : {
@@ -2763,10 +2788,26 @@
}
},
"Point (e.g. 1.42)" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Punkt (z.B. 1.42)"
}
},
"pound" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Punto (por ejemplo, 1.42)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Point (par exemple, 1.42)"
}
}
}
},
"Preparation" : {
"localizations" : {
@@ -2835,15 +2876,6 @@
}
}
}
},
"pt" : {
},
"qt" : {
},
"quart" : {
},
"Recipe" : {
"localizations" : {
@@ -2933,6 +2965,28 @@
}
}
},
"Refresh" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktualisieren"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Refrescar"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Actualiser"
}
}
}
},
"Refresh all" : {
"localizations" : {
"de" : {
@@ -3332,9 +3386,6 @@
}
}
}
},
"smidgen" : {
},
"Sodium content" : {
"comment" : "Sodium content",
@@ -3364,7 +3415,7 @@
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hier fehlen Zutaten! 🥬"
"value" : "Hier ist Platz für Zutaten! 🥬"
}
},
"es" : {
@@ -3535,15 +3586,6 @@
}
}
}
},
"tablespoon" : {
},
"tbsp" : {
},
"teaspoon" : {
},
"Thank you for downloading" : {
"localizations" : {
@@ -3634,6 +3676,28 @@
}
}
},
"There are no recipes in this cookbook!" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hier gibt es momentan noch keine Rezepte zu sehen."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "No hay recetas en esta categoría."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Il n'y a pas de recettes dans cette catégorie."
}
}
}
},
"There was no name in the request given for the recipe. Cannot save the recipe." : {
"extractionState" : "stale",
"localizations" : {
@@ -3702,7 +3766,26 @@
}
},
"This setting will take effect after the app is restarted. It affects the adjustment of ingredient quantities." : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Diese Einstellung wird erst nach einem Neustart der App wirksam. Die Einstellung betrifft die Mengenberechnung der Rezeptzutaten."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Esta configuración surtirá efecto después de reiniciar la aplicación. Afecta el ajuste de las cantidades de ingredientes."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ce paramètre prendra effet après le redémarrage de l'application. Il affecte l'ajustement des quantités d'ingrédients."
}
}
}
},
"This website might not be currently supported. If this appears incorrect, you can use the support options in the app settings to raise awareness about this issue." : {
"localizations" : {
@@ -3882,9 +3965,6 @@
}
}
}
},
"tsp" : {
},
"Unable to complete action." : {
"localizations" : {

View File

@@ -19,6 +19,9 @@ struct RecipeListView: View {
@State var selectedRecipe: Recipe? = nil
var body: some View {
Group {
let recipes = recipesFiltered()
if !recipes.isEmpty {
List(recipesFiltered(), id: \.recipe_id) { recipe in
RecipeCardView(recipe: recipe)
.shadow(radius: 2)
@@ -34,6 +37,22 @@ struct RecipeListView: View {
.listRowSeparatorTint(.clear)
}
.listStyle(.plain)
} else {
VStack {
Text("There are no recipes in this cookbook!")
Button {
Task {
await appState.getCategories()
await appState.getCategory(named: categoryName, fetchMode: .preferServer)
}
} label: {
Text("Refresh")
.bold()
}
.buttonStyle(.bordered)
}.padding()
}
}
.searchable(text: $searchText, prompt: "Search recipes/keywords")
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)

View File

@@ -19,16 +19,23 @@ struct RecipeTabView: View {
List(selection: $viewModel.selectedCategory) {
// Categories
ForEach(appState.categories) { category in
if category.recipe_count != 0 {
NavigationLink(value: category) {
HStack(alignment: .center) {
if viewModel.selectedCategory != nil && category.name == viewModel.selectedCategory!.name {
if viewModel.selectedCategory != nil &&
category.name == viewModel.selectedCategory!.name {
Image(systemName: "book")
} else {
Image(systemName: "book.closed.fill")
}
Text(category.name == "*" ? String(localized: "Other") : category.name)
if category.name == "*" {
Text("Other")
.font(.system(size: 20, weight: .medium, design: .default))
} else {
Text(category.name)
.font(.system(size: 20, weight: .medium, design: .default))
}
Spacer()
Text("\(category.recipe_count)")
.font(.system(size: 15, weight: .bold, design: .default))
@@ -43,7 +50,6 @@ struct RecipeTabView: View {
}
}
}
}
.navigationTitle("Cookbooks")
.toolbar {
RecipeTabViewToolBar()
@@ -100,7 +106,7 @@ struct RecipeTabView: View {
fileprivate struct RecipeTabViewToolBar: ToolbarContent {
@EnvironmentObject var mainViewModel: AppState
@EnvironmentObject var appState: AppState
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
var body: some ToolbarContent {
@@ -111,11 +117,11 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent {
Task {
viewModel.presentLoadingIndicator = true
UserSettings.shared.lastUpdate = Date.distantPast
await mainViewModel.getCategories()
for category in mainViewModel.categories {
await mainViewModel.getCategory(named: category.name, fetchMode: .preferServer)
await appState.getCategories()
for category in appState.categories {
await appState.getCategory(named: category.name, fetchMode: .preferServer)
}
await mainViewModel.updateAllRecipeDetails()
await appState.updateAllRecipeDetails()
viewModel.presentLoadingIndicator = false
}
} label: {