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>
113 lines
4.2 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|