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>
111 lines
3.6 KiB
Swift
111 lines
3.6 KiB
Swift
//
|
|
// RecentRecipesSection.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct RecentRecipesSection: View {
|
|
@EnvironmentObject var appState: AppState
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Text("Recently Viewed")
|
|
.font(.title2)
|
|
.bold()
|
|
Spacer()
|
|
Button {
|
|
appState.clearRecentRecipes()
|
|
} label: {
|
|
Text("Clear")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.padding(.horizontal)
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
LazyHStack(spacing: 12) {
|
|
ForEach(appState.recentRecipes) { recipe in
|
|
NavigationLink(value: recipe) {
|
|
RecentRecipeCard(recipe: recipe)
|
|
.environmentObject(appState)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
.padding(.horizontal)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct RecentRecipeCard: View {
|
|
@EnvironmentObject var appState: AppState
|
|
let recipe: Recipe
|
|
@State private var thumbnail: UIImage?
|
|
|
|
private var keywordsText: String? {
|
|
guard let keywords = recipe.keywords, !keywords.isEmpty else { return nil }
|
|
let items = keywords.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
|
|
guard !items.isEmpty else { return nil }
|
|
return items.prefix(3).joined(separator: " \u{00B7} ")
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Thumbnail
|
|
if let thumbnail {
|
|
Image(uiImage: thumbnail)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
.frame(width: 160, height: 120)
|
|
.clipped()
|
|
} else {
|
|
LinearGradient(
|
|
gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
.frame(width: 160, height: 120)
|
|
.overlay {
|
|
Image(systemName: "fork.knife")
|
|
.font(.title2)
|
|
.foregroundStyle(.white.opacity(0.6))
|
|
}
|
|
}
|
|
|
|
// Text content
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(recipe.name)
|
|
.font(.subheadline)
|
|
.fontWeight(.medium)
|
|
.lineLimit(2)
|
|
.multilineTextAlignment(.leading)
|
|
.foregroundStyle(.primary)
|
|
|
|
if let keywordsText {
|
|
Text(keywordsText)
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 6)
|
|
}
|
|
.frame(width: 160)
|
|
.background(Color.backgroundHighlight)
|
|
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
.shadow(color: .black.opacity(0.08), radius: 4, y: 2)
|
|
.task {
|
|
thumbnail = await appState.getImage(
|
|
id: recipe.recipe_id,
|
|
size: .THUMB,
|
|
fetchMode: UserSettings.shared.storeThumb ? .preferLocal : .onlyServer
|
|
)
|
|
}
|
|
}
|
|
}
|