Files
Nextcloud-Cookbook-iOS/Nextcloud Cookbook iOS Client/Views/Recipes/RecipeListView.swift
Hendrik Hogertz c8ddb098d1 Redesign search tab, add category cards, recent recipes, and complete German translations
Overhaul SearchTabView with search history, empty/no-results states, and dynamic
navigation title. Extract CategoryCardView and RecentRecipesSection into standalone
views. Update RecipeTabView, RecipeListView, RecipeCardView, and MainView for the
modernized UI. Add all 12 missing German translations in Localizable.xcstrings.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 01:47:16 +01:00

113 lines
4.2 KiB
Swift

//
// CategoryDetailView.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 15.09.23.
//
import Foundation
import SwiftUI
struct RecipeListView: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var groceryList: GroceryList
@State var categoryName: String
@State var searchText: String = ""
@Binding var showEditView: Bool
@State var selectedRecipe: Recipe? = nil
private let gridColumns = [GridItem(.adaptive(minimum: 160), spacing: 12)]
var body: some View {
Group {
let recipes = recipesFiltered()
if !recipes.isEmpty {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
Text("\(recipes.count) recipes")
.font(.subheadline)
.foregroundStyle(.secondary)
.padding(.horizontal)
LazyVGrid(columns: gridColumns, spacing: 12) {
ForEach(recipes, id: \.recipe_id) { recipe in
NavigationLink(value: recipe) {
RecipeCardView(recipe: recipe)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
}
.padding(.vertical)
}
} else {
VStack(spacing: 16) {
Image(systemName: "fork.knife")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("No recipes in this cookbook")
.font(.headline)
.foregroundStyle(.secondary)
Text("Recipes will appear here once they are added to this category.")
.font(.subheadline)
.foregroundStyle(.tertiary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
Button {
Task {
await appState.getCategories()
await appState.getCategory(named: categoryName, fetchMode: .preferServer)
}
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.bold()
}
.buttonStyle(.bordered)
.tint(.nextcloudBlue)
}.padding()
}
}
.searchable(text: $searchText, prompt: "Search recipes/keywords")
.navigationTitle(categoryName == "*" ? String(localized: "Other") : categoryName)
.navigationDestination(for: Recipe.self) { recipe in
RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe))
.environmentObject(appState)
.environmentObject(groceryList)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showEditView = true
} label: {
Image(systemName: "plus.circle.fill")
}
}
}
.task {
await appState.getCategory(
named: categoryName,
fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer
)
}
.refreshable {
await appState.getCategory(
named: categoryName,
fetchMode: UserSettings.shared.storeRecipes ? .preferServer : .onlyServer
)
}
}
func recipesFiltered() -> [Recipe] {
guard let recipes = appState.recipes[categoryName] else { return [] }
guard searchText != "" else { return recipes }
return recipes.filter { recipe in
recipe.name.lowercased().contains(searchText.lowercased()) || // check name for occurence of search term
(recipe.keywords != nil && recipe.keywords!.lowercased().contains(searchText.lowercased())) // check keywords for search term
}
}
}