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>
This commit is contained in:
2026-02-15 01:47:16 +01:00
parent 7c824b492e
commit c8ddb098d1
11 changed files with 792 additions and 121 deletions

View File

@@ -10,7 +10,6 @@ import SwiftUI
struct RecipeListView: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var groceryList: GroceryList
@@ -18,45 +17,62 @@ struct RecipeListView: View {
@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 {
List(recipesFiltered(), id: \.recipe_id) { recipe in
RecipeCardView(recipe: recipe)
.shadow(radius: 2)
.background(
NavigationLink(value: recipe) {
EmptyView()
}
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)
.opacity(0)
)
.frame(height: 85)
.listRowInsets(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.listRowSeparatorTint(.clear)
}
}
.padding(.horizontal)
}
.padding(.vertical)
}
.listStyle(.plain)
} else {
VStack {
Text("There are no recipes in this cookbook!")
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: {
Text("Refresh")
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)
@@ -84,7 +100,7 @@ struct RecipeListView: View {
)
}
}
func recipesFiltered() -> [Recipe] {
guard let recipes = appState.recipes[categoryName] else { return [] }
guard searchText != "" else { return recipes }