// // 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) } } } }