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>
This commit is contained in:
@@ -60,27 +60,75 @@ class MealPlanSyncManager {
|
||||
mealPlanManager.reconcileFromServer(serverAssignment: serverAssignment, recipeId: recipeId, recipeName: recipeName)
|
||||
}
|
||||
|
||||
// MARK: - Initial Sync
|
||||
// MARK: - Full Sync
|
||||
|
||||
func performInitialSync() async {
|
||||
func performSync() async {
|
||||
guard let appState, let mealPlanManager else { return }
|
||||
|
||||
let recipeIds = Array(mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId))
|
||||
let uniqueIds = Array(Set(recipeIds))
|
||||
|
||||
for recipeId in uniqueIds {
|
||||
guard let recipeIdInt = Int(recipeId) else { continue }
|
||||
|
||||
// Phase 1: Push locally-known meal plan state
|
||||
let localRecipeIds = Array(Set(
|
||||
mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId)
|
||||
))
|
||||
for recipeId in localRecipeIds {
|
||||
await pushMealPlanState(forRecipeId: recipeId)
|
||||
}
|
||||
|
||||
if let serverRecipe = await appState.getRecipe(id: recipeIdInt, fetchMode: .onlyServer) {
|
||||
reconcileFromServer(
|
||||
serverAssignment: serverRecipe.mealPlanAssignment,
|
||||
recipeId: recipeId,
|
||||
recipeName: serverRecipe.name
|
||||
)
|
||||
// Phase 2: Discover meal plan assignments from server
|
||||
let allRecipes = await appState.getRecipes()
|
||||
let lastSync = UserSettings.shared.lastMealPlanSyncDate
|
||||
|
||||
// Filter to recipes modified since last sync
|
||||
let recipesToCheck: [Recipe]
|
||||
if let lastSync {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
recipesToCheck = allRecipes.filter { recipe in
|
||||
guard let dateStr = recipe.dateModified,
|
||||
let date = formatter.date(from: dateStr) else { return true }
|
||||
return date > lastSync
|
||||
}
|
||||
} else {
|
||||
recipesToCheck = allRecipes // First sync: check all
|
||||
}
|
||||
|
||||
// Fetch details concurrently (max 5 parallel)
|
||||
await withTaskGroup(of: (String, String, MealPlanAssignment?)?.self) { group in
|
||||
var iterator = recipesToCheck.makeIterator()
|
||||
let maxConcurrent = 5
|
||||
var active = 0
|
||||
|
||||
while active < maxConcurrent, let recipe = iterator.next() {
|
||||
active += 1
|
||||
group.addTask {
|
||||
guard let detail = await appState.getRecipe(
|
||||
id: recipe.recipe_id, fetchMode: .onlyServer
|
||||
) else { return nil }
|
||||
return (String(recipe.recipe_id), detail.name, detail.mealPlanAssignment)
|
||||
}
|
||||
}
|
||||
|
||||
for await result in group {
|
||||
if let (recipeId, recipeName, assignment) = result,
|
||||
let assignment, !assignment.dates.isEmpty {
|
||||
mealPlanManager.reconcileFromServer(
|
||||
serverAssignment: assignment,
|
||||
recipeId: recipeId,
|
||||
recipeName: recipeName
|
||||
)
|
||||
}
|
||||
if let recipe = iterator.next() {
|
||||
group.addTask {
|
||||
guard let detail = await appState.getRecipe(
|
||||
id: recipe.recipe_id, fetchMode: .onlyServer
|
||||
) else { return nil }
|
||||
return (String(recipe.recipe_id), detail.name, detail.mealPlanAssignment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserSettings.shared.lastMealPlanSyncDate = Date()
|
||||
}
|
||||
|
||||
// MARK: - Merge Logic
|
||||
|
||||
Reference in New Issue
Block a user