Files
Nextcloud-Cookbook-iOS/Nextcloud Cookbook iOS Client/Views/MainView.swift
Hendrik Hogertz 1f7f19c74b Fix meal plan not populating on first login and add pull-to-refresh sync
Rewrite MealPlanSyncManager.performSync() (renamed from performInitialSync) to
discover _mealPlanAssignment metadata from all server recipes, not just locally-
known ones. On first sync all recipes are checked; on subsequent syncs only
recipes modified since lastMealPlanSyncDate are fetched (max 5 concurrent).

Trigger meal plan sync from pull-to-refresh on both the recipe and meal plan
tabs, and from the "Refresh all" toolbar button.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 11:40:31 +01:00

127 lines
4.6 KiB
Swift

//
// ContentView.swift
// Nextcloud Cookbook iOS Client
//
// Created by Vincent Meilinger on 06.09.23.
//
import SwiftUI
struct MainView: View {
@StateObject var appState = AppState()
@StateObject var groceryList = GroceryListManager()
@StateObject var mealPlan = MealPlanManager()
// Tab ViewModels
@StateObject var recipeViewModel = RecipeTabView.ViewModel()
@StateObject var searchViewModel = SearchTabView.ViewModel()
@ObservedObject private var userSettings = UserSettings.shared
@State private var selectedTab: Tab = .recipes
@Binding var pendingImportURL: String?
enum Tab {
case recipes, search, mealPlan, groceryList
}
var body: some View {
TabView(selection: $selectedTab) {
SwiftUI.Tab("Recipes", systemImage: "book.closed.fill", value: .recipes) {
RecipeTabView()
.environmentObject(recipeViewModel)
.environmentObject(appState)
.environmentObject(groceryList)
.environmentObject(mealPlan)
}
SwiftUI.Tab("Search", systemImage: "magnifyingglass", value: .search, role: .search) {
SearchTabView()
.environmentObject(searchViewModel)
.environmentObject(appState)
.environmentObject(groceryList)
.environmentObject(mealPlan)
}
SwiftUI.Tab("Meal Plan", systemImage: "calendar", value: .mealPlan) {
MealPlanTabView()
.environmentObject(mealPlan)
.environmentObject(appState)
.environmentObject(groceryList)
}
if userSettings.groceryListMode != GroceryListMode.appleReminders.rawValue {
SwiftUI.Tab("Grocery List", systemImage: "storefront", value: .groceryList) {
GroceryListTabView()
.environmentObject(groceryList)
}
}
}
.tabViewStyle(.sidebarAdaptable)
.modifier(TabBarMinimizeModifier())
.onChange(of: userSettings.groceryListMode) { _, newValue in
if newValue == GroceryListMode.appleReminders.rawValue && selectedTab == .groceryList {
selectedTab = .recipes
}
Task {
await groceryList.load()
}
}
.task {
recipeViewModel.presentLoadingIndicator = true
await appState.getCategories()
await appState.updateAllRecipeDetails()
// Preload category images
for category in appState.categories {
await appState.getCategoryImage(for: category.name)
}
// Load recently viewed recipes
await appState.loadRecentRecipes()
// Load category sorting data
await appState.loadCategoryAccessDates()
await appState.loadManualCategoryOrder()
// Open detail view for default category
if UserSettings.shared.defaultCategory != "" {
if let cat = appState.categories.first(where: { c in
if c.name == UserSettings.shared.defaultCategory {
return true
}
return false
}) {
recipeViewModel.selectedCategory = cat
}
}
await groceryList.load()
groceryList.configureSyncManager(appState: appState)
if UserSettings.shared.grocerySyncEnabled {
await groceryList.syncManager?.performSync()
}
await mealPlan.load()
mealPlan.configureSyncManager(appState: appState)
if UserSettings.shared.mealPlanSyncEnabled {
await mealPlan.syncManager?.performSync()
}
recipeViewModel.presentLoadingIndicator = false
}
.onChange(of: pendingImportURL) { _, newURL in
guard let url = newURL, !url.isEmpty else { return }
selectedTab = .recipes
recipeViewModel.pendingImportURL = url
// Dismiss any currently open import sheet before re-presenting
if recipeViewModel.showImportURLSheet {
recipeViewModel.showImportURLSheet = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
recipeViewModel.showImportURLSheet = true
}
} else {
recipeViewModel.showImportURLSheet = true
}
}
}
}