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>
83 lines
3.0 KiB
Swift
83 lines
3.0 KiB
Swift
//
|
|
// CategoryCardView.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct CategoryCardView: View {
|
|
@EnvironmentObject var appState: AppState
|
|
let category: Category
|
|
var isSelected: Bool = false
|
|
|
|
@State private var imageLoaded = false
|
|
|
|
private var displayName: String {
|
|
category.name == "*" ? String(localized: "Other") : category.name
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack(alignment: .bottomLeading) {
|
|
// Background image or gradient fallback
|
|
if let image = appState.categoryImages[category.name] {
|
|
Image(uiImage: image)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 140, maxHeight: 140)
|
|
.clipped()
|
|
.opacity(imageLoaded ? 1 : 0)
|
|
.animation(.easeIn(duration: 0.3), value: imageLoaded)
|
|
.onAppear { imageLoaded = true }
|
|
} else {
|
|
LinearGradient(
|
|
gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 140, maxHeight: 140)
|
|
.overlay(alignment: .center) {
|
|
Image(systemName: "book.closed.fill")
|
|
.font(.system(size: 36))
|
|
.foregroundStyle(.white.opacity(0.5))
|
|
}
|
|
}
|
|
|
|
// Bottom scrim with text
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Spacer()
|
|
LinearGradient(
|
|
colors: [.clear, .black.opacity(0.6)],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
.frame(height: 60)
|
|
.overlay(alignment: .bottomLeading) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(displayName)
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundStyle(.white)
|
|
.lineLimit(1)
|
|
Text("\(category.recipe_count) recipes")
|
|
.font(.system(size: 12, weight: .semibold))
|
|
.foregroundStyle(.white.opacity(0.85))
|
|
}
|
|
.padding(.horizontal, 10)
|
|
.padding(.bottom, 8)
|
|
}
|
|
}
|
|
}
|
|
.frame(height: 140)
|
|
.clipShape(RoundedRectangle(cornerRadius: 17))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 17)
|
|
.stroke(isSelected ? Color.nextcloudBlue : .clear, lineWidth: 3)
|
|
)
|
|
.shadow(color: .black.opacity(0.1), radius: 4, y: 2)
|
|
.task {
|
|
if appState.categoryImages[category.name] == nil {
|
|
await appState.getCategoryImage(for: category.name)
|
|
}
|
|
}
|
|
}
|
|
}
|