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

@@ -0,0 +1,58 @@
//
// GroceryStateModels.swift
// Nextcloud Cookbook iOS Client
//
import Foundation
/// Tracks grocery list state for a recipe, stored as `_groceryState` in the recipe JSON on the server.
struct GroceryState: Codable {
var version: Int = 1
var lastModified: String
var items: [String: GroceryItemState]
init(lastModified: String = GroceryStateDate.now(), items: [String: GroceryItemState] = [:]) {
self.version = 1
self.lastModified = lastModified
self.items = items
}
}
struct GroceryItemState: Codable {
enum Status: String, Codable {
case added
case completed
case removed
}
var status: Status
var addedAt: String
var modifiedAt: String
init(status: Status, addedAt: String = GroceryStateDate.now(), modifiedAt: String = GroceryStateDate.now()) {
self.status = status
self.addedAt = addedAt
self.modifiedAt = modifiedAt
}
}
/// ISO 8601 date helpers. Dates are stored as strings to avoid coupling to a parent encoder's date strategy.
enum GroceryStateDate {
private static let formatter: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
return f
}()
static func now() -> String {
formatter.string(from: Date())
}
static func date(from string: String) -> Date? {
formatter.date(from: string)
}
static func string(from date: Date) -> String {
formatter.string(from: date)
}
}