Add cross-device grocery list sync via Nextcloud Cookbook API

Store a _groceryState JSON field on each recipe to track which
ingredients have been added, completed, or removed. Uses per-item
last-writer-wins conflict resolution with ISO 8601 timestamps.
Debounced push (2s) avoids excessive API calls; pull reconciles
on recipe open and app launch. Includes a settings toggle to
enable/disable sync.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 04:14:02 +01:00
parent 501434bd0e
commit 5890dbcad4
11 changed files with 323 additions and 10 deletions

View File

@@ -72,6 +72,8 @@
D1A0CE012D0A000100000001 /* GroceryListMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE002D0A000100000001 /* GroceryListMode.swift */; };
D1A0CE032D0A000200000002 /* RemindersGroceryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */; };
D1A0CE052D0A000300000003 /* GroceryListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0CE042D0A000300000003 /* GroceryListManager.swift */; };
E1B0CF072D0B000400000004 /* GroceryStateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B0CF062D0B000400000004 /* GroceryStateModels.swift */; };
E1B0CF092D0B000500000005 /* GroceryStateSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B0CF082D0B000500000005 /* GroceryStateSyncManager.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -161,6 +163,8 @@
D1A0CE002D0A000100000001 /* GroceryListMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryListMode.swift; sourceTree = "<group>"; };
D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersGroceryStore.swift; sourceTree = "<group>"; };
D1A0CE042D0A000300000003 /* GroceryListManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryListManager.swift; sourceTree = "<group>"; };
E1B0CF062D0B000400000004 /* GroceryStateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryStateModels.swift; sourceTree = "<group>"; };
E1B0CF082D0B000500000005 /* GroceryStateSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroceryStateSyncManager.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -301,6 +305,8 @@
D1A0CE002D0A000100000001 /* GroceryListMode.swift */,
D1A0CE022D0A000200000002 /* RemindersGroceryStore.swift */,
D1A0CE042D0A000300000003 /* GroceryListManager.swift */,
E1B0CF062D0B000400000004 /* GroceryStateModels.swift */,
E1B0CF082D0B000500000005 /* GroceryStateSyncManager.swift */,
);
path = Data;
sourceTree = "<group>";
@@ -638,6 +644,8 @@
D1A0CE012D0A000100000001 /* GroceryListMode.swift in Sources */,
D1A0CE032D0A000200000002 /* RemindersGroceryStore.swift in Sources */,
D1A0CE052D0A000300000003 /* GroceryListManager.swift in Sources */,
E1B0CF072D0B000400000004 /* GroceryStateModels.swift in Sources */,
E1B0CF092D0B000500000005 /* GroceryStateSyncManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};