Add user-facing appearance setting (System/Light/Dark) wired via preferredColorScheme at the app root. Replace hardcoded .black tints and foreground styles with .primary so toolbar buttons and text remain visible in dark mode. Remove profile picture from settings and SwiftSoup from acknowledgements. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
125 lines
4.7 KiB
Swift
125 lines
4.7 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: GroceryListManager
|
|
@EnvironmentObject var mealPlan: MealPlanManager
|
|
@State var categoryName: String
|
|
@State var searchText: String = ""
|
|
var onCreateNew: () -> Void
|
|
var onImportFromURL: () -> Void
|
|
@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(.primary)
|
|
}.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)
|
|
.environmentObject(mealPlan)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
Button {
|
|
onCreateNew()
|
|
} label: {
|
|
Label("Create New Recipe", systemImage: "square.and.pencil")
|
|
}
|
|
Button {
|
|
onImportFromURL()
|
|
} label: {
|
|
Label("Import from URL", systemImage: "link")
|
|
}
|
|
} 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
|
|
}
|
|
}
|
|
}
|