209 lines
7.4 KiB
Swift
209 lines
7.4 KiB
Swift
//
|
|
// RecipeTabView.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
// Created by Vincent Meilinger on 23.01.24.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
import SwiftData
|
|
|
|
|
|
struct RecipeTabView: View {
|
|
//@State var cookbookState: CookbookState = CookbookState()
|
|
@Environment(\.modelContext) var modelContext
|
|
@Query var recipes: [Recipe]
|
|
@State var categories: [(String, Int)] = []
|
|
@State private var selectedRecipe: Recipe?
|
|
@State private var selectedCategory: String? = "*"
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
List(selection: $selectedCategory) {
|
|
CategoryListItem(category: "All Recipes", count: recipes.count, isSelected: selectedCategory == "*")
|
|
.tag("*") // Tag nil to select all recipes
|
|
|
|
Section("Categories") {
|
|
ForEach(categories, id: \.0.self) { category in
|
|
CategoryListItem(category: category.0, count: category.1, isSelected: selectedCategory == category.0)
|
|
.tag(category.0)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Categories")
|
|
} content: {
|
|
RecipeListView(selectedCategory: $selectedCategory, selectedRecipe: $selectedRecipe)
|
|
} detail: {
|
|
// Use a conditional view based on selection
|
|
if let selectedRecipe {
|
|
//RecipeDetailView(recipe: recipe) // Create a dedicated detail view
|
|
RecipeView(recipe: selectedRecipe, viewModel: RecipeView.ViewModel(recipe: selectedRecipe))
|
|
} else {
|
|
ContentUnavailableView("Select a Recipe", systemImage: "fork.knife.circle")
|
|
}
|
|
}
|
|
.task {
|
|
initCategories()
|
|
return
|
|
do {
|
|
try modelContext.delete(model: Recipe.self)
|
|
} catch {
|
|
print("Failed to delete recipes and categories.")
|
|
}
|
|
|
|
guard let categories = await CookbookApiV1.getCategories(auth: UserSettings.shared.authString).0 else { return }
|
|
for category in categories {
|
|
guard let recipeStubs = await CookbookApiV1.getCategory(auth: UserSettings.shared.authString, named: category.name).0 else { return }
|
|
for recipeStub in recipeStubs {
|
|
guard let recipe = await CookbookApiV1.getRecipe(auth: UserSettings.shared.authString, id: recipeStub.id).0 else { return }
|
|
modelContext.insert(recipe)
|
|
}
|
|
}
|
|
|
|
}/*
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button(action: {
|
|
//cookbookState.showSettings = true
|
|
}) {
|
|
Label("Settings", systemImage: "gearshape")
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
func initCategories() {
|
|
// Load Categories
|
|
var categoryDict: [String: Int] = [:]
|
|
for recipe in recipes {
|
|
// Ensure "Uncategorized" is a valid category if used
|
|
if !recipe.category.isEmpty {
|
|
categoryDict[recipe.category, default: 0] += 1
|
|
} else {
|
|
categoryDict["Other", default: 0] += 1
|
|
}
|
|
}
|
|
categories = categoryDict.map {
|
|
($0.key, $0.value)
|
|
}.sorted { $0.0 < $1.0 }
|
|
}
|
|
|
|
class ViewModel: ObservableObject {
|
|
@Published var presentEditView: Bool = false
|
|
@Published var presentSettingsView: Bool = false
|
|
|
|
@Published var presentLoadingIndicator: Bool = false
|
|
@Published var presentConnectionPopover: Bool = false
|
|
@Published var serverConnection: Bool = false
|
|
}
|
|
}
|
|
|
|
|
|
fileprivate struct CategoryListItem: View {
|
|
var category: String
|
|
var count: Int
|
|
var isSelected: Bool
|
|
|
|
var body: some View {
|
|
HStack(alignment: .center) {
|
|
if isSelected {
|
|
Image(systemName: "book")
|
|
} else {
|
|
Image(systemName: "book.closed.fill")
|
|
}
|
|
|
|
Text(category)
|
|
.font(.system(size: 20, weight: .medium, design: .default))
|
|
|
|
Spacer()
|
|
Text("\(count)")
|
|
.font(.system(size: 15, weight: .bold, design: .default))
|
|
.foregroundStyle(Color.background)
|
|
.frame(width: 25, height: 25, alignment: .center)
|
|
.minimumScaleFactor(0.5)
|
|
.background {
|
|
Circle()
|
|
.foregroundStyle(Color.secondary)
|
|
}
|
|
}.padding(7)
|
|
}
|
|
}
|
|
/*
|
|
fileprivate struct RecipeTabViewToolBar: ToolbarContent {
|
|
@EnvironmentObject var appState: AppState
|
|
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
|
|
|
|
var body: some ToolbarContent {
|
|
// Top left menu toolbar item
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Menu {
|
|
Button {
|
|
Task {
|
|
viewModel.presentLoadingIndicator = true
|
|
UserSettings.shared.lastUpdate = Date.distantPast
|
|
await appState.getCategories()
|
|
for category in appState.categories {
|
|
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
|
}
|
|
await appState.updateAllRecipeDetails()
|
|
viewModel.presentLoadingIndicator = false
|
|
}
|
|
} label: {
|
|
Text("Refresh all")
|
|
Image(systemName: "icloud.and.arrow.down")
|
|
}
|
|
|
|
Button {
|
|
viewModel.presentSettingsView = true
|
|
} label: {
|
|
Text("Settings")
|
|
Image(systemName: "gearshape")
|
|
}
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
}
|
|
|
|
// Server connection indicator
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button {
|
|
print("Check server connection")
|
|
viewModel.presentConnectionPopover = true
|
|
} label: {
|
|
if viewModel.presentLoadingIndicator {
|
|
ProgressView()
|
|
} else if viewModel.serverConnection {
|
|
Image(systemName: "checkmark.icloud")
|
|
} else {
|
|
Image(systemName: "xmark.icloud")
|
|
}
|
|
}.popover(isPresented: $viewModel.presentConnectionPopover) {
|
|
VStack(alignment: .leading) {
|
|
Text(viewModel.serverConnection ? LocalizedStringKey("Connected to server.") : LocalizedStringKey("Unable to connect to server."))
|
|
.bold()
|
|
|
|
Text("Last updated: \(DateFormatter.utcToString(date: UserSettings.shared.lastUpdate))")
|
|
.font(.caption)
|
|
.foregroundStyle(Color.secondary)
|
|
}
|
|
.padding()
|
|
.presentationCompactAdaptation(.popover)
|
|
}
|
|
}
|
|
|
|
// Create new recipes
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button {
|
|
print("Add new recipe")
|
|
viewModel.presentEditView = true
|
|
} label: {
|
|
Image(systemName: "plus.circle.fill")
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
*/
|
|
|