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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/hendrik.hogertz.xcuserdatad/UserInterfaceState.xcuserstate
|
Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/hendrik.hogertz.xcuserdatad/UserInterfaceState.xcuserstate
|
||||||
Nextcloud Cookbook iOS Client.xcodeproj/xcuserdata/hendrik.hogertz.xcuserdatad/xcschemes/xcschememanagement.plist
|
Nextcloud Cookbook iOS Client.xcodeproj/xcuserdata/hendrik.hogertz.xcuserdatad/xcschemes/xcschememanagement.plist
|
||||||
|
Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/hendrik.hogertz.xcuserdatad/IDEFindNavigatorScopes.plist
|
||||||
|
|||||||
@@ -933,13 +933,14 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist";
|
INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Cookbook;
|
INFOPLIST_KEY_CFBundleDisplayName = Cookbook;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";
|
||||||
|
INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "This app uses Reminders to save your grocery list items.";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
@@ -955,7 +956,7 @@
|
|||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.10.1;
|
MARKETING_VERSION = 1.10.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
@@ -977,13 +978,14 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Nextcloud Cookbook iOS Client/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist";
|
INFOPLIST_FILE = "Nextcloud-Cookbook-iOS-Client-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Cookbook;
|
INFOPLIST_KEY_CFBundleDisplayName = Cookbook;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";
|
||||||
|
INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "This app uses Reminders to save your grocery list items.";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
@@ -999,7 +1001,7 @@
|
|||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.10.1;
|
MARKETING_VERSION = 1.10.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
@@ -1017,12 +1019,12 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-ClientTests";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
@@ -1040,12 +1042,12 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-ClientTests";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
@@ -1062,12 +1064,12 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-ClientUITests";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
@@ -1084,12 +1086,12 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-ClientUITests";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
@@ -1107,7 +1109,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
@@ -1121,7 +1123,7 @@
|
|||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook.shareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1144,7 +1146,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = EF2ABA36D9;
|
DEVELOPMENT_TEAM = JGFU6788BP;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
@@ -1158,7 +1160,7 @@
|
|||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = eu.hogertz.cookbook.shareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class GroceryStateSyncManager {
|
|||||||
// MARK: - Initial Sync
|
// MARK: - Initial Sync
|
||||||
|
|
||||||
/// Pushes any local-only items and reconciles server items on app launch.
|
/// Pushes any local-only items and reconciles server items on app launch.
|
||||||
func performInitialSync() async {
|
func performSync() async {
|
||||||
guard let appState, let groceryManager else { return }
|
guard let appState, let groceryManager else { return }
|
||||||
|
|
||||||
await loadPendingRemovals()
|
await loadPendingRemovals()
|
||||||
@@ -208,7 +208,7 @@ class GroceryStateSyncManager {
|
|||||||
// MARK: - Pending Removal Tracking
|
// MARK: - Pending Removal Tracking
|
||||||
|
|
||||||
/// Records a recipe ID whose grocery items were fully removed, so that
|
/// Records a recipe ID whose grocery items were fully removed, so that
|
||||||
/// `performInitialSync` can push the deletion even after the key disappears
|
/// `performSync` can push the deletion even after the key disappears
|
||||||
/// from `groceryDict`.
|
/// from `groceryDict`.
|
||||||
func trackPendingRemoval(recipeId: String) {
|
func trackPendingRemoval(recipeId: String) {
|
||||||
pendingRemovalRecipeIds.insert(recipeId)
|
pendingRemovalRecipeIds.insert(recipeId)
|
||||||
|
|||||||
@@ -60,28 +60,76 @@ class MealPlanSyncManager {
|
|||||||
mealPlanManager.reconcileFromServer(serverAssignment: serverAssignment, recipeId: recipeId, recipeName: recipeName)
|
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 }
|
guard let appState, let mealPlanManager else { return }
|
||||||
|
|
||||||
let recipeIds = Array(mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId))
|
// Phase 1: Push locally-known meal plan state
|
||||||
let uniqueIds = Array(Set(recipeIds))
|
let localRecipeIds = Array(Set(
|
||||||
|
mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId)
|
||||||
for recipeId in uniqueIds {
|
))
|
||||||
guard let recipeIdInt = Int(recipeId) else { continue }
|
for recipeId in localRecipeIds {
|
||||||
|
|
||||||
await pushMealPlanState(forRecipeId: recipeId)
|
await pushMealPlanState(forRecipeId: recipeId)
|
||||||
|
}
|
||||||
|
|
||||||
if let serverRecipe = await appState.getRecipe(id: recipeIdInt, fetchMode: .onlyServer) {
|
// Phase 2: Discover meal plan assignments from server
|
||||||
reconcileFromServer(
|
let allRecipes = await appState.getRecipes()
|
||||||
serverAssignment: serverRecipe.mealPlanAssignment,
|
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,
|
recipeId: recipeId,
|
||||||
recipeName: serverRecipe.name
|
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
|
// MARK: - Merge Logic
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,12 @@ class UserSettings: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var lastMealPlanSyncDate: Date? {
|
||||||
|
didSet {
|
||||||
|
UserDefaults.standard.set(lastMealPlanSyncDate, forKey: "lastMealPlanSyncDate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
|
self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
|
||||||
self.token = UserDefaults.standard.object(forKey: "token") as? String ?? ""
|
self.token = UserDefaults.standard.object(forKey: "token") as? String ?? ""
|
||||||
@@ -203,6 +209,7 @@ class UserSettings: ObservableObject {
|
|||||||
self.categorySortAscending = UserDefaults.standard.object(forKey: "categorySortAscending") as? Bool ?? true
|
self.categorySortAscending = UserDefaults.standard.object(forKey: "categorySortAscending") as? Bool ?? true
|
||||||
self.recipeSortMode = UserDefaults.standard.object(forKey: "recipeSortMode") as? String ?? RecipeSortMode.recentlyAdded.rawValue
|
self.recipeSortMode = UserDefaults.standard.object(forKey: "recipeSortMode") as? String ?? RecipeSortMode.recentlyAdded.rawValue
|
||||||
self.recipeSortAscending = UserDefaults.standard.object(forKey: "recipeSortAscending") as? Bool ?? true
|
self.recipeSortAscending = UserDefaults.standard.object(forKey: "recipeSortAscending") as? Bool ?? true
|
||||||
|
self.lastMealPlanSyncDate = UserDefaults.standard.object(forKey: "lastMealPlanSyncDate") as? Date
|
||||||
|
|
||||||
if authString == "" {
|
if authString == "" {
|
||||||
if token != "" && username != "" {
|
if token != "" && username != "" {
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ struct MainView: View {
|
|||||||
await groceryList.load()
|
await groceryList.load()
|
||||||
groceryList.configureSyncManager(appState: appState)
|
groceryList.configureSyncManager(appState: appState)
|
||||||
if UserSettings.shared.grocerySyncEnabled {
|
if UserSettings.shared.grocerySyncEnabled {
|
||||||
await groceryList.syncManager?.performInitialSync()
|
await groceryList.syncManager?.performSync()
|
||||||
}
|
}
|
||||||
await mealPlan.load()
|
await mealPlan.load()
|
||||||
mealPlan.configureSyncManager(appState: appState)
|
mealPlan.configureSyncManager(appState: appState)
|
||||||
if UserSettings.shared.mealPlanSyncEnabled {
|
if UserSettings.shared.mealPlanSyncEnabled {
|
||||||
await mealPlan.syncManager?.performInitialSync()
|
await mealPlan.syncManager?.performSync()
|
||||||
}
|
}
|
||||||
recipeViewModel.presentLoadingIndicator = false
|
recipeViewModel.presentLoadingIndicator = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,15 @@ struct MealPlanTabView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Meal Plan")
|
.navigationTitle("Meal Plan")
|
||||||
|
.refreshable {
|
||||||
|
await appState.getCategories()
|
||||||
|
for category in appState.categories {
|
||||||
|
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
||||||
|
}
|
||||||
|
if UserSettings.shared.mealPlanSyncEnabled {
|
||||||
|
await mealPlan.syncManager?.performSync()
|
||||||
|
}
|
||||||
|
}
|
||||||
.navigationDestination(for: Recipe.self) { recipe in
|
.navigationDestination(for: Recipe.self) { recipe in
|
||||||
RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe))
|
RecipeView(viewModel: RecipeView.ViewModel(recipe: recipe))
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
|
|||||||
@@ -286,6 +286,9 @@ struct RecipeTabView: View {
|
|||||||
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
||||||
await appState.getCategoryImage(for: category.name)
|
await appState.getCategoryImage(for: category.name)
|
||||||
}
|
}
|
||||||
|
if UserSettings.shared.mealPlanSyncEnabled {
|
||||||
|
await mealPlan.syncManager?.performSync()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +345,7 @@ struct RecipeTabView: View {
|
|||||||
fileprivate struct RecipeTabViewToolBar: ToolbarContent {
|
fileprivate struct RecipeTabViewToolBar: ToolbarContent {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
|
@EnvironmentObject var viewModel: RecipeTabView.ViewModel
|
||||||
|
@EnvironmentObject var mealPlan: MealPlanManager
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
// Top left menu toolbar item
|
// Top left menu toolbar item
|
||||||
@@ -356,6 +360,9 @@ fileprivate struct RecipeTabViewToolBar: ToolbarContent {
|
|||||||
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
await appState.getCategory(named: category.name, fetchMode: .preferServer)
|
||||||
}
|
}
|
||||||
await appState.updateAllRecipeDetails()
|
await appState.updateAllRecipeDetails()
|
||||||
|
if UserSettings.shared.mealPlanSyncEnabled {
|
||||||
|
await mealPlan.syncManager?.performSync()
|
||||||
|
}
|
||||||
viewModel.presentLoadingIndicator = false
|
viewModel.presentLoadingIndicator = false
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -13,7 +13,5 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>NSRemindersFullAccessUsageDescription</key>
|
|
||||||
<string>This app uses Reminders to save your grocery list items.</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Reference in New Issue
Block a user