Introduces weekly meal planning with a calendar-based tab view, per-recipe date assignments synced via Nextcloud Cookbook custom metadata, and 30-day automatic pruning of old entries on load, save, and sync merge. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
84 lines
2.1 KiB
Swift
84 lines
2.1 KiB
Swift
//
|
|
// MealPlanModels.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Tracks meal plan assignments for a recipe, stored as `_mealPlanAssignment` in the recipe JSON on the server.
|
|
struct MealPlanAssignment: Codable {
|
|
var version: Int = 1
|
|
var lastModified: String
|
|
var dates: [String: MealPlanDateEntry]
|
|
|
|
init(lastModified: String = MealPlanDate.now(), dates: [String: MealPlanDateEntry] = [:]) {
|
|
self.version = 1
|
|
self.lastModified = lastModified
|
|
self.dates = dates
|
|
}
|
|
}
|
|
|
|
struct MealPlanDateEntry: Codable {
|
|
enum Status: String, Codable {
|
|
case assigned
|
|
case removed
|
|
}
|
|
|
|
var status: Status
|
|
var mealType: String?
|
|
var modifiedAt: String
|
|
|
|
init(status: Status, mealType: String? = nil, modifiedAt: String = MealPlanDate.now()) {
|
|
self.status = status
|
|
self.mealType = mealType
|
|
self.modifiedAt = modifiedAt
|
|
}
|
|
}
|
|
|
|
/// ISO 8601 date helpers for meal plan dates.
|
|
enum MealPlanDate {
|
|
private static let isoFormatter: ISO8601DateFormatter = {
|
|
let f = ISO8601DateFormatter()
|
|
f.formatOptions = [.withInternetDateTime]
|
|
return f
|
|
}()
|
|
|
|
private static let dayFormatter: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.dateFormat = "yyyy-MM-dd"
|
|
f.timeZone = .current
|
|
return f
|
|
}()
|
|
|
|
static func now() -> String {
|
|
isoFormatter.string(from: Date())
|
|
}
|
|
|
|
static func date(from string: String) -> Date? {
|
|
isoFormatter.date(from: string)
|
|
}
|
|
|
|
static func string(from date: Date) -> String {
|
|
isoFormatter.string(from: date)
|
|
}
|
|
|
|
static func dayString(from date: Date) -> String {
|
|
dayFormatter.string(from: date)
|
|
}
|
|
|
|
static func dateFromDay(_ dayString: String) -> Date? {
|
|
dayFormatter.date(from: dayString)
|
|
}
|
|
}
|
|
|
|
/// Local-only aggregated view struct used by the UI.
|
|
struct MealPlanEntry: Identifiable {
|
|
let recipeId: String
|
|
let recipeName: String
|
|
let date: Date
|
|
let dateString: String
|
|
let mealType: String?
|
|
|
|
var id: String { "\(recipeId)-\(dateString)" }
|
|
}
|